mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-08 07:05:23 +08:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ef925153d | |||
| ec33e9378c | |||
| 70fb77b199 | |||
| 442bd69222 | |||
| 25e33273a9 | |||
| 26979ce6f7 | |||
| 33087a2881 | |||
| ecf1499609 | |||
| e89f9ef839 | |||
| 931b7f6785 | |||
| 59530e3282 | |||
| 2d0ed8d6cd | |||
| dbba39e7e0 | |||
| 295b8bdbf9 | |||
| bda56d9462 | |||
| 0eda7f4727 | |||
| 00b4ed2f90 | |||
| 2b7ee8e9c6 | |||
| 79f39ff931 | |||
| b54712ed7d | |||
| 272d006037 | |||
| 51b25da6f8 | |||
| 8f2ef73d4b | |||
| a964d457a9 | |||
| 0279d2711f | |||
| 20bc5166b3 | |||
| cc0b5a4c14 | |||
| 145d48a02e | |||
| b3f017fc31 | |||
| 3eb30d64d3 | |||
| 06c2935d83 | |||
| b3621aa555 | |||
| a8c6000d86 | |||
| b490f3d753 | |||
| eaf95c7d1a | |||
| 2ae91c6097 | |||
| cb671ea1ca | |||
| e8d332d97e | |||
| ee50b8d499 | |||
| 3a35542f86 | |||
| 3881a678d5 | |||
| 4246888101 | |||
| 2e276973e4 | |||
| 8abd41a544 | |||
| e91d1a21a6 | |||
| 6f62322fe4 | |||
| 6301dbe7a7 | |||
| 963af52f15 | |||
| e70629e5f7 | |||
| 8599efabab | |||
| 93b80ca7ba | |||
| ccf7676455 | |||
| 6fd0ed4993 | |||
| ceeca7542d | |||
| a6ce5c823b | |||
| 921cdd37e2 | |||
| a9d168cfd7 | |||
| 43aee93d2b | |||
| d300d10962 | |||
| 617f479266 | |||
| 5c6346c420 | |||
| fb09ef05dc | |||
| 4fc216649d | |||
| 5fd152b7ad | |||
| 6c50fe1ec9 | |||
| 66cd07c6d9 | |||
| d2e3822d67 |
+34
-24
@@ -71,79 +71,89 @@ matrix:
|
||||
- os: linux
|
||||
compiler: clang
|
||||
# Exclude most osx, arm64 and ppc64le tests for pull requests, but build in branches
|
||||
- if: type != pull_request
|
||||
# NB: the cmake build is a partial java test
|
||||
- if: type = pull_request
|
||||
os: osx
|
||||
env: TEST_GROUP=1
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=1
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=1
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os: osx
|
||||
env: TEST_GROUP=2
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=2
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=2
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os: osx
|
||||
env: TEST_GROUP=3
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=3
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=3
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os: osx
|
||||
env: TEST_GROUP=4
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=4
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=4
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os : osx
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=java-test
|
||||
- if: type != pull_request
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=java-test
|
||||
- if: type != pull_request
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request
|
||||
os : osx
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=lite-build
|
||||
- if: type != pull_request
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=lite-build
|
||||
- if: type != pull_request
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request
|
||||
os : osx
|
||||
env: JOB_NAME=examples
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=examples
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=examples
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- if: type != pull_request
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
|
||||
@@ -512,6 +512,7 @@ set(SOURCES
|
||||
db/arena_wrapped_db_iter.cc
|
||||
db/blob/blob_file_addition.cc
|
||||
db/blob/blob_file_garbage.cc
|
||||
db/blob/blob_file_meta.cc
|
||||
db/builder.cc
|
||||
db/c.cc
|
||||
db/column_family.cc
|
||||
@@ -565,6 +566,7 @@ set(SOURCES
|
||||
db/trim_history_scheduler.cc
|
||||
db/version_builder.cc
|
||||
db/version_edit.cc
|
||||
db/version_edit_handler.cc
|
||||
db/version_set.cc
|
||||
db/wal_manager.cc
|
||||
db/write_batch.cc
|
||||
@@ -1022,6 +1024,7 @@ if(WITH_TESTS)
|
||||
env/io_posix_test.cc
|
||||
env/mock_env_test.cc
|
||||
file/delete_scheduler_test.cc
|
||||
file/random_access_file_reader_test.cc
|
||||
logging/auto_roll_logger_test.cc
|
||||
logging/env_logger_test.cc
|
||||
logging/event_logger_test.cc
|
||||
|
||||
+44
-1
@@ -1,18 +1,61 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 6.9.4 (05/08/2020)
|
||||
### Bug Fixes
|
||||
* Fix a bug caused by overwrite the status with io status in block based table builder when writing data blocks. If status stores the error message (e.g., failure of verify block compression), the bug will make the io status overwrite the status.
|
||||
* 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.
|
||||
* Fix corruption caused by enabling delete triggered compaction (NewCompactOnDeletionCollectorFactory) in universal compaction mode, along with parallel compactions. The bug can result in two parallel compactions picking the same input files, resulting in the DB resurrecting older and deleted versions of some keys.
|
||||
|
||||
### Behavior Changes
|
||||
* Disable delete triggered compaction (NewCompactOnDeletionCollectorFactory) in universal compaction mode and num_levels = 1 in order to avoid a corruption bug.
|
||||
|
||||
## 6.9.3 (04/28/2020)
|
||||
### Bug Fixes
|
||||
* Upgraded version of bzip library (1.0.6 -> 1.0.8) used with RocksJava to address potential vulnerabilities if an attacker can manipulate compressed data saved and loaded by RocksDB (not normal). See issue #6703.
|
||||
* Fix a bug by updating CURRENT file so that it points to the correct MANIFEST file after best-efforts recovery.
|
||||
* Fix a potential undefined behavior caused by trying to dereference nullable pointer (timestamp argument) in DB::MultiGet.
|
||||
* Fix a bug caused by not including user timestamp in MultiGet LookupKey construction. This can lead to wrong query result since the trailing bytes of a user key, if not shorter than timestamp, will be mistaken for user timestamp.
|
||||
* Fix a bug caused by using wrong compare function when sorting the input keys of MultiGet with timestamps.
|
||||
|
||||
## 6.9.2 (04/16/2020)
|
||||
### Public API Change
|
||||
* Add NewFileChecksumGenCrc32cFactory to the file checksum public API, such that the builtin Crc32c based file checksum generator factory can be used by applications.
|
||||
|
||||
### New Features
|
||||
* Log CompactOnDeletionCollectorFactory window_size and deletion_trigger parameters in the info log file for troubleshooting purposes.
|
||||
* Report kFilesMarkedForCompaction for delete triggered compactions in case of Universal and Levelled Compactions.
|
||||
|
||||
## 6.9.1 (04/09/2020)
|
||||
### Bug Fixes
|
||||
* Fix wrong result being read from ingested file. May happen when a key in the file happen to be prefix of another key also in the file. The issue can further cause more data corruption. The issue exists with rocksdb >= 5.0.0 since DB::IngestExternalFile() was introduced.
|
||||
* Fix a bug when making options.bottommost_compression, options.compression_opts and options.bottommost_compression_opts dynamically changeable: the modified values are not written to option files or returned back to users when being queried.
|
||||
|
||||
## 6.9.0 (03/29/2020)
|
||||
### Behavior changes
|
||||
* Since RocksDB 6.8, ttl-based FIFO compaction can drop a file whose oldest key becomes older than options.ttl while others have not. This fix reverts this and makes ttl-based FIFO compaction use the file's flush time as the criterion. This fix also requires that max_open_files = -1 and compaction_options_fifo.allow_compaction = false to function properly.
|
||||
|
||||
### Public API Change
|
||||
* Fix spelling so that API now has correctly spelled transaction state name `COMMITTED`, while the old misspelled `COMMITED` is still available as an alias.
|
||||
* Updated default format_version in BlockBasedTableOptions from 2 to 4. SST files generated with the new default can be read by RocksDB versions 5.16 and newer, and use more efficient encoding of keys in index blocks.
|
||||
* A new parameter `CreateBackupOptions` is added to both `BackupEngine::CreateNewBackup` and `BackupEngine::CreateNewBackupWithMetadata`, you can decrease CPU priority of `BackupEngine`'s background threads by setting `decrease_background_thread_cpu_priority` and `background_thread_cpu_priority` in `CreateBackupOptions`.
|
||||
* Updated the public API of SST file checksum. Introduce the FileChecksumGenFactory to create the FileChecksumGenerator for each SST file, such that the FileChecksumGenerator is not shared and it can be more general for checksum implementations. Changed the FileChecksumGenerator interface from Value, Extend, and GetChecksum to Update, Finalize, and GetChecksum. Finalize should be only called once after all data is processed to generate the final checksum. Temproal data should be maintained by the FileChecksumGenerator object itself and finally it can return the checksum string.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug where range tombstone blocks in ingested files were cached incorrectly during ingestion. If range tombstones were read from those incorrectly cached blocks, the keys they covered would be exposed.
|
||||
* Fix a data race that might cause crash when calling DB::GetCreationTimeOfOldestFile() by a small chance. The bug was introduced in 6.6 Release.
|
||||
* Fix a bug where a boolean value optimize_filters_for_hits was for max threads when calling load table handles after a flush or compaction. The value is correct to 1. The bug should not cause user visible problems.
|
||||
* Fix a bug which might crash the service when write buffer manager fails to insert the dummy handle to the block cache.
|
||||
|
||||
### Performance Improvements
|
||||
* In CompactRange, for levels starting from 0, if the level does not have any file with any key falling in the specified range, the level is skipped. So instead of always compacting from level 0, the compaction starts from the first level with keys in the specified range until the last such level.
|
||||
* Reduced memory copy when reading sst footer and blobdb in direct IO mode.
|
||||
* When restarting a database with large numbers of sst files, large amount of CPU time is spent on getting logical block size of the sst files, which slows down the starting progress, this inefficiency is optimized away with an internal cache for the logical block sizes.
|
||||
|
||||
### New Features
|
||||
* Basic support for user timestamp in iterator. Seek/SeekToFirst/Next and lower/upper bounds are supported. Reverse iteration is not supported. Merge is not considered.
|
||||
* When file lock failure when the lock is held by the current process, return acquiring time and thread ID in the error message.
|
||||
* Added a new option, best_efforts_recovery (default: false), to allow database to open in a db dir with missing table files. During best efforts recovery, missing table files are ignored, and database recovers to the most recent state without missing table file. Cross-column-family consistency is not guaranteed even if WAL is enabled.
|
||||
* options.bottommost_compression, options.compression_opts and options.bottommost_compression_opts are now dynamically changeable.
|
||||
|
||||
## 6.8.0 (02/24/2020)
|
||||
### Java API Changes
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
BASH_EXISTS := $(shell which bash)
|
||||
SHELL := $(shell which bash)
|
||||
PYTHON?=$(shell which python)
|
||||
|
||||
CLEAN_FILES = # deliberately empty, so we can append below.
|
||||
CFLAGS += ${EXTRA_CFLAGS}
|
||||
@@ -513,6 +514,7 @@ TESTS = \
|
||||
fault_injection_test \
|
||||
filelock_test \
|
||||
filename_test \
|
||||
random_access_file_reader_test \
|
||||
file_reader_writer_test \
|
||||
block_based_filter_block_test \
|
||||
full_filter_block_test \
|
||||
@@ -947,9 +949,9 @@ check: all
|
||||
fi
|
||||
rm -rf $(TMPD)
|
||||
ifneq ($(PLATFORM), OS_AIX)
|
||||
python tools/check_all_python.py
|
||||
$(PYTHON) tools/check_all_python.py
|
||||
ifeq ($(filter -DROCKSDB_LITE,$(OPT)),)
|
||||
python tools/ldb_test.py
|
||||
$(PYTHON) tools/ldb_test.py
|
||||
sh tools/rocksdb_dump_test.sh
|
||||
endif
|
||||
endif
|
||||
@@ -960,7 +962,7 @@ check_some: $(SUBSET)
|
||||
|
||||
.PHONY: ldb_tests
|
||||
ldb_tests: ldb
|
||||
python tools/ldb_test.py
|
||||
$(PYTHON) tools/ldb_test.py
|
||||
|
||||
crash_test: whitebox_crash_test blackbox_crash_test
|
||||
|
||||
@@ -969,31 +971,31 @@ crash_test_with_atomic_flush: whitebox_crash_test_with_atomic_flush blackbox_cra
|
||||
crash_test_with_txn: whitebox_crash_test_with_txn blackbox_crash_test_with_txn
|
||||
|
||||
blackbox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
python -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_atomic_flush: db_stress
|
||||
python -u tools/db_crashtest.py --cf_consistency blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py --cf_consistency blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_txn: db_stress
|
||||
python -u tools/db_crashtest.py --txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py --txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
|
||||
whitebox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py --simple whitebox --random_kill_odd \
|
||||
$(PYTHON) -u tools/db_crashtest.py --simple whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
python -u tools/db_crashtest.py whitebox --random_kill_odd \
|
||||
$(PYTHON) -u tools/db_crashtest.py whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_atomic_flush: db_stress
|
||||
python -u tools/db_crashtest.py --cf_consistency whitebox --random_kill_odd \
|
||||
$(PYTHON) -u tools/db_crashtest.py --cf_consistency whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_txn: db_stress
|
||||
python -u tools/db_crashtest.py --txn whitebox --random_kill_odd \
|
||||
$(PYTHON) -u tools/db_crashtest.py --txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
asan_check:
|
||||
@@ -1505,6 +1507,9 @@ delete_scheduler_test: file/delete_scheduler_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
filename_test: db/filename_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
random_access_file_reader_test: file/random_access_file_reader_test.o $(LIBOBJECTS) $(TESTHARNESS) $(TESTUTIL)
|
||||
$(AM_LINK)
|
||||
|
||||
file_reader_writer_test: util/file_reader_writer_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1818,9 +1823,9 @@ SHA256_CMD = sha256sum
|
||||
ZLIB_VER ?= 1.2.11
|
||||
ZLIB_SHA256 ?= c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
|
||||
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.6
|
||||
BZIP2_SHA256 ?= a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd
|
||||
BZIP2_DOWNLOAD_BASE ?= https://downloads.sourceforge.net/project/bzip2
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
BZIP2_DOWNLOAD_BASE ?= https://sourceware.org/pub/bzip2
|
||||
SNAPPY_VER ?= 1.1.8
|
||||
SNAPPY_SHA256 ?= 16b677f07832a612b0836178db7f374e414f94657c138e6993cbfc5dcc58651f
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
@@ -2089,7 +2094,7 @@ jtest_run:
|
||||
|
||||
jtest: rocksdbjava
|
||||
cd java;$(MAKE) sample;$(MAKE) test;
|
||||
python tools/check_all_python.py # TODO peterd: find a better place for this check in CI targets
|
||||
$(PYTHON) tools/check_all_python.py # TODO peterd: find a better place for this check in CI targets
|
||||
|
||||
jdb_bench:
|
||||
cd java;$(MAKE) db_bench;
|
||||
|
||||
@@ -118,6 +118,7 @@ cpp_library(
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/builder.cc",
|
||||
"db/c.cc",
|
||||
"db/column_family.cc",
|
||||
@@ -171,6 +172,7 @@ cpp_library(
|
||||
"db/trim_history_scheduler.cc",
|
||||
"db/version_builder.cc",
|
||||
"db/version_edit.cc",
|
||||
"db/version_edit_handler.cc",
|
||||
"db/version_set.cc",
|
||||
"db/wal_manager.cc",
|
||||
"db/write_batch.cc",
|
||||
@@ -1300,6 +1302,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"random_access_file_reader_test",
|
||||
"file/random_access_file_reader_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"random_test",
|
||||
"util/random_test.cc",
|
||||
|
||||
@@ -17,16 +17,19 @@ if [ -d /mnt/gvfs/third-party -a -z "$CXX" ]; then
|
||||
else
|
||||
GCOV=$(which gcov)
|
||||
fi
|
||||
echo -e "Using $GCOV"
|
||||
|
||||
COVERAGE_DIR="$PWD/COVERAGE_REPORT"
|
||||
mkdir -p $COVERAGE_DIR
|
||||
|
||||
# Find all gcno files to generate the coverage report
|
||||
|
||||
PYTHON=${1:-`which python`}
|
||||
echo -e "Using $PYTHON"
|
||||
GCNO_FILES=`find $ROOT -name "*.gcno"`
|
||||
$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null |
|
||||
# Parse the raw gcov report to more human readable form.
|
||||
python $ROOT/coverage/parse_gcov_output.py |
|
||||
$PYTHON $ROOT/coverage/parse_gcov_output.py |
|
||||
# Write the output to both stdout and report file.
|
||||
tee $COVERAGE_DIR/coverage_report_all.txt &&
|
||||
echo -e "Generated coverage report for all files: $COVERAGE_DIR/coverage_report_all.txt\n"
|
||||
@@ -41,7 +44,7 @@ RECENT_REPORT=$COVERAGE_DIR/coverage_report_recent.txt
|
||||
|
||||
echo -e "Recently updated files: $LATEST_FILES\n" > $RECENT_REPORT
|
||||
$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null |
|
||||
python $ROOT/coverage/parse_gcov_output.py -interested-files $LATEST_FILES |
|
||||
$PYTHON $ROOT/coverage/parse_gcov_output.py -interested-files $LATEST_FILES |
|
||||
tee -a $RECENT_REPORT &&
|
||||
echo -e "Generated coverage report for recently updated files: $RECENT_REPORT\n"
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import optparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
# the gcov report follows certain pattern. Each file will have two lines
|
||||
# of report, from which we can extract the file name, total lines and coverage
|
||||
# percentage.
|
||||
@@ -48,7 +50,7 @@ def parse_gcov_report(gcov_input):
|
||||
def get_option_parser():
|
||||
usage = "Parse the gcov output and generate more human-readable code " +\
|
||||
"coverage report."
|
||||
parser = OptionParser(usage)
|
||||
parser = optparse.OptionParser(usage)
|
||||
|
||||
parser.add_option(
|
||||
"--interested-files", "-i",
|
||||
@@ -73,8 +75,8 @@ def display_file_coverage(per_file_coverage, total_coverage):
|
||||
header_template = \
|
||||
"%" + str(max_file_name_length) + "s\t%s\t%s"
|
||||
separator = "-" * (max_file_name_length + 10 + 20)
|
||||
print header_template % ("Filename", "Coverage", "Lines") # noqa: E999 T25377293 Grandfathered in
|
||||
print separator
|
||||
print(header_template % ("Filename", "Coverage", "Lines")) # noqa: E999 T25377293 Grandfathered in
|
||||
print(separator)
|
||||
|
||||
# -- Print body
|
||||
# template for printing coverage report for each file.
|
||||
@@ -82,12 +84,12 @@ def display_file_coverage(per_file_coverage, total_coverage):
|
||||
|
||||
for fname, coverage_info in per_file_coverage.items():
|
||||
coverage, lines = coverage_info
|
||||
print record_template % (fname, coverage, lines)
|
||||
print(record_template % (fname, coverage, lines))
|
||||
|
||||
# -- Print footer
|
||||
if total_coverage:
|
||||
print separator
|
||||
print record_template % ("Total", total_coverage[0], total_coverage[1])
|
||||
print(separator)
|
||||
print(record_template % ("Total", total_coverage[0], total_coverage[1]))
|
||||
|
||||
def report_coverage():
|
||||
parser = get_option_parser()
|
||||
@@ -111,7 +113,7 @@ def report_coverage():
|
||||
total_coverage = None
|
||||
|
||||
if not len(per_file_coverage):
|
||||
print >> sys.stderr, "Cannot find coverage info for the given files."
|
||||
print("Cannot find coverage info for the given files.", file=sys.stderr)
|
||||
return
|
||||
display_file_coverage(per_file_coverage, total_coverage)
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
SharedBlobFileMetaData::~SharedBlobFileMetaData() {
|
||||
// TODO: add the blob file to the list of obsolete files here
|
||||
}
|
||||
|
||||
std::string SharedBlobFileMetaData::DebugString() const {
|
||||
std::ostringstream oss;
|
||||
oss << (*this);
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const SharedBlobFileMetaData& shared_meta) {
|
||||
os << "blob_file_number: " << shared_meta.GetBlobFileNumber()
|
||||
<< " total_blob_count: " << shared_meta.GetTotalBlobCount()
|
||||
<< " total_blob_bytes: " << shared_meta.GetTotalBlobBytes()
|
||||
<< " checksum_method: " << shared_meta.GetChecksumMethod()
|
||||
<< " checksum_value: " << shared_meta.GetChecksumValue();
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string BlobFileMetaData::DebugString() const {
|
||||
std::ostringstream oss;
|
||||
oss << (*this);
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const BlobFileMetaData& meta) {
|
||||
const auto& shared_meta = meta.GetSharedMeta();
|
||||
assert(shared_meta);
|
||||
|
||||
os << (*shared_meta) << " garbage_blob_count: " << meta.GetGarbageBlobCount()
|
||||
<< " garbage_blob_bytes: " << meta.GetGarbageBlobBytes();
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// SharedBlobFileMetaData represents the immutable part of blob files' metadata,
|
||||
// like the blob file number, total number and size of blobs, or checksum
|
||||
// method and value. There is supposed to be one object of this class per blob
|
||||
// file (shared across all versions that include the blob file in question);
|
||||
// hence, the type is neither copyable nor movable. A blob file can be marked
|
||||
// obsolete when the corresponding SharedBlobFileMetaData object is destroyed.
|
||||
|
||||
class SharedBlobFileMetaData {
|
||||
public:
|
||||
SharedBlobFileMetaData(uint64_t blob_file_number, uint64_t total_blob_count,
|
||||
uint64_t total_blob_bytes, std::string checksum_method,
|
||||
std::string checksum_value)
|
||||
: blob_file_number_(blob_file_number),
|
||||
total_blob_count_(total_blob_count),
|
||||
total_blob_bytes_(total_blob_bytes),
|
||||
checksum_method_(std::move(checksum_method)),
|
||||
checksum_value_(std::move(checksum_value)) {
|
||||
assert(checksum_method_.empty() == checksum_value_.empty());
|
||||
}
|
||||
|
||||
~SharedBlobFileMetaData();
|
||||
|
||||
SharedBlobFileMetaData(const SharedBlobFileMetaData&) = delete;
|
||||
SharedBlobFileMetaData& operator=(const SharedBlobFileMetaData&) = delete;
|
||||
|
||||
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
|
||||
uint64_t GetTotalBlobCount() const { return total_blob_count_; }
|
||||
uint64_t GetTotalBlobBytes() const { return total_blob_bytes_; }
|
||||
const std::string& GetChecksumMethod() const { return checksum_method_; }
|
||||
const std::string& GetChecksumValue() const { return checksum_value_; }
|
||||
|
||||
std::string DebugString() const;
|
||||
|
||||
private:
|
||||
uint64_t blob_file_number_;
|
||||
uint64_t total_blob_count_;
|
||||
uint64_t total_blob_bytes_;
|
||||
std::string checksum_method_;
|
||||
std::string checksum_value_;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const SharedBlobFileMetaData& shared_meta);
|
||||
|
||||
// BlobFileMetaData contains the part of the metadata for blob files that can
|
||||
// vary across versions, like the amount of garbage in the blob file. In
|
||||
// addition, BlobFileMetaData objects point to and share the ownership of the
|
||||
// SharedBlobFileMetaData object for the corresponding blob file. Similarly to
|
||||
// SharedBlobFileMetaData, BlobFileMetaData are not copyable or movable. They
|
||||
// are meant to be jointly owned by the versions in which the blob file has the
|
||||
// same (immutable *and* mutable) state.
|
||||
|
||||
class BlobFileMetaData {
|
||||
public:
|
||||
BlobFileMetaData(std::shared_ptr<SharedBlobFileMetaData> shared_meta,
|
||||
uint64_t garbage_blob_count, uint64_t garbage_blob_bytes)
|
||||
: shared_meta_(std::move(shared_meta)),
|
||||
garbage_blob_count_(garbage_blob_count),
|
||||
garbage_blob_bytes_(garbage_blob_bytes) {
|
||||
assert(shared_meta_);
|
||||
assert(garbage_blob_count_ <= shared_meta_->GetTotalBlobCount());
|
||||
assert(garbage_blob_bytes_ <= shared_meta_->GetTotalBlobBytes());
|
||||
}
|
||||
|
||||
~BlobFileMetaData() = default;
|
||||
|
||||
BlobFileMetaData(const BlobFileMetaData&) = delete;
|
||||
BlobFileMetaData& operator=(const BlobFileMetaData&) = delete;
|
||||
|
||||
const std::shared_ptr<SharedBlobFileMetaData>& GetSharedMeta() const {
|
||||
return shared_meta_;
|
||||
}
|
||||
|
||||
uint64_t GetBlobFileNumber() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetBlobFileNumber();
|
||||
}
|
||||
uint64_t GetTotalBlobCount() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetTotalBlobCount();
|
||||
}
|
||||
uint64_t GetTotalBlobBytes() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetTotalBlobBytes();
|
||||
}
|
||||
const std::string& GetChecksumMethod() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetChecksumMethod();
|
||||
}
|
||||
const std::string& GetChecksumValue() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetChecksumValue();
|
||||
}
|
||||
|
||||
uint64_t GetGarbageBlobCount() const { return garbage_blob_count_; }
|
||||
uint64_t GetGarbageBlobBytes() const { return garbage_blob_bytes_; }
|
||||
|
||||
std::string DebugString() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<SharedBlobFileMetaData> shared_meta_;
|
||||
uint64_t garbage_blob_count_;
|
||||
uint64_t garbage_blob_bytes_;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const BlobFileMetaData& meta);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+22
-11
@@ -81,10 +81,11 @@ Status BuildTable(
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
bool paranoid_file_checks, InternalStats* internal_stats,
|
||||
TableFileCreationReason reason, EventLogger* event_logger, int job_id,
|
||||
const Env::IOPriority io_priority, TableProperties* table_properties,
|
||||
int level, const uint64_t creation_time, const uint64_t oldest_key_time,
|
||||
Env::WriteLifeTimeHint write_hint, const uint64_t file_creation_time) {
|
||||
TableFileCreationReason reason, IOStatus* io_status,
|
||||
EventLogger* event_logger, int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, Env::WriteLifeTimeHint write_hint,
|
||||
const uint64_t file_creation_time) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
@@ -133,7 +134,7 @@ Status BuildTable(
|
||||
|
||||
file_writer.reset(new WritableFileWriter(
|
||||
std::move(file), fname, file_options, env, ioptions.statistics,
|
||||
ioptions.listeners, ioptions.sst_file_checksum_func));
|
||||
ioptions.listeners, ioptions.file_checksum_gen_factory));
|
||||
|
||||
builder = NewTableBuilder(
|
||||
ioptions, mutable_cf_options, internal_comparator,
|
||||
@@ -185,11 +186,13 @@ Status BuildTable(
|
||||
tp = builder->GetTableProperties();
|
||||
bool empty = builder->NumEntries() == 0 && tp.num_range_deletions == 0;
|
||||
s = c_iter.status();
|
||||
TEST_SYNC_POINT("BuildTable:BeforeFinishBuildTable");
|
||||
if (!s.ok() || empty) {
|
||||
builder->Abandon();
|
||||
} else {
|
||||
s = builder->Finish();
|
||||
}
|
||||
*io_status = builder->io_status();
|
||||
|
||||
if (s.ok() && !empty) {
|
||||
uint64_t file_size = builder->FileSize();
|
||||
@@ -200,20 +203,28 @@ Status BuildTable(
|
||||
if (table_properties) {
|
||||
*table_properties = tp;
|
||||
}
|
||||
// Add the checksum information to file metadata.
|
||||
meta->file_checksum = builder->GetFileChecksum();
|
||||
meta->file_checksum_func_name = builder->GetFileChecksumFuncName();
|
||||
}
|
||||
delete builder;
|
||||
|
||||
// Finish and check for file errors
|
||||
if (s.ok() && !empty) {
|
||||
StopWatch sw(env, ioptions.statistics, TABLE_SYNC_MICROS);
|
||||
s = file_writer->Sync(ioptions.use_fsync);
|
||||
*io_status = file_writer->Sync(ioptions.use_fsync);
|
||||
}
|
||||
if (s.ok() && !empty) {
|
||||
s = file_writer->Close();
|
||||
if (io_status->ok() && !empty) {
|
||||
*io_status = file_writer->Close();
|
||||
}
|
||||
if (io_status->ok() && !empty) {
|
||||
// Add the checksum information to file metadata.
|
||||
meta->file_checksum = file_writer->GetFileChecksum();
|
||||
meta->file_checksum_func_name = file_writer->GetFileChecksumFuncName();
|
||||
}
|
||||
|
||||
if (!io_status->ok()) {
|
||||
s = *io_status;
|
||||
}
|
||||
|
||||
// TODO Also check the IO status when create the Iterator.
|
||||
|
||||
if (s.ok() && !empty) {
|
||||
// Verify that the table is usable
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ extern Status BuildTable(
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
IOStatus* io_status, EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
const Env::IOPriority io_priority = Env::IO_HIGH,
|
||||
TableProperties* table_properties = nullptr, int level = -1,
|
||||
const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
|
||||
|
||||
+4
-4
@@ -1397,8 +1397,8 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
const ImmutableDBOptions* db_options,
|
||||
const FileOptions& file_options,
|
||||
Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
WriteBufferManager* _write_buffer_manager,
|
||||
WriteController* _write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer)
|
||||
: max_column_family_(0),
|
||||
dummy_cfd_(new ColumnFamilyData(
|
||||
@@ -1410,8 +1410,8 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
db_options_(db_options),
|
||||
file_options_(file_options),
|
||||
table_cache_(table_cache),
|
||||
write_buffer_manager_(write_buffer_manager),
|
||||
write_controller_(write_controller),
|
||||
write_buffer_manager_(_write_buffer_manager),
|
||||
write_controller_(_write_controller),
|
||||
block_cache_tracer_(block_cache_tracer) {
|
||||
// initialize linked list
|
||||
dummy_cfd_->prev_ = dummy_cfd_;
|
||||
|
||||
+6
-2
@@ -647,8 +647,8 @@ class ColumnFamilySet {
|
||||
ColumnFamilySet(const std::string& dbname,
|
||||
const ImmutableDBOptions* db_options,
|
||||
const FileOptions& file_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
WriteBufferManager* _write_buffer_manager,
|
||||
WriteController* _write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer);
|
||||
~ColumnFamilySet();
|
||||
|
||||
@@ -678,6 +678,10 @@ class ColumnFamilySet {
|
||||
|
||||
Cache* get_table_cache() { return table_cache_; }
|
||||
|
||||
WriteBufferManager* write_buffer_manager() { return write_buffer_manager_; }
|
||||
|
||||
WriteController* write_controller() { return write_controller_; }
|
||||
|
||||
private:
|
||||
friend class ColumnFamilyData;
|
||||
// helper function that gets called from cfd destructor
|
||||
|
||||
@@ -613,8 +613,13 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
}
|
||||
|
||||
IOStatus io_s;
|
||||
if (status.ok() && output_directory_) {
|
||||
status = output_directory_->Fsync(IOOptions(), nullptr);
|
||||
io_s = output_directory_->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
status = io_s;
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
@@ -713,9 +718,13 @@ 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);
|
||||
}
|
||||
if (!versions_->io_status().ok()) {
|
||||
io_status_ = versions_->io_status();
|
||||
}
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
auto vstorage = cfd->current()->storage_info();
|
||||
const auto& stats = compaction_stats_;
|
||||
@@ -1294,25 +1303,35 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
} else {
|
||||
sub_compact->builder->Abandon();
|
||||
}
|
||||
if (!sub_compact->builder->io_status().ok()) {
|
||||
io_status_ = sub_compact->builder->io_status();
|
||||
s = io_status_;
|
||||
}
|
||||
const uint64_t current_bytes = sub_compact->builder->FileSize();
|
||||
if (s.ok()) {
|
||||
// Add the checksum information to file metadata.
|
||||
meta->file_checksum = sub_compact->builder->GetFileChecksum();
|
||||
meta->file_checksum_func_name =
|
||||
sub_compact->builder->GetFileChecksumFuncName();
|
||||
|
||||
meta->fd.file_size = current_bytes;
|
||||
}
|
||||
sub_compact->current_output()->finished = true;
|
||||
sub_compact->total_bytes += current_bytes;
|
||||
|
||||
// Finish and check for file errors
|
||||
IOStatus io_s;
|
||||
if (s.ok()) {
|
||||
StopWatch sw(env_, stats_, COMPACTION_OUTFILE_SYNC_MICROS);
|
||||
s = sub_compact->outfile->Sync(db_options_.use_fsync);
|
||||
io_s = sub_compact->outfile->Sync(db_options_.use_fsync);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = sub_compact->outfile->Close();
|
||||
if (io_s.ok()) {
|
||||
io_s = sub_compact->outfile->Close();
|
||||
}
|
||||
if (io_s.ok()) {
|
||||
// Add the checksum information to file metadata.
|
||||
meta->file_checksum = sub_compact->outfile->GetFileChecksum();
|
||||
meta->file_checksum_func_name =
|
||||
sub_compact->outfile->GetFileChecksumFuncName();
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
s = io_s;
|
||||
}
|
||||
sub_compact->outfile.reset();
|
||||
|
||||
@@ -1514,7 +1533,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
sub_compact->outfile.reset(
|
||||
new WritableFileWriter(std::move(writable_file), fname, file_options_,
|
||||
env_, db_options_.statistics.get(), listeners,
|
||||
db_options_.sst_file_checksum_func.get()));
|
||||
db_options_.file_checksum_gen_factory.get()));
|
||||
|
||||
// If the Column family flag is to only optimize filters for hits,
|
||||
// we can skip creating filters if this is the bottommost_level where
|
||||
|
||||
@@ -100,6 +100,9 @@ class CompactionJob {
|
||||
// Add compaction input/output to the current version
|
||||
Status Install(const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// Return the IO status
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
private:
|
||||
struct SubcompactionState;
|
||||
|
||||
@@ -193,6 +196,7 @@ class CompactionJob {
|
||||
std::vector<uint64_t> sizes_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
Env::Priority thread_pri_;
|
||||
IOStatus io_status_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -281,7 +281,7 @@ class CompactionJobTest : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
@@ -314,11 +314,11 @@ class CompactionJobTest : public testing::Test {
|
||||
num_input_files += level_files.size();
|
||||
}
|
||||
|
||||
Compaction compaction(cfd->current()->storage_info(), *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
compaction_input_files, output_level, 1024 * 1024,
|
||||
10 * 1024 * 1024, 0, kNoCompression,
|
||||
cfd->ioptions()->compression_opts, 0, {}, true);
|
||||
Compaction compaction(
|
||||
cfd->current()->storage_info(), *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(), compaction_input_files, output_level,
|
||||
1024 * 1024, 10 * 1024 * 1024, 0, kNoCompression,
|
||||
cfd->GetLatestMutableCFOptions()->compression_opts, 0, {}, true);
|
||||
compaction.SetInputVersion(cfd->current());
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
|
||||
@@ -110,9 +110,9 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
|
||||
// If bottommost_compression is set and we are compacting to the
|
||||
// bottommost level then we should use it.
|
||||
if (ioptions.bottommost_compression != kDisableCompressionOption &&
|
||||
if (mutable_cf_options.bottommost_compression != kDisableCompressionOption &&
|
||||
level >= (vstorage->num_non_empty_levels() - 1)) {
|
||||
return ioptions.bottommost_compression;
|
||||
return mutable_cf_options.bottommost_compression;
|
||||
}
|
||||
// If the user has specified a different compression level for each level,
|
||||
// then pick the compression for that level.
|
||||
@@ -132,22 +132,22 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
}
|
||||
}
|
||||
|
||||
CompressionOptions GetCompressionOptions(const ImmutableCFOptions& ioptions,
|
||||
CompressionOptions GetCompressionOptions(const MutableCFOptions& cf_options,
|
||||
const VersionStorageInfo* vstorage,
|
||||
int level,
|
||||
const bool enable_compression) {
|
||||
if (!enable_compression) {
|
||||
return ioptions.compression_opts;
|
||||
return cf_options.compression_opts;
|
||||
}
|
||||
// If bottommost_compression is set and we are compacting to the
|
||||
// bottommost level then we should use the specified compression options
|
||||
// for the bottmomost_compression.
|
||||
if (ioptions.bottommost_compression != kDisableCompressionOption &&
|
||||
if (cf_options.bottommost_compression != kDisableCompressionOption &&
|
||||
level >= (vstorage->num_non_empty_levels() - 1) &&
|
||||
ioptions.bottommost_compression_opts.enabled) {
|
||||
return ioptions.bottommost_compression_opts;
|
||||
cf_options.bottommost_compression_opts.enabled) {
|
||||
return cf_options.bottommost_compression_opts;
|
||||
}
|
||||
return ioptions.compression_opts;
|
||||
return cf_options.compression_opts;
|
||||
}
|
||||
|
||||
CompactionPicker::CompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
@@ -359,7 +359,7 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
vstorage, ioptions_, mutable_cf_options, input_files, output_level,
|
||||
compact_options.output_file_size_limit,
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id, compression_type,
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, true);
|
||||
RegisterCompaction(c);
|
||||
@@ -634,7 +634,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_range_options.max_subcompactions, /* grandparents */ {},
|
||||
/* is manual */ true);
|
||||
RegisterCompaction(c);
|
||||
@@ -787,7 +787,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_range_options.max_subcompactions, std::move(grandparents),
|
||||
/* is manual compaction */ true);
|
||||
|
||||
@@ -1085,6 +1085,8 @@ void CompactionPicker::PickFilesMarkedForCompaction(
|
||||
Random64 rnd(/* seed */ reinterpret_cast<uint64_t>(vstorage));
|
||||
size_t random_file_index = static_cast<size_t>(rnd.Uniform(
|
||||
static_cast<uint64_t>(vstorage->FilesMarkedForCompaction().size())));
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::PickFilesMarkedForCompaction",
|
||||
&random_file_index);
|
||||
|
||||
if (continuation(vstorage->FilesMarkedForCompaction()[random_file_index])) {
|
||||
// found the compaction!
|
||||
|
||||
@@ -305,9 +305,9 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
int level, int base_level,
|
||||
const bool enable_compression = true);
|
||||
|
||||
CompressionOptions GetCompressionOptions(const ImmutableCFOptions& ioptions,
|
||||
const VersionStorageInfo* vstorage,
|
||||
int level,
|
||||
const bool enable_compression = true);
|
||||
CompressionOptions GetCompressionOptions(
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const VersionStorageInfo* vstorage, int level,
|
||||
const bool enable_compression = true);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -71,10 +71,13 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
if (current_time > mutable_cf_options.ttl) {
|
||||
for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) {
|
||||
FileMetaData* f = *ritr;
|
||||
uint64_t creation_time = f->TryGetFileCreationTime();
|
||||
if (creation_time == kUnknownFileCreationTime ||
|
||||
creation_time >= (current_time - mutable_cf_options.ttl)) {
|
||||
break;
|
||||
if (f->fd.table_reader && f->fd.table_reader->GetTableProperties()) {
|
||||
uint64_t creation_time =
|
||||
f->fd.table_reader->GetTableProperties()->creation_time;
|
||||
if (creation_time == 0 ||
|
||||
creation_time >= (current_time - mutable_cf_options.ttl)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
total_size -= f->compensated_file_size;
|
||||
inputs[0].files.push_back(f);
|
||||
@@ -92,17 +95,21 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
}
|
||||
|
||||
for (const auto& f : inputs[0].files) {
|
||||
uint64_t creation_time = 0;
|
||||
if (f && f->fd.table_reader && f->fd.table_reader->GetTableProperties()) {
|
||||
creation_time = f->fd.table_reader->GetTableProperties()->creation_time;
|
||||
}
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with creation time %" PRIu64 " for deletion",
|
||||
cf_name.c_str(), f->fd.GetNumber(),
|
||||
f->TryGetFileCreationTime());
|
||||
cf_name.c_str(), f->fd.GetNumber(), creation_time);
|
||||
}
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0,
|
||||
kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0,
|
||||
{}, /* is manual */ false, vstorage->CompactionScore(0),
|
||||
kNoCompression, mutable_cf_options.compression_opts,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, CompactionReason::kFIFOTtl);
|
||||
return c;
|
||||
}
|
||||
@@ -142,7 +149,7 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
16 * 1024 * 1024 /* output file size limit */,
|
||||
0 /* max compaction bytes, not applicable */,
|
||||
0 /* output path ID */, mutable_cf_options.compression,
|
||||
ioptions_.compression_opts, 0 /* max_subcompactions */, {},
|
||||
mutable_cf_options.compression_opts, 0 /* max_subcompactions */, {},
|
||||
/* is manual */ false, vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false,
|
||||
CompactionReason::kFIFOReduceNumFiles);
|
||||
@@ -190,8 +197,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0,
|
||||
kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0,
|
||||
{}, /* is manual */ false, vstorage->CompactionScore(0),
|
||||
kNoCompression, mutable_cf_options.compression_opts,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, CompactionReason::kFIFOMaxSize);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -250,7 +250,6 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
cf_name_, vstorage_, &start_level_, &output_level_,
|
||||
&start_level_inputs_);
|
||||
if (!start_level_inputs_.empty()) {
|
||||
is_manual_ = true;
|
||||
compaction_reason_ = CompactionReason::kFilesMarkedForCompaction;
|
||||
return;
|
||||
}
|
||||
@@ -384,7 +383,7 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
GetPathId(ioptions_, mutable_cf_options_, output_level_),
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level_, vstorage_->base_level()),
|
||||
GetCompressionOptions(ioptions_, vstorage_, output_level_),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level_),
|
||||
/* max_subcompactions */ 0, std::move(grandparents_), is_manual_,
|
||||
start_level_score_, false /* deletion_compaction */, compaction_reason_);
|
||||
|
||||
|
||||
@@ -78,8 +78,17 @@ class CompactionPickerTest : public testing::Test {
|
||||
vstorage_->CalculateBaseBytes(ioptions_, mutable_cf_options_);
|
||||
}
|
||||
|
||||
// Create a new VersionStorageInfo object so we can add mode files and then
|
||||
// merge it with the existing VersionStorageInfo
|
||||
void AddVersionStorage() {
|
||||
temp_vstorage_.reset(new VersionStorageInfo(
|
||||
&icmp_, ucmp_, options_.num_levels, ioptions_.compaction_style,
|
||||
vstorage_.get(), false));
|
||||
}
|
||||
|
||||
void DeleteVersionStorage() {
|
||||
vstorage_.reset();
|
||||
temp_vstorage_.reset();
|
||||
files_.clear();
|
||||
file_map_.clear();
|
||||
input_files_.clear();
|
||||
@@ -88,18 +97,24 @@ class CompactionPickerTest : public testing::Test {
|
||||
void Add(int level, uint32_t file_number, const char* smallest,
|
||||
const char* largest, uint64_t file_size = 1, uint32_t path_id = 0,
|
||||
SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100,
|
||||
size_t compensated_file_size = 0) {
|
||||
assert(level < vstorage_->num_levels());
|
||||
size_t compensated_file_size = 0, bool marked_for_compact = false) {
|
||||
VersionStorageInfo* vstorage;
|
||||
if (temp_vstorage_) {
|
||||
vstorage = temp_vstorage_.get();
|
||||
} else {
|
||||
vstorage = vstorage_.get();
|
||||
}
|
||||
assert(level < vstorage->num_levels());
|
||||
FileMetaData* f = new FileMetaData(
|
||||
file_number, path_id, file_size,
|
||||
InternalKey(smallest, smallest_seq, kTypeValue),
|
||||
InternalKey(largest, largest_seq, kTypeValue), smallest_seq,
|
||||
largest_seq, /* marked_for_compact */ false, kInvalidBlobFileNumber,
|
||||
largest_seq, marked_for_compact, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
f->compensated_file_size =
|
||||
(compensated_file_size != 0) ? compensated_file_size : file_size;
|
||||
vstorage_->AddFile(level, f);
|
||||
vstorage->AddFile(level, f);
|
||||
files_.emplace_back(f);
|
||||
file_map_.insert({file_number, {f, level}});
|
||||
}
|
||||
@@ -122,6 +137,11 @@ class CompactionPickerTest : public testing::Test {
|
||||
}
|
||||
|
||||
void UpdateVersionStorageInfo() {
|
||||
if (temp_vstorage_) {
|
||||
VersionBuilder builder(FileOptions(), nullptr, vstorage_.get());
|
||||
builder.SaveTo(temp_vstorage_.get());
|
||||
vstorage_ = std::move(temp_vstorage_);
|
||||
}
|
||||
vstorage_->CalculateBaseBytes(ioptions_, mutable_cf_options_);
|
||||
vstorage_->UpdateFilesByCompactionPri(ioptions_.compaction_pri);
|
||||
vstorage_->UpdateNumNonEmptyLevels();
|
||||
@@ -132,6 +152,27 @@ class CompactionPickerTest : public testing::Test {
|
||||
vstorage_->ComputeFilesMarkedForCompaction();
|
||||
vstorage_->SetFinalized();
|
||||
}
|
||||
void AddFileToVersionStorage(int level, uint32_t file_number,
|
||||
const char* smallest, const char* largest,
|
||||
uint64_t file_size = 1, uint32_t path_id = 0,
|
||||
SequenceNumber smallest_seq = 100,
|
||||
SequenceNumber largest_seq = 100,
|
||||
size_t compensated_file_size = 0,
|
||||
bool marked_for_compact = false) {
|
||||
VersionStorageInfo* base_vstorage = vstorage_.release();
|
||||
vstorage_.reset(new VersionStorageInfo(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleUniversal,
|
||||
base_vstorage, false));
|
||||
Add(level, file_number, smallest, largest, file_size, path_id, smallest_seq,
|
||||
largest_seq, compensated_file_size, marked_for_compact);
|
||||
|
||||
VersionBuilder builder(FileOptions(), nullptr, base_vstorage);
|
||||
builder.SaveTo(vstorage_.get());
|
||||
UpdateVersionStorageInfo();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<VersionStorageInfo> temp_vstorage_;
|
||||
};
|
||||
|
||||
TEST_F(CompactionPickerTest, Empty) {
|
||||
@@ -1733,6 +1774,163 @@ TEST_F(CompactionPickerTest, IntraL0ForEarliestSeqno) {
|
||||
ASSERT_EQ(0, compaction->output_level());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
ioptions_.compaction_style = kCompactionStyleUniversal;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
// This test covers the case where a "regular" universal compaction is
|
||||
// scheduled first, followed by a delete triggered compaction. The latter
|
||||
// should fail
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(0, 2U, "201", "250", 2 * kFileSize, 0, 401, 450);
|
||||
Add(0, 4U, "260", "300", 4 * kFileSize, 0, 260, 300);
|
||||
Add(3, 5U, "010", "080", 8 * kFileSize, 0, 200, 251);
|
||||
Add(4, 3U, "301", "350", 8 * kFileSize, 0, 101, 150);
|
||||
Add(4, 6U, "501", "750", 8 * kFileSize, 0, 101, 150);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
|
||||
ASSERT_TRUE(compaction);
|
||||
// Validate that its a compaction to reduce sorted runs
|
||||
ASSERT_EQ(CompactionReason::kUniversalSortedRunNum,
|
||||
compaction->compaction_reason());
|
||||
ASSERT_EQ(0, compaction->output_level());
|
||||
ASSERT_EQ(0, compaction->start_level());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
|
||||
AddVersionStorage();
|
||||
// Simulate a flush and mark the file for compaction
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 551, 600, 0, true);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction2(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_FALSE(compaction2);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap2) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
ioptions_.compaction_style = kCompactionStyleUniversal;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
// This test covers the case where a delete triggered compaction is
|
||||
// scheduled first, followed by a "regular" compaction. The latter
|
||||
// should fail
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
// Mark file number 4 for compaction
|
||||
Add(0, 4U, "260", "300", 4 * kFileSize, 0, 260, 300, 0, true);
|
||||
Add(3, 5U, "240", "290", 8 * kFileSize, 0, 201, 250);
|
||||
Add(4, 3U, "301", "350", 8 * kFileSize, 0, 101, 150);
|
||||
Add(4, 6U, "501", "750", 8 * kFileSize, 0, 101, 150);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
|
||||
ASSERT_TRUE(compaction);
|
||||
// Validate that its a delete triggered compaction
|
||||
ASSERT_EQ(CompactionReason::kFilesMarkedForCompaction,
|
||||
compaction->compaction_reason());
|
||||
ASSERT_EQ(3, compaction->output_level());
|
||||
ASSERT_EQ(0, compaction->start_level());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
|
||||
AddVersionStorage();
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(0, 2U, "201", "250", 2 * kFileSize, 0, 401, 450);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction2(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_FALSE(compaction2);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalMarkedCompactionStartOutputOverlap) {
|
||||
// The case where universal periodic compaction can be picked
|
||||
// with some newer files being compacted.
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
ioptions_.compaction_style = kCompactionStyleUniversal;
|
||||
|
||||
bool input_level_overlap = false;
|
||||
bool output_level_overlap = false;
|
||||
// Let's mark 2 files in 2 different levels for compaction. The
|
||||
// compaction picker will randomly pick one, so use the sync point to
|
||||
// ensure a deterministic order. Loop until both cases are covered
|
||||
size_t random_index = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionPicker::PickFilesMarkedForCompaction", [&](void* arg) {
|
||||
size_t* index = static_cast<size_t*>(arg);
|
||||
*index = random_index;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
while (!input_level_overlap || !output_level_overlap) {
|
||||
// Ensure that the L0 file gets picked first
|
||||
random_index = !input_level_overlap ? 0 : 1;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "260", "300", 4 * kFileSize, 0, 260, 300, 0, true);
|
||||
Add(3, 2U, "010", "020", 2 * kFileSize, 0, 201, 248);
|
||||
Add(3, 3U, "250", "270", 2 * kFileSize, 0, 202, 249);
|
||||
Add(3, 4U, "290", "310", 2 * kFileSize, 0, 203, 250);
|
||||
Add(3, 5U, "310", "320", 2 * kFileSize, 0, 204, 251, 0, true);
|
||||
Add(4, 6U, "301", "350", 8 * kFileSize, 0, 101, 150);
|
||||
Add(4, 7U, "501", "750", 8 * kFileSize, 0, 101, 150);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
|
||||
ASSERT_TRUE(compaction);
|
||||
// Validate that its a delete triggered compaction
|
||||
ASSERT_EQ(CompactionReason::kFilesMarkedForCompaction,
|
||||
compaction->compaction_reason());
|
||||
ASSERT_TRUE(compaction->start_level() == 0 ||
|
||||
compaction->start_level() == 3);
|
||||
if (compaction->start_level() == 0) {
|
||||
// The L0 file was picked. The next compaction will detect an
|
||||
// overlap on its input level
|
||||
input_level_overlap = true;
|
||||
ASSERT_EQ(3, compaction->output_level());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(3U, compaction->num_input_files(1));
|
||||
} else {
|
||||
// The level 3 file was picked. The next compaction will pick
|
||||
// the L0 file and will detect overlap when adding output
|
||||
// level inputs
|
||||
output_level_overlap = true;
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
}
|
||||
|
||||
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
|
||||
// After recomputing the compaction score, only one marked file will remain
|
||||
random_index = 0;
|
||||
std::unique_ptr<Compaction> compaction2(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_FALSE(compaction2);
|
||||
DeleteVersionStorage();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -120,8 +120,7 @@ class UniversalCompactionBuilder {
|
||||
LogBuffer* log_buffer_;
|
||||
|
||||
static std::vector<SortedRun> CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
const VersionStorageInfo& vstorage);
|
||||
|
||||
// Pick a path ID to place a newly generated file, with its estimated file
|
||||
// size.
|
||||
@@ -325,8 +324,7 @@ void UniversalCompactionBuilder::SortedRun::DumpSizeInfo(
|
||||
|
||||
std::vector<UniversalCompactionBuilder::SortedRun>
|
||||
UniversalCompactionBuilder::CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, const ImmutableCFOptions& /*ioptions*/,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
const VersionStorageInfo& vstorage) {
|
||||
std::vector<UniversalCompactionBuilder::SortedRun> ret;
|
||||
for (FileMetaData* f : vstorage.LevelFiles(0)) {
|
||||
ret.emplace_back(0, f, f->fd.GetFileSize(), f->compensated_file_size,
|
||||
@@ -336,27 +334,16 @@ UniversalCompactionBuilder::CalculateSortedRuns(
|
||||
uint64_t total_compensated_size = 0U;
|
||||
uint64_t total_size = 0U;
|
||||
bool being_compacted = false;
|
||||
bool is_first = true;
|
||||
for (FileMetaData* f : vstorage.LevelFiles(level)) {
|
||||
total_compensated_size += f->compensated_file_size;
|
||||
total_size += f->fd.GetFileSize();
|
||||
if (mutable_cf_options.compaction_options_universal.allow_trivial_move ==
|
||||
true) {
|
||||
if (f->being_compacted) {
|
||||
being_compacted = f->being_compacted;
|
||||
}
|
||||
} else {
|
||||
// Compaction always includes all files for a non-zero level, so for a
|
||||
// non-zero level, all the files should share the same being_compacted
|
||||
// value.
|
||||
// This assumption is only valid when
|
||||
// mutable_cf_options.compaction_options_universal.allow_trivial_move
|
||||
// is false
|
||||
assert(is_first || f->being_compacted == being_compacted);
|
||||
}
|
||||
if (is_first) {
|
||||
// Size amp, read amp and periodic compactions always include all files
|
||||
// for a non-zero level. However, a delete triggered compaction and
|
||||
// a trivial move might pick a subset of files in a sorted run. So
|
||||
// always check all files in a sorted run and mark the entire run as
|
||||
// being compacted if one or more files are being compacted
|
||||
if (f->being_compacted) {
|
||||
being_compacted = f->being_compacted;
|
||||
is_first = false;
|
||||
}
|
||||
}
|
||||
if (total_compensated_size > 0) {
|
||||
@@ -372,8 +359,7 @@ UniversalCompactionBuilder::CalculateSortedRuns(
|
||||
Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
const int kLevel0 = 0;
|
||||
score_ = vstorage_->CompactionScore(kLevel0);
|
||||
sorted_runs_ =
|
||||
CalculateSortedRuns(*vstorage_, ioptions_, mutable_cf_options_);
|
||||
sorted_runs_ = CalculateSortedRuns(*vstorage_);
|
||||
|
||||
if (sorted_runs_.size() == 0 ||
|
||||
(vstorage_->FilesMarkedForPeriodicCompaction().empty() &&
|
||||
@@ -751,7 +737,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_, start_level,
|
||||
1, enable_compression),
|
||||
GetCompressionOptions(ioptions_, vstorage_, start_level,
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, start_level,
|
||||
enable_compression),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */, compaction_reason);
|
||||
@@ -855,6 +841,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
std::vector<CompactionInputFiles> inputs;
|
||||
|
||||
if (vstorage_->num_levels() == 1) {
|
||||
#if defined(ENABLE_SINGLE_LEVEL_DTC)
|
||||
// This is single level universal. Since we're basically trying to reclaim
|
||||
// space by processing files marked for compaction due to high tombstone
|
||||
// density, let's do the same thing as compaction to reduce size amp which
|
||||
@@ -877,6 +864,11 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
return nullptr;
|
||||
}
|
||||
inputs.push_back(start_level_inputs);
|
||||
#else
|
||||
// Disable due to a known race condition.
|
||||
// TODO: Reenable once the race condition is fixed
|
||||
return nullptr;
|
||||
#endif // ENABLE_SINGLE_LEVEL_DTC
|
||||
} else {
|
||||
int start_level;
|
||||
|
||||
@@ -959,8 +951,8 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(ioptions_, vstorage_, output_level),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ true,
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
}
|
||||
@@ -1029,7 +1021,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToOldest(
|
||||
LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_, start_level,
|
||||
1, true /* enable_compression */),
|
||||
GetCompressionOptions(ioptions_, vstorage_, start_level,
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, start_level,
|
||||
true /* enable_compression */),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */, compaction_reason);
|
||||
|
||||
@@ -190,7 +190,6 @@ class CorruptionTest : public testing::Test {
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
Options options;
|
||||
EnvOptions env_options;
|
||||
options.file_system.reset(new LegacyFileSystemWrapper(options.env));
|
||||
ASSERT_NOK(VerifySstFileChecksum(options, env_options, fname));
|
||||
}
|
||||
|
||||
|
||||
+281
-72
@@ -6,6 +6,7 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
@@ -924,44 +925,44 @@ TEST_F(DBBasicTest, MmapAndBufferOptions) {
|
||||
#endif
|
||||
|
||||
class TestEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit TestEnv(Env* base_env) : EnvWrapper(base_env), close_count(0) {}
|
||||
public:
|
||||
explicit TestEnv(Env* base_env) : EnvWrapper(base_env), close_count(0) {}
|
||||
|
||||
class TestLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
explicit TestLogger(TestEnv* env_ptr) : Logger() { env = env_ptr; }
|
||||
~TestLogger() override {
|
||||
if (!closed_) {
|
||||
CloseHelper();
|
||||
}
|
||||
}
|
||||
void Logv(const char* /*format*/, va_list /*ap*/) override {}
|
||||
|
||||
protected:
|
||||
Status CloseImpl() override { return CloseHelper(); }
|
||||
|
||||
private:
|
||||
Status CloseHelper() {
|
||||
env->CloseCountInc();
|
||||
;
|
||||
return Status::IOError();
|
||||
}
|
||||
TestEnv* env;
|
||||
};
|
||||
|
||||
void CloseCountInc() { close_count++; }
|
||||
|
||||
int GetCloseCount() { return close_count; }
|
||||
|
||||
Status NewLogger(const std::string& /*fname*/,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
result->reset(new TestLogger(this));
|
||||
return Status::OK();
|
||||
class TestLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
explicit TestLogger(TestEnv* env_ptr) : Logger() { env = env_ptr; }
|
||||
~TestLogger() override {
|
||||
if (!closed_) {
|
||||
CloseHelper();
|
||||
}
|
||||
}
|
||||
void Logv(const char* /*format*/, va_list /*ap*/) override {}
|
||||
|
||||
protected:
|
||||
Status CloseImpl() override { return CloseHelper(); }
|
||||
|
||||
private:
|
||||
int close_count;
|
||||
Status CloseHelper() {
|
||||
env->CloseCountInc();
|
||||
;
|
||||
return Status::IOError();
|
||||
}
|
||||
TestEnv* env;
|
||||
};
|
||||
|
||||
void CloseCountInc() { close_count++; }
|
||||
|
||||
int GetCloseCount() { return close_count; }
|
||||
|
||||
Status NewLogger(const std::string& /*fname*/,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
result->reset(new TestLogger(this));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
int close_count;
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTest, DBClose) {
|
||||
@@ -1013,7 +1014,7 @@ TEST_F(DBBasicTest, DBCloseFlushError) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.manual_wal_flush = true;
|
||||
options.write_buffer_size=100;
|
||||
options.write_buffer_size = 100;
|
||||
options.env = fault_injection_env.get();
|
||||
|
||||
Reopen(options);
|
||||
@@ -1294,14 +1295,18 @@ TEST_F(DBBasicTest, MultiGetBatchedSimpleUnsorted) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedSimpleSorted) {
|
||||
TEST_F(DBBasicTest, MultiGetBatchedSortedMultiFile) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
SetPerfLevel(kEnableCount);
|
||||
// To expand the power of this test, generate > 1 table file and
|
||||
// mix with memtable
|
||||
ASSERT_OK(Put(1, "k1", "v1"));
|
||||
ASSERT_OK(Put(1, "k2", "v2"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Put(1, "k3", "v3"));
|
||||
ASSERT_OK(Put(1, "k4", "v4"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Delete(1, "k4"));
|
||||
ASSERT_OK(Put(1, "k5", "v5"));
|
||||
ASSERT_OK(Delete(1, "no_key"));
|
||||
@@ -1332,7 +1337,7 @@ TEST_F(DBBasicTest, MultiGetBatchedSimpleSorted) {
|
||||
ASSERT_TRUE(s[5].IsNotFound());
|
||||
|
||||
SetPerfLevel(kDisable);
|
||||
} while (ChangeCompactOptions());
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
|
||||
@@ -1463,7 +1468,8 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevelMerge) {
|
||||
ASSERT_EQ(0, num_keys);
|
||||
|
||||
for (int i = 0; i < 128; i += 9) {
|
||||
ASSERT_OK(Merge("key_" + std::to_string(i), "val_mem_" + std::to_string(i)));
|
||||
ASSERT_OK(
|
||||
Merge("key_" + std::to_string(i), "val_mem_" + std::to_string(i)));
|
||||
}
|
||||
|
||||
std::vector<std::string> keys;
|
||||
@@ -1701,8 +1707,8 @@ TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.pin_l0_filter_and_index_blocks_in_cache = true;
|
||||
table_options.block_size = 16 * 1024;
|
||||
assert(table_options.block_size >
|
||||
BlockBasedTable::kMultiGetReadStackBufSize);
|
||||
ASSERT_TRUE(table_options.block_size >
|
||||
BlockBasedTable::kMultiGetReadStackBufSize);
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
@@ -1734,6 +1740,205 @@ TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, IncrementalRecoveryNoCorrupt) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(3, num_cfs);
|
||||
WriteOptions write_opts;
|
||||
write_opts.disableWAL = true;
|
||||
for (size_t cf = 0; cf != num_cfs; ++cf) {
|
||||
for (size_t i = 0; i != 10000; ++i) {
|
||||
std::string key_str = Key(static_cast<int>(i));
|
||||
std::string value_str = std::to_string(cf) + "_" + std::to_string(i);
|
||||
|
||||
ASSERT_OK(Put(static_cast<int>(cf), key_str, value_str));
|
||||
if (0 == (i % 1000)) {
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t cf = 0; cf != num_cfs; ++cf) {
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
}
|
||||
Close();
|
||||
options.best_efforts_recovery = true;
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
num_cfs = handles_.size();
|
||||
ASSERT_EQ(3, num_cfs);
|
||||
for (size_t cf = 0; cf != num_cfs; ++cf) {
|
||||
for (int i = 0; i != 10000; ++i) {
|
||||
std::string key_str = Key(static_cast<int>(i));
|
||||
std::string expected_value_str =
|
||||
std::to_string(cf) + "_" + std::to_string(i);
|
||||
ASSERT_EQ(expected_value_str, Get(static_cast<int>(cf), key_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, BestEffortsRecoveryWithVersionBuildingFailure) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionBuilder::CheckConsistencyBeforeReturn", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
*(reinterpret_cast<Status*>(arg)) =
|
||||
Status::Corruption("Inject corruption");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
options.best_efforts_recovery = true;
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace {
|
||||
class TableFileListener : public EventListener {
|
||||
public:
|
||||
void OnTableFileCreated(const TableFileCreationInfo& info) override {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
cf_to_paths_[info.cf_name].push_back(info.file_path);
|
||||
}
|
||||
std::vector<std::string>& GetFiles(const std::string& cf_name) {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
return cf_to_paths_[cf_name];
|
||||
}
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::unordered_map<std::string, std::vector<std::string>> cf_to_paths_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBBasicTest, RecoverWithMissingFiles) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
TableFileListener* listener = new TableFileListener();
|
||||
// Disable auto compaction to simplify SST file name tracking.
|
||||
options.disable_auto_compactions = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
std::vector<std::string> all_cf_names = {kDefaultColumnFamilyName, "pikachu",
|
||||
"eevee"};
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(3, num_cfs);
|
||||
for (size_t cf = 0; cf != num_cfs; ++cf) {
|
||||
ASSERT_OK(Put(static_cast<int>(cf), "a", "0_value"));
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
ASSERT_OK(Put(static_cast<int>(cf), "b", "0_value"));
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
ASSERT_OK(Put(static_cast<int>(cf), "c", "0_value"));
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
}
|
||||
|
||||
// Delete files
|
||||
for (size_t i = 0; i < all_cf_names.size(); ++i) {
|
||||
std::vector<std::string>& files = listener->GetFiles(all_cf_names[i]);
|
||||
ASSERT_EQ(3, files.size());
|
||||
for (int j = static_cast<int>(files.size() - 1); j >= static_cast<int>(i);
|
||||
--j) {
|
||||
ASSERT_OK(env_->DeleteFile(files[j]));
|
||||
}
|
||||
}
|
||||
options.best_efforts_recovery = true;
|
||||
ReopenWithColumnFamilies(all_cf_names, options);
|
||||
// Verify data
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts, handles_[0]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
iter.reset(db_->NewIterator(read_opts, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("a", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
iter.reset(db_->NewIterator(read_opts, handles_[2]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("a", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("b", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, RecoverWithNoCurrentFile) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
options.best_efforts_recovery = true;
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(Put(1, "bar", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Flush(1));
|
||||
Close();
|
||||
ASSERT_OK(env_->DeleteFile(CurrentFileName(dbname_)));
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
std::vector<std::string> cf_names;
|
||||
ASSERT_OK(DB::ListColumnFamilies(DBOptions(options), dbname_, &cf_names));
|
||||
ASSERT_EQ(2, cf_names.size());
|
||||
for (const auto& name : cf_names) {
|
||||
ASSERT_TRUE(name == kDefaultColumnFamilyName || name == "pikachu");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, SkipWALIfMissingTableFiles) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
TableFileListener* listener = new TableFileListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<std::string> kAllCfNames = {kDefaultColumnFamilyName, "pikachu"};
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
for (int cf = 0; cf < static_cast<int>(kAllCfNames.size()); ++cf) {
|
||||
ASSERT_OK(Put(cf, "a", "0_value"));
|
||||
ASSERT_OK(Flush(cf));
|
||||
ASSERT_OK(Put(cf, "b", "0_value"));
|
||||
}
|
||||
// Delete files
|
||||
for (size_t i = 0; i < kAllCfNames.size(); ++i) {
|
||||
std::vector<std::string>& files = listener->GetFiles(kAllCfNames[i]);
|
||||
ASSERT_EQ(1, files.size());
|
||||
for (int j = static_cast<int>(files.size() - 1); j >= static_cast<int>(i);
|
||||
--j) {
|
||||
ASSERT_OK(env_->DeleteFile(files[j]));
|
||||
}
|
||||
}
|
||||
options.best_efforts_recovery = true;
|
||||
ReopenWithColumnFamilies(kAllCfNames, options);
|
||||
// Verify WAL is not applied
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts, handles_[0]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
iter.reset(db_->NewIterator(read_opts, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("a", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class DBBasicTestWithParallelIO
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool, bool, bool>> {
|
||||
@@ -1765,10 +1970,17 @@ class DBBasicTestWithParallelIO
|
||||
compression_types = GetSupportedCompressions();
|
||||
// Not every platform may have compression libraries available, so
|
||||
// dynamically pick based on what's available
|
||||
if (compression_types.size() == 0) {
|
||||
compression_enabled_ = false;
|
||||
CompressionType tmp_type = kNoCompression;
|
||||
for (auto c_type : compression_types) {
|
||||
if (c_type != kNoCompression) {
|
||||
tmp_type = c_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tmp_type != kNoCompression) {
|
||||
options.compression = tmp_type;
|
||||
} else {
|
||||
options.compression = compression_types[0];
|
||||
compression_enabled_ = false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -1776,7 +1988,7 @@ class DBBasicTestWithParallelIO
|
||||
if (!Snappy_Supported()) {
|
||||
compression_enabled_ = false;
|
||||
}
|
||||
#endif //ROCKSDB_LITE
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
table_options.block_cache = uncompressed_cache_;
|
||||
if (table_options.block_cache == nullptr) {
|
||||
@@ -1978,8 +2190,6 @@ class DBBasicTestWithParallelIO
|
||||
bool fill_cache_;
|
||||
};
|
||||
|
||||
// TODO: fails on CircleCI's Windows env
|
||||
#ifndef OS_WIN
|
||||
TEST_P(DBBasicTestWithParallelIO, MultiGet) {
|
||||
std::vector<std::string> key_data(10);
|
||||
std::vector<Slice> keys;
|
||||
@@ -2102,7 +2312,6 @@ TEST_P(DBBasicTestWithParallelIO, MultiGet) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // OS_WIN
|
||||
|
||||
TEST_P(DBBasicTestWithParallelIO, MultiGetWithChecksumMismatch) {
|
||||
std::vector<std::string> key_data(10);
|
||||
@@ -2116,13 +2325,13 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithChecksumMismatch) {
|
||||
ro.fill_cache = fill_cache();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"RetrieveMultipleBlocks:VerifyChecksum", [&](void *status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
read_count++;
|
||||
if (read_count == 2) {
|
||||
*s = Status::Corruption();
|
||||
}
|
||||
});
|
||||
"RetrieveMultipleBlocks:VerifyChecksum", [&](void* status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
read_count++;
|
||||
if (read_count == 2) {
|
||||
*s = Status::Corruption();
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Warm up the cache first
|
||||
@@ -2135,7 +2344,7 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithChecksumMismatch) {
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
ASSERT_TRUE(CheckValue(0, values[0].ToString()));
|
||||
//ASSERT_TRUE(CheckValue(50, values[1].ToString()));
|
||||
// ASSERT_TRUE(CheckValue(50, values[1].ToString()));
|
||||
ASSERT_EQ(statuses[0], Status::OK());
|
||||
ASSERT_EQ(statuses[1], Status::Corruption());
|
||||
|
||||
@@ -2153,10 +2362,10 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithMissingFile) {
|
||||
ro.fill_cache = fill_cache();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"TableCache::MultiGet:FindTable", [&](void *status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
*s = Status::IOError();
|
||||
});
|
||||
"TableCache::MultiGet:FindTable", [&](void* status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
*s = Status::IOError();
|
||||
});
|
||||
// DB open will create table readers unless we reduce the table cache
|
||||
// capacity.
|
||||
// SanitizeOptions will set max_open_files to minimum of 20. Table cache
|
||||
@@ -2165,10 +2374,10 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithMissingFile) {
|
||||
// prevent file open during DB open and force the file to be opened
|
||||
// during MultiGet
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void *arg) {
|
||||
int* max_open_files = (int*)arg;
|
||||
*max_open_files = 11;
|
||||
});
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) {
|
||||
int* max_open_files = (int*)arg;
|
||||
*max_open_files = 11;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Reopen(CurrentOptions());
|
||||
@@ -2188,15 +2397,15 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithMissingFile) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ParallelIO, DBBasicTestWithParallelIO,
|
||||
// Params are as follows -
|
||||
// Param 0 - Compressed cache enabled
|
||||
// Param 1 - Uncompressed cache enabled
|
||||
// Param 2 - Data compression enabled
|
||||
// Param 3 - ReadOptions::fill_cache
|
||||
::testing::Combine(::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool(), ::testing::Bool()));
|
||||
INSTANTIATE_TEST_CASE_P(ParallelIO, DBBasicTestWithParallelIO,
|
||||
// Params are as follows -
|
||||
// Param 0 - Compressed cache enabled
|
||||
// Param 1 - Uncompressed cache enabled
|
||||
// Param 2 - Data compression enabled
|
||||
// Param 3 - ReadOptions::fill_cache
|
||||
::testing::Combine(::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool(),
|
||||
::testing::Bool()));
|
||||
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+66
-25
@@ -593,6 +593,72 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTriggerReopen) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeBottomPri) {
|
||||
ASSERT_OK(Put(Key(50), ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Key(100), ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Key(200), ""));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
{
|
||||
CompactRangeOptions cro;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 2;
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
}
|
||||
ASSERT_EQ("0,0,3", FilesPerLevel(0));
|
||||
|
||||
ASSERT_OK(Put(Key(1), ""));
|
||||
ASSERT_OK(Put(Key(199), ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Key(2), ""));
|
||||
ASSERT_OK(Put(Key(199), ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("2,0,3", FilesPerLevel(0));
|
||||
|
||||
// Now we have 2 L0 files, and 3 L2 files, and a manual compaction will
|
||||
// be triggered.
|
||||
// Two compaction jobs will run. One compacts 2 L0 files in Low Pri Pool
|
||||
// and one compact to L2 in bottom pri pool.
|
||||
int low_pri_count = 0;
|
||||
int bottom_pri_count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ThreadPoolImpl::Impl::BGThread:BeforeRun", [&](void* arg) {
|
||||
Env::Priority* pri = reinterpret_cast<Env::Priority*>(arg);
|
||||
// First time is low pri pool in the test case.
|
||||
if (low_pri_count == 0 && bottom_pri_count == 0) {
|
||||
ASSERT_EQ(Env::Priority::LOW, *pri);
|
||||
}
|
||||
if (*pri == Env::Priority::LOW) {
|
||||
low_pri_count++;
|
||||
} else {
|
||||
bottom_pri_count++;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
env_->SetBackgroundThreads(1, Env::Priority::BOTTOM);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(1, low_pri_count);
|
||||
ASSERT_EQ(1, bottom_pri_count);
|
||||
ASSERT_EQ("0,0,2", FilesPerLevel(0));
|
||||
|
||||
// Recompact bottom most level uses bottom pool
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
ASSERT_EQ(1, low_pri_count);
|
||||
ASSERT_EQ(2, bottom_pri_count);
|
||||
|
||||
env_->SetBackgroundThreads(0, Env::Priority::BOTTOM);
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
// Low pri pool is used if bottom pool has size 0.
|
||||
ASSERT_EQ(2, low_pri_count);
|
||||
ASSERT_EQ(2, bottom_pri_count);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DisableStatsUpdateReopen) {
|
||||
uint64_t db_size[3];
|
||||
for (int test = 0; test < 2; ++test) {
|
||||
@@ -5151,31 +5217,6 @@ TEST_P(DBCompactionTestWithParam,
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, FifoCompactionGetFileCreationTime) {
|
||||
MockEnv mock_env(env_);
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.table_factory.reset(new BlockBasedTableFactory());
|
||||
options.env = &mock_env;
|
||||
options.ttl = static_cast<uint64_t>(24) * 3600;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
constexpr size_t kNumFiles = 24;
|
||||
options.max_open_files = 20;
|
||||
constexpr size_t kNumKeysPerFile = 10;
|
||||
DestroyAndReopen(options);
|
||||
for (size_t i = 0; i < kNumFiles; ++i) {
|
||||
for (size_t j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(Put(std::to_string(j), "value_" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
mock_env.FakeSleepForMicroseconds(
|
||||
static_cast<uint64_t>(1000 * 1000 * (1 + options.ttl)));
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
#endif // !defined(ROCKSDB_LITE)
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
+115
-29
@@ -150,7 +150,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
own_info_log_(options.info_log == nullptr),
|
||||
initial_db_options_(SanitizeOptions(dbname, options)),
|
||||
env_(initial_db_options_.env),
|
||||
fs_(initial_db_options_.file_system),
|
||||
fs_(initial_db_options_.env->GetFileSystem()),
|
||||
immutable_db_options_(initial_db_options_),
|
||||
mutable_db_options_(initial_db_options_),
|
||||
stats_(immutable_db_options_.statistics.get()),
|
||||
@@ -311,6 +311,11 @@ Status DBImpl::ResumeImpl() {
|
||||
s = bg_error;
|
||||
}
|
||||
|
||||
// Make sure the IO Status stored in version set is set to OK.
|
||||
if (s.ok()) {
|
||||
versions_->SetIOStatusOK();
|
||||
}
|
||||
|
||||
// We cannot guarantee consistency of the WAL. So force flush Memtables of
|
||||
// all the column families
|
||||
if (s.ok()) {
|
||||
@@ -1146,25 +1151,25 @@ int DBImpl::FindMinimumEmptyLevelFitting(
|
||||
|
||||
Status DBImpl::FlushWAL(bool sync) {
|
||||
if (manual_wal_flush_) {
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
{
|
||||
// We need to lock log_write_mutex_ since logs_ might change concurrently
|
||||
InstrumentedMutexLock wl(&log_write_mutex_);
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
s = cur_log_writer->WriteBuffer();
|
||||
io_s = cur_log_writer->WriteBuffer();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "WAL flush error %s",
|
||||
s.ToString().c_str());
|
||||
io_s.ToString().c_str());
|
||||
// In case there is a fs error we should set it globally to prevent the
|
||||
// future writes
|
||||
WriteStatusCheck(s);
|
||||
IOStatusCheck(io_s);
|
||||
// whether sync or not, we should abort the rest of function upon error
|
||||
return s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
if (!sync) {
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "FlushWAL sync=false");
|
||||
return s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
}
|
||||
if (!sync) {
|
||||
@@ -1216,12 +1221,21 @@ Status DBImpl::SyncWAL() {
|
||||
TEST_SYNC_POINT("DBWALTest::SyncWALNotWaitWrite:1");
|
||||
RecordTick(stats_, WAL_FILE_SYNCED);
|
||||
Status status;
|
||||
IOStatus io_s;
|
||||
for (log::Writer* log : logs_to_sync) {
|
||||
status = log->file()->SyncWithoutFlush(immutable_db_options_.use_fsync);
|
||||
if (!status.ok()) {
|
||||
io_s = log->file()->SyncWithoutFlush(immutable_db_options_.use_fsync);
|
||||
if (!io_s.ok()) {
|
||||
status = io_s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "WAL Sync error %s",
|
||||
io_s.ToString().c_str());
|
||||
// In case there is a fs error we should set it globally to prevent the
|
||||
// future writes
|
||||
IOStatusCheck(io_s);
|
||||
}
|
||||
if (status.ok() && need_log_dir_sync) {
|
||||
status = directories_.GetWalDir()->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
@@ -1248,7 +1262,7 @@ Status DBImpl::LockWAL() {
|
||||
// future writes
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
return status;
|
||||
return std::move(status);
|
||||
}
|
||||
|
||||
Status DBImpl::UnlockWAL() {
|
||||
@@ -1513,6 +1527,19 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
GetImplOptions& get_impl_options) {
|
||||
assert(get_impl_options.value != nullptr ||
|
||||
get_impl_options.merge_operands != nullptr);
|
||||
#ifndef NDEBUG
|
||||
assert(get_impl_options.column_family);
|
||||
ColumnFamilyHandle* cf = get_impl_options.column_family;
|
||||
const Comparator* const ucmp = cf->GetComparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0) {
|
||||
assert(read_options.timestamp);
|
||||
assert(read_options.timestamp->size() == ucmp->timestamp_size());
|
||||
} else {
|
||||
assert(!read_options.timestamp);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
|
||||
StopWatch sw(env_, stats_, DB_GET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
@@ -1687,12 +1714,34 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values) {
|
||||
return MultiGet(read_options, column_family, keys, values,
|
||||
/*timestamps=*/nullptr);
|
||||
}
|
||||
|
||||
std::vector<Status> DBImpl::MultiGet(
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) {
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
|
||||
StopWatch sw(env_, stats_, DB_MULTIGET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (const auto* cfh : column_family) {
|
||||
assert(cfh);
|
||||
const Comparator* const ucmp = cfh->GetComparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0) {
|
||||
assert(read_options.timestamp);
|
||||
assert(ucmp->timestamp_size() == read_options.timestamp->size());
|
||||
} else {
|
||||
assert(!read_options.timestamp);
|
||||
}
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
SequenceNumber consistent_seqnum;
|
||||
;
|
||||
|
||||
std::unordered_map<uint32_t, MultiGetColumnFamilyData> multiget_cf_data(
|
||||
column_family.size());
|
||||
@@ -1723,6 +1772,9 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
size_t num_keys = keys.size();
|
||||
std::vector<Status> stat_list(num_keys);
|
||||
values->resize(num_keys);
|
||||
if (timestamps) {
|
||||
timestamps->resize(num_keys);
|
||||
}
|
||||
|
||||
// Keep track of bytes that we read for statistics-recording later
|
||||
uint64_t bytes_read = 0;
|
||||
@@ -1737,8 +1789,9 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
merge_context.Clear();
|
||||
Status& s = stat_list[i];
|
||||
std::string* value = &(*values)[i];
|
||||
std::string* timestamp = timestamps ? &(*timestamps)[i] : nullptr;
|
||||
|
||||
LookupKey lkey(keys[i], consistent_seqnum);
|
||||
LookupKey lkey(keys[i], consistent_seqnum, read_options.timestamp);
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family[i]);
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
auto mgd_iter = multiget_cf_data.find(cfh->cfd()->GetID());
|
||||
@@ -1750,13 +1803,12 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
has_unpersisted_data_.load(std::memory_order_relaxed));
|
||||
bool done = false;
|
||||
if (!skip_memtable) {
|
||||
if (super_version->mem->Get(lkey, value, /*timestamp=*/nullptr, &s,
|
||||
&merge_context, &max_covering_tombstone_seq,
|
||||
read_options)) {
|
||||
if (super_version->mem->Get(lkey, value, timestamp, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if (super_version->imm->Get(
|
||||
lkey, value, nullptr, &s, &merge_context,
|
||||
lkey, value, timestamp, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
@@ -1765,8 +1817,8 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
if (!done) {
|
||||
PinnableSlice pinnable_val;
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->Get(read_options, lkey, &pinnable_val,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
super_version->current->Get(read_options, lkey, &pinnable_val, timestamp,
|
||||
&s, &merge_context,
|
||||
&max_covering_tombstone_seq);
|
||||
value->assign(pinnable_val.data(), pinnable_val.size());
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
@@ -1929,14 +1981,39 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
return MultiGet(read_options, num_keys, column_families, keys, values,
|
||||
/*timestamps=*/nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses, const bool sorted_input) {
|
||||
if (num_keys == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
ColumnFamilyHandle* cfh = column_families[i];
|
||||
assert(cfh);
|
||||
const Comparator* const ucmp = cfh->GetComparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0) {
|
||||
assert(read_options.timestamp);
|
||||
assert(read_options.timestamp->size() == ucmp->timestamp_size());
|
||||
} else {
|
||||
assert(!read_options.timestamp);
|
||||
}
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
key_context.emplace_back(column_families[i], keys[i], &values[i],
|
||||
timestamps ? ×tamps[i] : nullptr,
|
||||
&statuses[i]);
|
||||
}
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
@@ -2007,7 +2084,8 @@ struct CompareKeyContext {
|
||||
}
|
||||
|
||||
// Both keys are from the same column family
|
||||
int cmp = comparator->Compare(*(lhs->key), *(rhs->key));
|
||||
int cmp = comparator->CompareWithoutTimestamp(
|
||||
*(lhs->key), /*a_has_ts=*/false, *(rhs->key), /*b_has_ts=*/false);
|
||||
if (cmp < 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -2039,7 +2117,8 @@ void DBImpl::PrepareMultiGetKeys(
|
||||
}
|
||||
|
||||
// Both keys are from the same column family
|
||||
int cmp = comparator->Compare(*(lhs->key), *(rhs->key));
|
||||
int cmp = comparator->CompareWithoutTimestamp(
|
||||
*(lhs->key), /*a_has_ts=*/false, *(rhs->key), /*b_has_ts=*/false);
|
||||
assert(cmp <= 0);
|
||||
}
|
||||
index++;
|
||||
@@ -2057,11 +2136,22 @@ void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
Status* statuses, const bool sorted_input) {
|
||||
return MultiGet(read_options, column_family, num_keys, keys, values,
|
||||
/*timestamp=*/nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
std::string* timestamps, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
key_context.emplace_back(column_family, keys[i], &values[i], &statuses[i]);
|
||||
key_context.emplace_back(column_family, keys[i], &values[i],
|
||||
timestamps ? ×tamps[i] : nullptr,
|
||||
&statuses[i]);
|
||||
}
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
sorted_keys[i] = &key_context[i];
|
||||
@@ -2139,7 +2229,7 @@ void DBImpl::MultiGetImpl(
|
||||
? MultiGetContext::MAX_BATCH_SIZE
|
||||
: keys_left;
|
||||
MultiGetContext ctx(sorted_keys, start_key + num_keys - keys_left,
|
||||
batch_size, snapshot);
|
||||
batch_size, snapshot, read_options);
|
||||
MultiGetRange range = ctx.GetMultiGetRange();
|
||||
bool lookup_current = false;
|
||||
|
||||
@@ -3478,12 +3568,8 @@ Status DBImpl::Close() {
|
||||
Status DB::ListColumnFamilies(const DBOptions& db_options,
|
||||
const std::string& name,
|
||||
std::vector<std::string>* column_families) {
|
||||
FileSystem* fs = db_options.file_system.get();
|
||||
LegacyFileSystemWrapper legacy_fs(db_options.env);
|
||||
if (!fs) {
|
||||
fs = &legacy_fs;
|
||||
}
|
||||
return VersionSet::ListColumnFamilies(column_families, name, fs);
|
||||
const std::shared_ptr<FileSystem>& fs = db_options.env->GetFileSystem();
|
||||
return VersionSet::ListColumnFamilies(column_families, name, fs.get());
|
||||
}
|
||||
|
||||
Snapshot::~Snapshot() {}
|
||||
|
||||
+39
-10
@@ -188,6 +188,11 @@ class DBImpl : public DB {
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) override;
|
||||
|
||||
// This MultiGet is a batched version, which may be faster than calling Get
|
||||
// multiple times, especially if the keys have some spatial locality that
|
||||
@@ -201,11 +206,22 @@ class DBImpl : public DB {
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
virtual void MultiGet(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
|
||||
virtual void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
virtual void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
|
||||
virtual void MultiGetWithCallback(
|
||||
const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
@@ -1124,6 +1140,15 @@ class DBImpl : public DB {
|
||||
|
||||
virtual bool OwnTablesAndLogs() const { return true; }
|
||||
|
||||
// REQUIRES: db mutex held when calling this function, but the db mutex can
|
||||
// be released and re-acquired. Db mutex will be held when the function
|
||||
// returns.
|
||||
// After best-efforts recovery, there may be SST files in db/cf paths that are
|
||||
// not referenced in the MANIFEST. We delete these SST files. In the
|
||||
// meantime, we find out the largest file number present in the paths, and
|
||||
// bump up the version set's next_file_number_ to be 1 + largest_file_number.
|
||||
Status FinishBestEffortsRecovery();
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class ErrorHandler;
|
||||
@@ -1337,7 +1362,7 @@ class DBImpl : public DB {
|
||||
void ReleaseFileNumberFromPendingOutputs(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v);
|
||||
|
||||
Status SyncClosedLogs(JobContext* job_context);
|
||||
IOStatus SyncClosedLogs(JobContext* job_context);
|
||||
|
||||
// Flush the in-memory write buffer to storage. Switches to a new
|
||||
// log-file/memtable and writes a new descriptor iff successful. Then
|
||||
@@ -1474,21 +1499,25 @@ class DBImpl : public DB {
|
||||
WriteBatch* tmp_batch, size_t* write_with_wal,
|
||||
WriteBatch** to_be_cached_state);
|
||||
|
||||
Status WriteToWAL(const WriteBatch& merged_batch, log::Writer* log_writer,
|
||||
uint64_t* log_used, uint64_t* log_size);
|
||||
IOStatus WriteToWAL(const WriteBatch& merged_batch, log::Writer* log_writer,
|
||||
uint64_t* log_used, uint64_t* log_size);
|
||||
|
||||
Status WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence);
|
||||
IOStatus WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence);
|
||||
|
||||
Status ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used, SequenceNumber* last_sequence,
|
||||
size_t seq_inc);
|
||||
IOStatus ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used,
|
||||
SequenceNumber* last_sequence, size_t seq_inc);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ if paranoid check is enabled.
|
||||
void WriteStatusCheck(const Status& status);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ when IO error happens, e.g., write
|
||||
// WAL, sync WAL fails, if paranoid check is enabled.
|
||||
void IOStatusCheck(const IOStatus& status);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ in case of memtable insert error.
|
||||
void MemTableInsertStatusCheck(const Status& memtable_insert_status);
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ bool DBImpl::RequestCompactionToken(ColumnFamilyData* cfd, bool force,
|
||||
return false;
|
||||
}
|
||||
|
||||
Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
IOStatus DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start");
|
||||
mutex_.AssertHeld();
|
||||
autovector<log::Writer*, 1> logs_to_sync;
|
||||
@@ -96,7 +96,7 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
logs_to_sync.push_back(log.writer);
|
||||
}
|
||||
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
if (!logs_to_sync.empty()) {
|
||||
mutex_.Unlock();
|
||||
|
||||
@@ -104,34 +104,34 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[JOB %d] Syncing log #%" PRIu64, job_context->job_id,
|
||||
log->get_log_number());
|
||||
s = log->file()->Sync(immutable_db_options_.use_fsync);
|
||||
if (!s.ok()) {
|
||||
io_s = log->file()->Sync(immutable_db_options_.use_fsync);
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (immutable_db_options_.recycle_log_file_num > 0) {
|
||||
s = log->Close();
|
||||
if (!s.ok()) {
|
||||
io_s = log->Close();
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = directories_.GetWalDir()->Fsync(IOOptions(), nullptr);
|
||||
if (io_s.ok()) {
|
||||
io_s = directories_.GetWalDir()->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
|
||||
mutex_.Lock();
|
||||
|
||||
// "number <= current_log_number - 1" is equivalent to
|
||||
// "number < current_log_number".
|
||||
MarkLogsSynced(current_log_number - 1, true, s);
|
||||
if (!s.ok()) {
|
||||
error_handler_.SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
MarkLogsSynced(current_log_number - 1, true, io_s);
|
||||
if (!io_s.ok()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Failed");
|
||||
return s;
|
||||
return io_s;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
return io_s;
|
||||
}
|
||||
|
||||
Status DBImpl::FlushMemTableToOutputFile(
|
||||
@@ -155,7 +155,6 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
true /* sync_output_directory */, true /* write_manifest */, thread_pri);
|
||||
|
||||
FileMetaData file_meta;
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:BeforePickMemtables");
|
||||
@@ -168,6 +167,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
if (logfile_number_ > 0 &&
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() > 1) {
|
||||
// If there are more than one column families, we need to make sure that
|
||||
@@ -176,7 +176,8 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// flushed SST may contain data from write batches whose updates to
|
||||
// other column families are missing.
|
||||
// SyncClosedLogs() may unlock and re-lock the db_mutex.
|
||||
s = SyncClosedLogs(job_context);
|
||||
io_s = SyncClosedLogs(job_context);
|
||||
s = io_s;
|
||||
} else {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Skip");
|
||||
}
|
||||
@@ -192,6 +193,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
} else {
|
||||
flush_job.Cancel();
|
||||
}
|
||||
io_s = flush_job.io_status();
|
||||
|
||||
if (s.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, superversion_context,
|
||||
@@ -206,8 +208,13 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
}
|
||||
|
||||
if (!s.ok() && !s.IsShutdownInProgress() && !s.IsColumnFamilyDropped()) {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
if (!io_s.ok() && !io_s.IsShutdownInProgress() &&
|
||||
!io_s.IsColumnFamilyDropped()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
} else {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -344,6 +351,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
|
||||
std::vector<FileMetaData> file_meta(num_cfs);
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
assert(num_cfs == static_cast<int>(jobs.size()));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -358,15 +366,18 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
if (logfile_number_ > 0) {
|
||||
// TODO (yanqin) investigate whether we should sync the closed logs for
|
||||
// single column family case.
|
||||
s = SyncClosedLogs(job_context);
|
||||
io_s = SyncClosedLogs(job_context);
|
||||
s = io_s;
|
||||
}
|
||||
|
||||
// exec_status stores the execution status of flush_jobs as
|
||||
// <bool /* executed */, Status /* status code */>
|
||||
autovector<std::pair<bool, Status>> exec_status;
|
||||
autovector<IOStatus> io_status;
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
// Initially all jobs are not executed, with status OK.
|
||||
exec_status.emplace_back(false, Status::OK());
|
||||
io_status.emplace_back(IOStatus::OK());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -375,6 +386,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
exec_status[i].second =
|
||||
jobs[i]->Run(&logs_with_prep_tracker_, &file_meta[i]);
|
||||
exec_status[i].first = true;
|
||||
io_status[i] = jobs[i]->io_status();
|
||||
}
|
||||
if (num_cfs > 1) {
|
||||
TEST_SYNC_POINT(
|
||||
@@ -387,6 +399,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
exec_status[0].second =
|
||||
jobs[0]->Run(&logs_with_prep_tracker_, &file_meta[0]);
|
||||
exec_status[0].first = true;
|
||||
io_status[0] = jobs[0]->io_status();
|
||||
|
||||
Status error_status;
|
||||
for (const auto& e : exec_status) {
|
||||
@@ -405,6 +418,20 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
s = error_status.ok() ? s : error_status;
|
||||
}
|
||||
|
||||
if (io_s.ok()) {
|
||||
IOStatus io_error = IOStatus::OK();
|
||||
for (int i = 0; i != static_cast<int>(io_status.size()); i++) {
|
||||
if (!io_status[i].ok() && !io_status[i].IsShutdownInProgress() &&
|
||||
!io_status[i].IsColumnFamilyDropped()) {
|
||||
io_error = io_status[i];
|
||||
}
|
||||
}
|
||||
io_s = io_error;
|
||||
if (s.ok() && !io_s.ok()) {
|
||||
s = io_s;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.IsColumnFamilyDropped()) {
|
||||
s = Status::OK();
|
||||
}
|
||||
@@ -543,9 +570,15 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
if (!s.ok() && !s.IsShutdownInProgress()) {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
// Need to undo atomic flush if something went wrong, i.e. s is not OK and
|
||||
// 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);
|
||||
} else {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -1510,7 +1543,12 @@ Status DBImpl::RunManualCompaction(
|
||||
}
|
||||
manual.incomplete = false;
|
||||
bg_compaction_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
|
||||
Env::Priority thread_pool_pri = Env::Priority::LOW;
|
||||
if (compaction->bottommost_level() &&
|
||||
env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) {
|
||||
thread_pool_pri = Env::Priority::BOTTOM;
|
||||
}
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, ca, thread_pool_pri, this,
|
||||
&DBImpl::UnscheduleCompactionCallback);
|
||||
scheduled = true;
|
||||
}
|
||||
@@ -2628,6 +2666,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
}
|
||||
}
|
||||
|
||||
IOStatus io_s;
|
||||
if (!c) {
|
||||
// Nothing to do
|
||||
ROCKS_LOG_BUFFER(log_buffer, "Compaction nothing to do");
|
||||
@@ -2649,9 +2688,11 @@ 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());
|
||||
io_s = versions_->io_status();
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options());
|
||||
@@ -2705,9 +2746,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
}
|
||||
}
|
||||
|
||||
versions_->SetIOStatusOK();
|
||||
status = versions_->LogAndApply(c->column_family_data(),
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
io_s = versions_->io_status();
|
||||
// Use latest MutableCFOptions
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
@@ -2794,6 +2837,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
mutex_.Lock();
|
||||
|
||||
status = compaction_job.Install(*c->mutable_cf_options());
|
||||
io_s = compaction_job.io_status();
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
@@ -2803,6 +2847,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
|
||||
c->column_family_data());
|
||||
}
|
||||
|
||||
if (status.ok() && !io_s.ok()) {
|
||||
status = io_s;
|
||||
}
|
||||
|
||||
if (c != nullptr) {
|
||||
c->ReleaseCompactionFiles(status);
|
||||
*made_progress = true;
|
||||
@@ -2828,7 +2877,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log, "Compaction error: %s",
|
||||
status.ToString().c_str());
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
if (!io_s.ok()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kCompaction);
|
||||
} else {
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
}
|
||||
if (c != nullptr && !is_manual && !error_handler_.IsBGWorkStopped()) {
|
||||
// Put this cfd back in the compaction queue so we can retry after some
|
||||
// time
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "file/file_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
@@ -664,4 +665,69 @@ uint64_t PrecomputeMinLogNumberToKeep(
|
||||
return min_log_number_to_keep;
|
||||
}
|
||||
|
||||
Status DBImpl::FinishBestEffortsRecovery() {
|
||||
mutex_.AssertHeld();
|
||||
std::vector<std::string> paths;
|
||||
paths.push_back(dbname_);
|
||||
for (const auto& db_path : immutable_db_options_.db_paths) {
|
||||
paths.push_back(db_path.path);
|
||||
}
|
||||
for (const auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
for (const auto& cf_path : cfd->ioptions()->cf_paths) {
|
||||
paths.push_back(cf_path.path);
|
||||
}
|
||||
}
|
||||
// Dedup paths
|
||||
std::sort(paths.begin(), paths.end());
|
||||
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
|
||||
|
||||
uint64_t next_file_number = versions_->current_next_file_number();
|
||||
uint64_t largest_file_number = next_file_number;
|
||||
std::set<std::string> files_to_delete;
|
||||
for (const auto& path : paths) {
|
||||
std::vector<std::string> files;
|
||||
env_->GetChildren(path, &files);
|
||||
for (const auto& fname : files) {
|
||||
uint64_t number = 0;
|
||||
FileType type;
|
||||
if (!ParseFileName(fname, &number, &type)) {
|
||||
continue;
|
||||
}
|
||||
const std::string normalized_fpath = NormalizePath(path + fname);
|
||||
largest_file_number = std::max(largest_file_number, number);
|
||||
if (type == kTableFile && number >= next_file_number &&
|
||||
files_to_delete.find(normalized_fpath) == files_to_delete.end()) {
|
||||
files_to_delete.insert(normalized_fpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (largest_file_number > next_file_number) {
|
||||
versions_->next_file_number_.store(largest_file_number + 1);
|
||||
}
|
||||
|
||||
VersionEdit edit;
|
||||
edit.SetNextFile(versions_->next_file_number_.load());
|
||||
assert(versions_->GetColumnFamilySet());
|
||||
ColumnFamilyData* default_cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
assert(default_cfd);
|
||||
// Even if new_descriptor_log is false, we will still switch to a new
|
||||
// MANIFEST and update CURRENT file, since this is in recovery.
|
||||
Status s = versions_->LogAndApply(
|
||||
default_cfd, *default_cfd->GetLatestMutableCFOptions(), &edit, &mutex_,
|
||||
directories_.GetDbDir(), /*new_descriptor_log*/ false);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
mutex_.Unlock();
|
||||
for (const auto& fname : files_to_delete) {
|
||||
s = env_->DeleteFile(fname);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+26
-15
@@ -35,16 +35,8 @@ Options SanitizeOptions(const std::string& dbname, const Options& src) {
|
||||
DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
DBOptions result(src);
|
||||
|
||||
if (result.file_system == nullptr) {
|
||||
if (result.env == Env::Default()) {
|
||||
result.file_system = FileSystem::Default();
|
||||
} else {
|
||||
result.file_system.reset(new LegacyFileSystemWrapper(result.env));
|
||||
}
|
||||
} else {
|
||||
if (result.env == nullptr) {
|
||||
result.env = Env::Default();
|
||||
}
|
||||
if (result.env == nullptr) {
|
||||
result.env = Env::Default();
|
||||
}
|
||||
|
||||
// result.max_open_files means an "infinite" open files.
|
||||
@@ -252,6 +244,12 @@ Status DBImpl::ValidateOptions(const DBOptions& db_options) {
|
||||
"atomic_flush is incompatible with enable_pipelined_write");
|
||||
}
|
||||
|
||||
// TODO remove this restriction
|
||||
if (db_options.atomic_flush && db_options.best_efforts_recovery) {
|
||||
return Status::InvalidArgument(
|
||||
"atomic_flush is currently incompatible with best-efforts recovery");
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -294,7 +292,7 @@ Status DBImpl::NewDB() {
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(env_, dbname_, 1, directories_.GetDbDir());
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, directories_.GetDbDir());
|
||||
} else {
|
||||
fs_->DeleteFile(manifest, IOOptions(), nullptr);
|
||||
}
|
||||
@@ -419,7 +417,17 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
assert(db_id_.empty());
|
||||
Status s = versions_->Recover(column_families, read_only, &db_id_);
|
||||
Status s;
|
||||
bool missing_table_file = false;
|
||||
if (!immutable_db_options_.best_efforts_recovery) {
|
||||
s = versions_->Recover(column_families, read_only, &db_id_);
|
||||
} else {
|
||||
s = versions_->TryRecover(column_families, read_only, &db_id_,
|
||||
&missing_table_file);
|
||||
if (s.ok()) {
|
||||
s = FinishBestEffortsRecovery();
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -499,7 +507,9 @@ Status DBImpl::Recover(
|
||||
// attention to it in case we are recovering a database
|
||||
// produced by an older version of rocksdb.
|
||||
std::vector<std::string> filenames;
|
||||
s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames);
|
||||
if (!immutable_db_options_.best_efforts_recovery) {
|
||||
s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames);
|
||||
}
|
||||
if (s.IsNotFound()) {
|
||||
return Status::InvalidArgument("wal_dir not found",
|
||||
immutable_db_options_.wal_dir);
|
||||
@@ -1229,6 +1239,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
IOStatus io_s;
|
||||
s = BuildTable(
|
||||
dbname_, env_, fs_.get(), *cfd->ioptions(), mutable_cf_options,
|
||||
file_options_for_compaction_, cfd->table_cache(), iter.get(),
|
||||
@@ -1237,8 +1248,8 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
mutable_cf_options.sample_for_compression,
|
||||
cfd->ioptions()->compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery,
|
||||
mutable_cf_options.compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery, &io_s,
|
||||
&event_logger_, job_id, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
-1 /* level */, current_time, write_hint);
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
|
||||
+68
-38
@@ -101,6 +101,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
disable_memtable);
|
||||
|
||||
Status status;
|
||||
IOStatus io_s;
|
||||
if (write_options.low_pri) {
|
||||
status = ThrottleLowPriWritesIfNeeded(write_options, my_batch);
|
||||
if (!status.ok()) {
|
||||
@@ -322,21 +323,22 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (!two_write_queues_) {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
status = WriteToWAL(write_group, log_writer, log_used, need_log_sync,
|
||||
need_log_dir_sync, last_sequence + 1);
|
||||
io_s = WriteToWAL(write_group, log_writer, log_used, need_log_sync,
|
||||
need_log_dir_sync, last_sequence + 1);
|
||||
}
|
||||
} else {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
// LastAllocatedSequence is increased inside WriteToWAL under
|
||||
// wal_write_mutex_ to ensure ordered events in WAL
|
||||
status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
|
||||
seq_inc);
|
||||
io_s = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
|
||||
seq_inc);
|
||||
} else {
|
||||
// Otherwise we inc seq number for memtable writes
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
}
|
||||
}
|
||||
status = io_s;
|
||||
assert(last_sequence != kMaxSequenceNumber);
|
||||
const SequenceNumber current_sequence = last_sequence + 1;
|
||||
last_sequence += seq_inc;
|
||||
@@ -411,7 +413,11 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
WriteStatusCheck(status);
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
@@ -515,6 +521,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
IOStatus io_s;
|
||||
if (w.status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
stats->AddDBStats(InternalStats::kIntStatsWriteDoneBySelf, 1);
|
||||
@@ -524,12 +531,17 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
wal_write_group.size - 1);
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER, wal_write_group.size - 1);
|
||||
}
|
||||
w.status = WriteToWAL(wal_write_group, log_writer, log_used,
|
||||
need_log_sync, need_log_dir_sync, current_sequence);
|
||||
io_s = WriteToWAL(wal_write_group, log_writer, log_used, need_log_sync,
|
||||
need_log_dir_sync, current_sequence);
|
||||
w.status = io_s;
|
||||
}
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
WriteStatusCheck(w.status);
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(w.status);
|
||||
}
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
@@ -740,9 +752,10 @@ Status DBImpl::WriteImplWALOnly(
|
||||
}
|
||||
seq_inc = total_batch_cnt;
|
||||
}
|
||||
IOStatus io_s;
|
||||
if (!write_options.disableWAL) {
|
||||
status =
|
||||
ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
io_s = ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
status = io_s;
|
||||
} else {
|
||||
// Otherwise we inc seq number to do solely the seq allocation
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
@@ -777,7 +790,11 @@ Status DBImpl::WriteImplWALOnly(
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
WriteStatusCheck(status);
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
size_t index = 0;
|
||||
@@ -823,6 +840,17 @@ void DBImpl::WriteStatusCheck(const Status& status) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::IOStatusCheck(const IOStatus& io_status) {
|
||||
// Is setting bg_error_ enough here? This will at least stop
|
||||
// compaction and fail any further writes.
|
||||
if (immutable_db_options_.paranoid_checks && !io_status.ok() &&
|
||||
!io_status.IsBusy() && !io_status.IsIncomplete()) {
|
||||
mutex_.Lock();
|
||||
error_handler_.SetBGError(io_status, BackgroundErrorReason::kWriteCallback);
|
||||
mutex_.Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::MemTableInsertStatusCheck(const Status& status) {
|
||||
// A non-OK status here indicates that the state implied by the
|
||||
// WAL has diverged from the in-memory state. This could be
|
||||
@@ -961,9 +989,9 @@ WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
|
||||
// When two_write_queues_ is disabled, this function is called from the only
|
||||
// write thread. Otherwise this must be called holding log_write_mutex_.
|
||||
Status DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
uint64_t* log_size) {
|
||||
IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
uint64_t* log_size) {
|
||||
assert(log_size != nullptr);
|
||||
Slice log_entry = WriteBatchInternal::Contents(&merged_batch);
|
||||
*log_size = log_entry.size();
|
||||
@@ -978,7 +1006,8 @@ Status DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
Status status = log_writer->AddRecord(log_entry);
|
||||
IOStatus io_s = log_writer->AddRecord(log_entry);
|
||||
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
@@ -990,15 +1019,14 @@ Status DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
// since alive_log_files_ might be modified concurrently
|
||||
alive_log_files_.back().AddSize(log_entry.size());
|
||||
log_empty_ = false;
|
||||
return status;
|
||||
return io_s;
|
||||
}
|
||||
|
||||
Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence) {
|
||||
Status status;
|
||||
|
||||
IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence) {
|
||||
IOStatus io_s;
|
||||
assert(!write_group.leader->disable_wal);
|
||||
// Same holds for all in the batch group
|
||||
size_t write_with_wal = 0;
|
||||
@@ -1016,13 +1044,13 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
WriteBatchInternal::SetSequence(merged_batch, sequence);
|
||||
|
||||
uint64_t log_size;
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
io_s = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
|
||||
if (status.ok() && need_log_sync) {
|
||||
if (io_s.ok() && need_log_sync) {
|
||||
StopWatch sw(env_, stats_, WAL_FILE_SYNC_MICROS);
|
||||
// It's safe to access logs_ with unlocked mutex_ here because:
|
||||
// - we've set getting_synced=true for all logs,
|
||||
@@ -1032,23 +1060,24 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
// - as long as other threads don't modify it, it's safe to read
|
||||
// from std::deque from multiple threads concurrently.
|
||||
for (auto& log : logs_) {
|
||||
status = log.writer->file()->Sync(immutable_db_options_.use_fsync);
|
||||
if (!status.ok()) {
|
||||
io_s = log.writer->file()->Sync(immutable_db_options_.use_fsync);
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (status.ok() && need_log_dir_sync) {
|
||||
|
||||
if (io_s.ok() && need_log_dir_sync) {
|
||||
// We only sync WAL directory the first time WAL syncing is
|
||||
// requested, so that in case users never turn on WAL sync,
|
||||
// we can avoid the disk I/O in the write code path.
|
||||
status = directories_.GetWalDir()->Fsync(IOOptions(), nullptr);
|
||||
io_s = directories_.GetWalDir()->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (merged_batch == &tmp_batch_) {
|
||||
tmp_batch_.Clear();
|
||||
}
|
||||
if (status.ok()) {
|
||||
if (io_s.ok()) {
|
||||
auto stats = default_cf_internal_stats_;
|
||||
if (need_log_sync) {
|
||||
stats->AddDBStats(InternalStats::kIntStatsWalFileSynced, 1);
|
||||
@@ -1059,14 +1088,13 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
stats->AddDBStats(InternalStats::kIntStatsWriteWithWal, write_with_wal);
|
||||
RecordTick(stats_, WRITE_WITH_WAL, write_with_wal);
|
||||
}
|
||||
return status;
|
||||
return io_s;
|
||||
}
|
||||
|
||||
Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used,
|
||||
SequenceNumber* last_sequence,
|
||||
size_t seq_inc) {
|
||||
Status status;
|
||||
IOStatus DBImpl::ConcurrentWriteToWAL(
|
||||
const WriteThread::WriteGroup& write_group, uint64_t* log_used,
|
||||
SequenceNumber* last_sequence, size_t seq_inc) {
|
||||
IOStatus io_s;
|
||||
|
||||
assert(!write_group.leader->disable_wal);
|
||||
// Same holds for all in the batch group
|
||||
@@ -1092,14 +1120,14 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
|
||||
log::Writer* log_writer = logs_.back().writer;
|
||||
uint64_t log_size;
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
io_s = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
|
||||
if (status.ok()) {
|
||||
if (io_s.ok()) {
|
||||
const bool concurrent = true;
|
||||
auto stats = default_cf_internal_stats_;
|
||||
stats->AddDBStats(InternalStats::kIntStatsWalFileBytes, log_size,
|
||||
@@ -1109,7 +1137,7 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
concurrent);
|
||||
RecordTick(stats_, WRITE_WITH_WAL, write_with_wal);
|
||||
}
|
||||
return status;
|
||||
return io_s;
|
||||
}
|
||||
|
||||
Status DBImpl::WriteRecoverableState() {
|
||||
@@ -1792,6 +1820,8 @@ Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
const Slice* ts = opt.timestamp;
|
||||
assert(nullptr != ts);
|
||||
size_t ts_sz = ts->size();
|
||||
assert(column_family->GetComparator());
|
||||
assert(ts_sz == column_family->GetComparator()->timestamp_size());
|
||||
WriteBatch batch(key.size() + ts_sz + value.size() + 24, /*max_bytes=*/0,
|
||||
ts_sz);
|
||||
Status s = batch.Put(column_family, key, value);
|
||||
|
||||
@@ -45,6 +45,8 @@ class DBSecondaryTest : public DBTestBase {
|
||||
|
||||
void OpenSecondary(const Options& options);
|
||||
|
||||
Status TryOpenSecondary(const Options& options);
|
||||
|
||||
void OpenSecondaryWithColumnFamilies(
|
||||
const std::vector<std::string>& column_families, const Options& options);
|
||||
|
||||
@@ -70,9 +72,13 @@ class DBSecondaryTest : public DBTestBase {
|
||||
};
|
||||
|
||||
void DBSecondaryTest::OpenSecondary(const Options& options) {
|
||||
ASSERT_OK(TryOpenSecondary(options));
|
||||
}
|
||||
|
||||
Status DBSecondaryTest::TryOpenSecondary(const Options& options) {
|
||||
Status s =
|
||||
DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_secondary_);
|
||||
ASSERT_OK(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBSecondaryTest::OpenSecondaryWithColumnFamilies(
|
||||
@@ -858,6 +864,56 @@ TEST_F(DBSecondaryTest, CheckConsistencyWhenOpen) {
|
||||
thread.join();
|
||||
ASSERT_TRUE(called);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, StartFromInconsistent) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionBuilder::CheckConsistencyBeforeReturn", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
*(reinterpret_cast<Status*>(arg)) =
|
||||
Status::Corruption("Inject corruption");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Options options1;
|
||||
Status s = TryOpenSecondary(options1);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, InconsistencyDuringCatchUp) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Options options1;
|
||||
OpenSecondary(options1);
|
||||
|
||||
{
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ReadOptions(), "foo", &value));
|
||||
ASSERT_EQ("value", value);
|
||||
}
|
||||
|
||||
ASSERT_OK(Put("bar", "value1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionBuilder::CheckConsistencyBeforeReturn", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
*(reinterpret_cast<Status*>(arg)) =
|
||||
Status::Corruption("Inject corruption");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = db_secondary_->TryCatchUpWithPrimary();
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
#endif //! ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -37,17 +37,15 @@ class DBLogicalBlockSizeCacheTest : public testing::Test {
|
||||
data_path_1_(dbname_ + "/data_path_1"),
|
||||
cf_path_0_(dbname_ + "/cf_path_0"),
|
||||
cf_path_1_(dbname_ + "/cf_path_1") {
|
||||
auto get_fd_block_size = [&](int fd) {
|
||||
return fd;
|
||||
};
|
||||
auto get_fd_block_size = [&](int fd) { return fd; };
|
||||
auto get_dir_block_size = [&](const std::string& /*dir*/, size_t* size) {
|
||||
*size = 1024;
|
||||
return Status::OK();
|
||||
};
|
||||
cache_.reset(new LogicalBlockSizeCache(
|
||||
get_fd_block_size, get_dir_block_size));
|
||||
env_.reset(new EnvWithCustomLogicalBlockSizeCache(
|
||||
Env::Default(), cache_.get()));
|
||||
cache_.reset(
|
||||
new LogicalBlockSizeCache(get_fd_block_size, get_dir_block_size));
|
||||
env_.reset(
|
||||
new EnvWithCustomLogicalBlockSizeCache(Env::Default(), cache_.get()));
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -507,7 +505,7 @@ TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithSamePaths) {
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // OS_LINUX
|
||||
#endif // OS_LINUX
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
@@ -861,6 +861,62 @@ TEST_F(DBOptionsTest, FIFOTtlBackwardCompatible) {
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBOptionsTest, ChangeCompression) {
|
||||
if (!Snappy_Supported() || !LZ4_Supported()) {
|
||||
return;
|
||||
}
|
||||
Options options;
|
||||
options.write_buffer_size = 10 << 10; // 10KB
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.create_if_missing = true;
|
||||
options.compression = CompressionType::kLZ4Compression;
|
||||
options.bottommost_compression = CompressionType::kNoCompression;
|
||||
options.bottommost_compression_opts.level = 2;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
CompressionType compression_used = CompressionType::kLZ4Compression;
|
||||
CompressionOptions compression_opt_used;
|
||||
bool compacted = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* c = reinterpret_cast<Compaction*>(arg);
|
||||
compression_used = c->output_compression();
|
||||
compression_opt_used = c->output_compression_opts();
|
||||
compacted = true;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("foo", "foofoofoo"));
|
||||
ASSERT_OK(Put("bar", "foofoofoo"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "foofoofoo"));
|
||||
ASSERT_OK(Put("bar", "foofoofoo"));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_TRUE(compacted);
|
||||
ASSERT_EQ(CompressionType::kNoCompression, compression_used);
|
||||
ASSERT_EQ(options.compression_opts.level, compression_opt_used.level);
|
||||
|
||||
compression_used = CompressionType::kLZ4Compression;
|
||||
compacted = false;
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"bottommost_compression", "kSnappyCompression"},
|
||||
{"bottommost_compression_opts", "0:6:0:0:0:true"}}));
|
||||
ASSERT_OK(Put("foo", "foofoofoo"));
|
||||
ASSERT_OK(Put("bar", "foofoofoo"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "foofoofoo"));
|
||||
ASSERT_OK(Put("bar", "foofoofoo"));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_TRUE(compacted);
|
||||
ASSERT_EQ(CompressionType::kSnappyCompression, compression_used);
|
||||
ASSERT_EQ(6, compression_opt_used.level);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -45,7 +45,8 @@ void VerifyTableProperties(DB* db, uint64_t expected_entries_size) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class DBTablePropertiesTest : public DBTestBase {
|
||||
class DBTablePropertiesTest : public DBTestBase,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
public:
|
||||
DBTablePropertiesTest() : DBTestBase("/db_table_properties_test") {}
|
||||
TablePropertiesCollection TestGetPropertiesOfTablesInRange(
|
||||
@@ -251,7 +252,20 @@ TEST_F(DBTablePropertiesTest, GetColumnFamilyNameProperty) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
class DeletionTriggeredCompactionTestListener : public EventListener {
|
||||
public:
|
||||
void OnCompactionBegin(DB* , const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.compaction_reason,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
}
|
||||
|
||||
void OnCompactionCompleted(DB* , const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.compaction_reason,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
int kNumKeys = 1000;
|
||||
int kWindowSize = 100;
|
||||
int kNumDelsTrigger = 90;
|
||||
@@ -260,6 +274,10 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
|
||||
Options opts = CurrentOptions();
|
||||
opts.table_properties_collector_factories.emplace_back(compact_on_del);
|
||||
|
||||
if(GetParam() == "kCompactionStyleUniversal") {
|
||||
opts.compaction_style = kCompactionStyleUniversal;
|
||||
}
|
||||
Reopen(opts);
|
||||
|
||||
// add an L1 file to prevent tombstones from dropping due to obsolescence
|
||||
@@ -268,6 +286,11 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
Flush();
|
||||
MoveFilesToLevel(1);
|
||||
|
||||
DeletionTriggeredCompactionTestListener *listener =
|
||||
new DeletionTriggeredCompactionTestListener();
|
||||
opts.listeners.emplace_back(listener);
|
||||
Reopen(opts);
|
||||
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
if (i >= kNumKeys - kWindowSize &&
|
||||
i < kNumKeys - kWindowSize + kNumDelsTrigger) {
|
||||
@@ -280,7 +303,6 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
// Change the window size and deletion trigger and ensure new values take
|
||||
// effect
|
||||
@@ -302,7 +324,6 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
// Change the window size to disable delete triggered compaction
|
||||
kWindowSize = 0;
|
||||
@@ -322,9 +343,16 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBTablePropertiesTest,
|
||||
DBTablePropertiesTest,
|
||||
::testing::Values(
|
||||
"kCompactionStyleLevel",
|
||||
"kCompactionStyleUniversal"
|
||||
));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
+2
-1
@@ -5503,7 +5503,7 @@ TEST_F(DBTest, EmptyCompactedDB) {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest, SuggestCompactRangeTest) {
|
||||
TEST_F(DBTest, DISABLED_SuggestCompactRangeTest) {
|
||||
class CompactionFilterFactoryGetContext : public CompactionFilterFactory {
|
||||
public:
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
@@ -5611,6 +5611,7 @@ TEST_F(DBTest, SuggestCompactRangeTest) {
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(DBTest, PromoteL0) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
@@ -4306,6 +4306,24 @@ TEST_F(DBTest2, SameSmallestInSameLevel) {
|
||||
ASSERT_EQ("2,3,4,5,6,7,8", Get("key"));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, FileConsistencyCheckInOpen) {
|
||||
Put("foo", "bar");
|
||||
Flush();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionBuilder::CheckConsistencyBeforeReturn", [&](void* arg) {
|
||||
Status* ret_s = static_cast<Status*>(arg);
|
||||
*ret_s = Status::Corruption("fcc");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.force_consistency_checks = true;
|
||||
ASSERT_NOK(TryReopen(options));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, BlockBasedTablePrefixIndexSeekForPrev) {
|
||||
// create a DB with block prefix index
|
||||
BlockBasedTableOptions table_options;
|
||||
|
||||
@@ -1953,6 +1953,7 @@ TEST_F(DBTestUniversalCompaction2, BasicL0toL1) {
|
||||
ASSERT_GT(NumTableFilesAtLevel(6), 0);
|
||||
}
|
||||
|
||||
#if defined(ENABLE_SINGLE_LEVEL_DTC)
|
||||
TEST_F(DBTestUniversalCompaction2, SingleLevel) {
|
||||
const int kNumKeys = 3000;
|
||||
const int kWindowSize = 100;
|
||||
@@ -1991,6 +1992,7 @@ TEST_F(DBTestUniversalCompaction2, SingleLevel) {
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
#endif // ENABLE_SINGLE_LEVEL_DTC
|
||||
|
||||
TEST_F(DBTestUniversalCompaction2, MultipleLevels) {
|
||||
const int kWindowSize = 100;
|
||||
|
||||
@@ -580,7 +580,7 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGetWithCompaction) {
|
||||
std::vector<std::string> write_ts_list;
|
||||
std::vector<std::string> read_ts_list;
|
||||
|
||||
const auto& verify_record_func = [&](size_t i, size_t k,
|
||||
const auto& verify_records_func = [&](size_t i, size_t begin, size_t end,
|
||||
ColumnFamilyHandle* cfh) {
|
||||
std::string value;
|
||||
std::string timestamp;
|
||||
@@ -591,9 +591,11 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGetWithCompaction) {
|
||||
std::string expected_timestamp =
|
||||
std::string(write_ts_list[i].data(), write_ts_list[i].size());
|
||||
|
||||
ASSERT_OK(db_->Get(ropts, cfh, Key1(k), &value, ×tamp));
|
||||
ASSERT_EQ("value_" + std::to_string(k) + "_" + std::to_string(i), value);
|
||||
ASSERT_EQ(expected_timestamp, timestamp);
|
||||
for (size_t j = begin; j <= end; ++j) {
|
||||
ASSERT_OK(db_->Get(ropts, cfh, Key1(j), &value, ×tamp));
|
||||
ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i), value);
|
||||
ASSERT_EQ(expected_timestamp, timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
@@ -609,9 +611,7 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGetWithCompaction) {
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
wopts));
|
||||
if (j == kSplitPosBase + i || j == kNumKeysPerTimestamp - 1) {
|
||||
for (size_t k = memtable_get_start; k <= j; ++k) {
|
||||
verify_record_func(i, k, handles_[cf]);
|
||||
}
|
||||
verify_records_func(i, memtable_get_start, j, handles_[cf]);
|
||||
memtable_get_start = j + 1;
|
||||
|
||||
// flush all keys with the same timestamp to two sst files, split at
|
||||
@@ -641,15 +641,153 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGetWithCompaction) {
|
||||
write_ts_list[i].size());
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
ColumnFamilyHandle* cfh = handles_[cf];
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
verify_record_func(i, j, cfh);
|
||||
}
|
||||
verify_records_func(i, 0, kNumKeysPerTimestamp - 1, cfh);
|
||||
}
|
||||
}
|
||||
};
|
||||
verify_db_func();
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, BatchWriteAndMultiGet) {
|
||||
const int kNumKeysPerFile = 8192;
|
||||
const size_t kNumTimestamps = 2;
|
||||
const size_t kNumKeysPerTimestamp = (kNumKeysPerFile - 1) / kNumTimestamps;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
|
||||
size_t ts_sz = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(ts_sz);
|
||||
options.comparator = &test_cmp;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(
|
||||
10 /*bits_per_key*/, false /*use_block_based_builder*/));
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
std::vector<std::string> write_ts_list;
|
||||
std::vector<std::string> read_ts_list;
|
||||
|
||||
const auto& verify_records_func = [&](size_t i, ColumnFamilyHandle* cfh) {
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> key_vals;
|
||||
std::vector<std::string> values;
|
||||
std::vector<std::string> timestamps;
|
||||
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
key_vals.push_back(Key1(j));
|
||||
}
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
keys.push_back(key_vals[j]);
|
||||
}
|
||||
|
||||
ReadOptions ropts;
|
||||
const Slice read_ts = read_ts_list[i];
|
||||
ropts.timestamp = &read_ts;
|
||||
std::string expected_timestamp(write_ts_list[i].data(),
|
||||
write_ts_list[i].size());
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfhs(keys.size(), cfh);
|
||||
std::vector<Status> statuses =
|
||||
db_->MultiGet(ropts, cfhs, keys, &values, ×tamps);
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
ASSERT_OK(statuses[j]);
|
||||
ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
values[j]);
|
||||
ASSERT_EQ(expected_timestamp, timestamps[j]);
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
write_ts_list.push_back(Timestamp(i * 2, 0));
|
||||
read_ts_list.push_back(Timestamp(1 + i * 2, 0));
|
||||
const Slice& write_ts = write_ts_list.back();
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
WriteOptions wopts;
|
||||
WriteBatch batch(0, 0, ts_sz);
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
ASSERT_OK(
|
||||
batch.Put(handles_[cf], Key1(j),
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i)));
|
||||
}
|
||||
batch.AssignTimestamp(write_ts);
|
||||
ASSERT_OK(db_->Write(wopts, &batch));
|
||||
|
||||
verify_records_func(i, handles_[cf]);
|
||||
|
||||
ASSERT_OK(Flush(cf));
|
||||
}
|
||||
}
|
||||
|
||||
const auto& verify_db_func = [&]() {
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
ReadOptions ropts;
|
||||
const Slice read_ts = read_ts_list[i];
|
||||
ropts.timestamp = &read_ts;
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
ColumnFamilyHandle* cfh = handles_[cf];
|
||||
verify_records_func(i, cfh);
|
||||
}
|
||||
}
|
||||
};
|
||||
verify_db_func();
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetNoReturnTs) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions write_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
ASSERT_OK(db_->Put(write_opts, "foo", "value"));
|
||||
ASSERT_OK(db_->Put(write_opts, "bar", "value"));
|
||||
ASSERT_OK(db_->Put(write_opts, "fooxxxxxxxxxxxxxxxx", "value"));
|
||||
ASSERT_OK(db_->Put(write_opts, "barxxxxxxxxxxxxxxxx", "value"));
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
ts_str = Timestamp(2, 0);
|
||||
ts = ts_str;
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &ts;
|
||||
{
|
||||
ColumnFamilyHandle* column_families[] = {cfh, cfh};
|
||||
Slice keys[] = {"foo", "bar"};
|
||||
PinnableSlice values[] = {PinnableSlice(), PinnableSlice()};
|
||||
Status statuses[] = {Status::OK(), Status::OK()};
|
||||
dbfull()->MultiGet(read_opts, /*num_keys=*/2, &column_families[0], &keys[0],
|
||||
&values[0], &statuses[0], /*sorted_input=*/false);
|
||||
for (const auto& s : statuses) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
{
|
||||
ColumnFamilyHandle* column_families[] = {cfh, cfh, cfh, cfh};
|
||||
// Make user keys longer than configured timestamp size (16 bytes) to
|
||||
// verify RocksDB does not use the trailing bytes 'x' as timestamp.
|
||||
Slice keys[] = {"fooxxxxxxxxxxxxxxxx", "barxxxxxxxxxxxxxxxx", "foo", "bar"};
|
||||
PinnableSlice values[] = {PinnableSlice(), PinnableSlice(), PinnableSlice(),
|
||||
PinnableSlice()};
|
||||
Status statuses[] = {Status::OK(), Status::OK(), Status::OK(),
|
||||
Status::OK()};
|
||||
dbfull()->MultiGet(read_opts, /*num_keys=*/4, &column_families[0], &keys[0],
|
||||
&values[0], &statuses[0], /*sorted_input=*/false);
|
||||
for (const auto& s : statuses) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
|
||||
@@ -238,6 +238,46 @@ Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reas
|
||||
return bg_error_;
|
||||
}
|
||||
|
||||
Status ErrorHandler::SetBGError(const IOStatus& bg_io_err,
|
||||
BackgroundErrorReason reason) {
|
||||
db_mutex_->AssertHeld();
|
||||
if (bg_io_err.ok()) {
|
||||
return Status::OK();
|
||||
}
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = bg_io_err;
|
||||
}
|
||||
Status new_bg_io_err = bg_io_err;
|
||||
Status s;
|
||||
if (bg_io_err.GetDataLoss()) {
|
||||
// FIrst, data loss is treated as unrecoverable error. So it can directly
|
||||
// overwrite any existing bg_error_.
|
||||
bool auto_recovery = false;
|
||||
Status bg_err(new_bg_io_err, Status::Severity::kUnrecoverableError);
|
||||
bg_error_ = bg_err;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s,
|
||||
db_mutex_, &auto_recovery);
|
||||
return bg_error_;
|
||||
} else if (bg_io_err.GetRetryable()) {
|
||||
// Second, check if the error is a retryable IO error or not. if it is
|
||||
// retryable error and its severity is higher than bg_error_, overwrite
|
||||
// the bg_error_ with new error.
|
||||
// In current stage, treat retryable error as HardError. No automatic
|
||||
// recovery.
|
||||
bool auto_recovery = false;
|
||||
Status bg_err(new_bg_io_err, Status::Severity::kHardError);
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s,
|
||||
db_mutex_, &auto_recovery);
|
||||
if (bg_err.severity() > bg_error_.severity()) {
|
||||
bg_error_ = bg_err;
|
||||
}
|
||||
return bg_error_;
|
||||
} else {
|
||||
s = SetBGError(new_bg_io_err, reason);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status ErrorHandler::OverrideNoSpaceError(Status bg_error,
|
||||
bool* auto_recovery) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "options/db_options.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
@@ -34,6 +35,8 @@ class ErrorHandler {
|
||||
|
||||
Status SetBGError(const Status& bg_err, BackgroundErrorReason reason);
|
||||
|
||||
Status SetBGError(const IOStatus& bg_io_err, BackgroundErrorReason reason);
|
||||
|
||||
Status GetBGError() { return bg_error_; }
|
||||
|
||||
Status GetRecoveryError() { return recovery_error_; }
|
||||
|
||||
+380
-42
@@ -44,7 +44,7 @@ class DBErrorHandlingFSTest : public DBTestBase {
|
||||
class DBErrorHandlingFS : public FileSystemWrapper {
|
||||
public:
|
||||
DBErrorHandlingFS()
|
||||
: FileSystemWrapper(FileSystem::Default().get()),
|
||||
: FileSystemWrapper(FileSystem::Default()),
|
||||
trig_no_space(false),
|
||||
trig_io_error(false) {}
|
||||
|
||||
@@ -150,12 +150,13 @@ class ErrorHandlerFSListener : public EventListener {
|
||||
};
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
@@ -180,13 +181,77 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableeError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
Put(Key(1), "val1");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeFinishBuildTable",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
|
||||
Put(Key(2), "val2");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeSyncTable",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val2", Get(Key(2)));
|
||||
|
||||
Put(Key(3), "val3");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeCloseTableFile",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val3", Get(Key(3)));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
@@ -222,13 +287,59 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
std::string new_manifest;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
old_manifest = GetManifestNameFromLiveFiles();
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
Put(Key(0), "val");
|
||||
Flush();
|
||||
Put(Key(1), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
ASSERT_EQ("val", Get(Key(1)));
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
@@ -272,12 +383,13 @@ TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
@@ -343,13 +455,88 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionWriteError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
std::string new_manifest;
|
||||
std::atomic<bool> fail_manifest(false);
|
||||
DestroyAndReopen(options);
|
||||
old_manifest = GetManifestNameFromLiveFiles();
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
Put(Key(0), "val");
|
||||
Put(Key(2), "val");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError));
|
||||
listener->EnableAutoRecovery(false);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
// Wait for flush of 2nd L0 file before starting compaction
|
||||
{{"DBImpl::FlushMemTable:FlushMemTableFinished",
|
||||
"BackgroundCallCompaction:0"},
|
||||
// Wait for compaction to detect manifest write error
|
||||
{"BackgroundCallCompaction:1", "CompactionManifestWriteError:0"},
|
||||
// Make compaction thread wait for error to be cleared
|
||||
{"CompactionManifestWriteError:1",
|
||||
"DBImpl::BackgroundCallCompaction:FoundObsoleteFiles"}});
|
||||
// trigger manifest write failure in compaction thread
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0", [&](void*) { fail_manifest.store(true); });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
|
||||
if (fail_manifest.load()) {
|
||||
fault_fs->SetFilesystemActive(false,error_msg); }
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put(Key(1), "val");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:0");
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:1");
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
ASSERT_EQ("val", Get(Key(1)));
|
||||
ASSERT_EQ("val", Get(Key(2)));
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
@@ -386,11 +573,59 @@ TEST_F(DBErrorHandlingFSTest, CompactionWriteError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, CorruptionError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionWriteRetryableError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
Put(Key(0), "va;");
|
||||
Put(Key(2), "va;");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError));
|
||||
listener->EnableAutoRecovery(false);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::FlushMemTable:FlushMemTableFinished",
|
||||
"BackgroundCallCompaction:0"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put(Key(1), "val");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, CorruptionError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
Status s;
|
||||
@@ -426,12 +661,13 @@ TEST_F(DBErrorHandlingFSTest, CorruptionError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
@@ -460,12 +696,13 @@ TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FailRecoverFlushError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
@@ -487,12 +724,13 @@ TEST_F(DBErrorHandlingFSTest, FailRecoverFlushError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, WALWriteError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 32768;
|
||||
options.listeners.emplace_back(listener);
|
||||
@@ -557,13 +795,109 @@ TEST_F(DBErrorHandlingFSTest, WALWriteError) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, MultiCFWALWriteError) {
|
||||
FaultInjectionTestFS* fault_fs =
|
||||
new FaultInjectionTestFS(FileSystem::Default().get());
|
||||
TEST_F(DBErrorHandlingFSTest, WALWriteRetryableError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.file_system.reset(fault_fs);
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 32768;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.paranoid_checks = true;
|
||||
Status s;
|
||||
Random rnd(301);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
// For the first batch, write is successful, require sync
|
||||
{
|
||||
WriteBatch batch;
|
||||
|
||||
for (auto i = 0; i < 100; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK());
|
||||
};
|
||||
|
||||
// For the second batch, the first 2 file Append are successful, then the
|
||||
// following Append fails due to file system retryable IOError.
|
||||
{
|
||||
WriteBatch batch;
|
||||
int write_error = 0;
|
||||
|
||||
for (auto i = 100; i < 200; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"WritableFileWriter::Append:BeforePrepareWrite", [&](void*) {
|
||||
write_error++;
|
||||
if (write_error > 2) {
|
||||
fault_fs->SetFilesystemActive(false, error_msg);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
s = dbfull()->Write(wopts, &batch);
|
||||
ASSERT_EQ(true, s.IsIOError());
|
||||
}
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// Data in corrupted WAL are not stored
|
||||
for (auto i = 0; i < 199; ++i) {
|
||||
if (i < 100) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
} else {
|
||||
ASSERT_EQ(Get(Key(i)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
|
||||
// Resume and write a new batch, should be in the WAL
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
{
|
||||
WriteBatch batch;
|
||||
|
||||
for (auto i = 200; i < 300; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK());
|
||||
};
|
||||
|
||||
Reopen(options);
|
||||
for (auto i = 0; i < 300; ++i) {
|
||||
if (i < 100 || i >= 200) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
} else {
|
||||
ASSERT_EQ(Get(Key(i)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, MultiCFWALWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 32768;
|
||||
options.listeners.emplace_back(listener);
|
||||
@@ -643,6 +977,7 @@ TEST_F(DBErrorHandlingFSTest, MultiCFWALWriteError) {
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
|
||||
FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default());
|
||||
std::vector<std::unique_ptr<Env>> fault_envs;
|
||||
std::vector<FaultInjectionTestFS*> fault_fs;
|
||||
std::vector<Options> options;
|
||||
std::vector<std::shared_ptr<ErrorHandlerFSListener>> listener;
|
||||
@@ -654,12 +989,13 @@ TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
listener.emplace_back(new ErrorHandlerFSListener());
|
||||
options.emplace_back(GetDefaultOptions());
|
||||
fault_fs.emplace_back(
|
||||
new FaultInjectionTestFS(FileSystem::Default().get()));
|
||||
fault_fs.emplace_back(new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::shared_ptr<FileSystem> fs(fault_fs.back());
|
||||
fault_envs.emplace_back(new CompositeEnvWrapper(def_env, fs));
|
||||
options[i].env = fault_envs.back().get();
|
||||
options[i].create_if_missing = true;
|
||||
options[i].level0_file_num_compaction_trigger = 2;
|
||||
options[i].writable_file_max_buffer_size = 32768;
|
||||
options[i].file_system.reset(fault_fs[i]);
|
||||
options[i].listeners.emplace_back(listener[i]);
|
||||
options[i].sst_file_manager = sfm;
|
||||
DB* dbptr;
|
||||
@@ -742,6 +1078,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
|
||||
FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default());
|
||||
std::vector<std::unique_ptr<Env>> fault_envs;
|
||||
std::vector<FaultInjectionTestFS*> fault_fs;
|
||||
std::vector<Options> options;
|
||||
std::vector<std::shared_ptr<ErrorHandlerFSListener>> listener;
|
||||
@@ -753,12 +1090,13 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
listener.emplace_back(new ErrorHandlerFSListener());
|
||||
options.emplace_back(GetDefaultOptions());
|
||||
fault_fs.emplace_back(
|
||||
new FaultInjectionTestFS(FileSystem::Default().get()));
|
||||
fault_fs.emplace_back(new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::shared_ptr<FileSystem> fs(fault_fs.back());
|
||||
fault_envs.emplace_back(new CompositeEnvWrapper(def_env, fs));
|
||||
options[i].env = fault_envs.back().get();
|
||||
options[i].create_if_missing = true;
|
||||
options[i].level0_file_num_compaction_trigger = 2;
|
||||
options[i].writable_file_max_buffer_size = 32768;
|
||||
options[i].file_system.reset(fault_fs[i]);
|
||||
options[i].listeners.emplace_back(listener[i]);
|
||||
options[i].sst_file_manager = sfm;
|
||||
DB* dbptr;
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "file/filename.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -2799,6 +2801,47 @@ TEST_P(ExternalSSTFileTest, IngestFilesTriggerFlushingWithTwoWriteQueue) {
|
||||
GenerateAndAddExternalFile(options, data);
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileTest, DeltaEncodingWhileGlobalSeqnoPresents) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
constexpr size_t kValueSize = 8;
|
||||
Random rnd(301);
|
||||
std::string value(RandomString(&rnd, kValueSize));
|
||||
|
||||
// Write some key to make global seqno larger than zero
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put("ab" + Key(i), value));
|
||||
}
|
||||
// Get a Snapshot to make RocksDB assign global seqno to ingested sst files.
|
||||
auto snap = dbfull()->GetSnapshot();
|
||||
|
||||
std::string fname = sst_files_dir_ + "test_file";
|
||||
rocksdb::SstFileWriter writer(EnvOptions(), options);
|
||||
ASSERT_OK(writer.Open(fname));
|
||||
std::string key1 = "ab";
|
||||
std::string key2 = "ab";
|
||||
|
||||
// Make the prefix of key2 is same with key1 add zero seqno. The tail of every
|
||||
// key is composed as (seqno << 8 | value_type), and here `1` represents
|
||||
// ValueType::kTypeValue
|
||||
|
||||
PutFixed64(&key2, PackSequenceAndType(0, kTypeValue));
|
||||
key2 += "cdefghijkl";
|
||||
|
||||
ASSERT_OK(writer.Put(key1, value));
|
||||
ASSERT_OK(writer.Put(key2, value));
|
||||
|
||||
ExternalSstFileInfo info;
|
||||
ASSERT_OK(writer.Finish(&info));
|
||||
|
||||
ASSERT_OK(dbfull()->IngestExternalFile({info.file_path},
|
||||
IngestExternalFileOptions()));
|
||||
dbfull()->ReleaseSnapshot(snap);
|
||||
ASSERT_EQ(value, Get(key1));
|
||||
// You will get error here
|
||||
ASSERT_EQ(value, Get(key2));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileTest, ExternalSSTFileTest,
|
||||
testing::Values(std::make_tuple(false, false),
|
||||
std::make_tuple(false, true),
|
||||
|
||||
+18
-6
@@ -238,10 +238,14 @@ Status FlushJob::Run(LogsWithPrepTracker* prep_tracker,
|
||||
} else if (write_manifest_) {
|
||||
TEST_SYNC_POINT("FlushJob::InstallResults");
|
||||
// Replace immutable memtable with the generated Table
|
||||
IOStatus tmp_io_s;
|
||||
s = cfd_->imm()->TryInstallMemtableFlushResults(
|
||||
cfd_, mutable_cf_options_, mems_, prep_tracker, versions_, db_mutex_,
|
||||
meta_.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_,
|
||||
log_buffer_, &committed_flush_jobs_info_);
|
||||
log_buffer_, &committed_flush_jobs_info_, &tmp_io_s);
|
||||
if (!tmp_io_s.ok()) {
|
||||
io_status_ = tmp_io_s;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && file_meta != nullptr) {
|
||||
@@ -371,6 +375,12 @@ Status FlushJob::WriteLevel0Table() {
|
||||
meta_.oldest_ancester_time = std::min(current_time, oldest_key_time);
|
||||
meta_.file_creation_time = current_time;
|
||||
|
||||
uint64_t creation_time = (cfd_->ioptions()->compaction_style ==
|
||||
CompactionStyle::kCompactionStyleFIFO)
|
||||
? current_time
|
||||
: meta_.oldest_ancester_time;
|
||||
|
||||
IOStatus io_s;
|
||||
s = BuildTable(
|
||||
dbname_, db_options_.env, db_options_.fs.get(), *cfd_->ioptions(),
|
||||
mutable_cf_options_, file_options_, cfd_->table_cache(), iter.get(),
|
||||
@@ -379,12 +389,14 @@ Status FlushJob::WriteLevel0Table() {
|
||||
cfd_->GetName(), existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, snapshot_checker_,
|
||||
output_compression_, mutable_cf_options_.sample_for_compression,
|
||||
cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
TableFileCreationReason::kFlush, event_logger_, job_context_->job_id,
|
||||
Env::IO_HIGH, &table_properties_, 0 /* level */,
|
||||
meta_.oldest_ancester_time, oldest_key_time, write_hint,
|
||||
current_time);
|
||||
TableFileCreationReason::kFlush, &io_s, event_logger_,
|
||||
job_context_->job_id, Env::IO_HIGH, &table_properties_, 0 /* level */,
|
||||
creation_time, oldest_key_time, write_hint, current_time);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
LogFlush(db_options_.info_log);
|
||||
}
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
|
||||
@@ -90,6 +90,9 @@ class FlushJob {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// Return the IO status
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
private:
|
||||
void ReportStartedFlush();
|
||||
void ReportFlushInputSize(const autovector<MemTable*>& mems);
|
||||
@@ -154,6 +157,7 @@ class FlushJob {
|
||||
Version* base_;
|
||||
bool pick_memtable_called;
|
||||
Env::Priority thread_pri_;
|
||||
IOStatus io_status_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -108,7 +108,7 @@ class FlushJobTest : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
|
||||
+7
-7
@@ -37,10 +37,10 @@ Writer::~Writer() {
|
||||
}
|
||||
}
|
||||
|
||||
Status Writer::WriteBuffer() { return dest_->Flush(); }
|
||||
IOStatus Writer::WriteBuffer() { return dest_->Flush(); }
|
||||
|
||||
Status Writer::Close() {
|
||||
Status s;
|
||||
IOStatus Writer::Close() {
|
||||
IOStatus s;
|
||||
if (dest_) {
|
||||
s = dest_->Close();
|
||||
dest_.reset();
|
||||
@@ -48,7 +48,7 @@ Status Writer::Close() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status Writer::AddRecord(const Slice& slice) {
|
||||
IOStatus Writer::AddRecord(const Slice& slice) {
|
||||
const char* ptr = slice.data();
|
||||
size_t left = slice.size();
|
||||
|
||||
@@ -59,7 +59,7 @@ Status Writer::AddRecord(const Slice& slice) {
|
||||
// Fragment the record if necessary and emit it. Note that if slice
|
||||
// is empty, we still want to iterate once to emit a single
|
||||
// zero-length record
|
||||
Status s;
|
||||
IOStatus s;
|
||||
bool begin = true;
|
||||
do {
|
||||
const int64_t leftover = kBlockSize - block_offset_;
|
||||
@@ -114,7 +114,7 @@ Status Writer::AddRecord(const Slice& slice) {
|
||||
|
||||
bool Writer::TEST_BufferIsEmpty() { return dest_->TEST_BufferIsEmpty(); }
|
||||
|
||||
Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
|
||||
IOStatus Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
|
||||
assert(n <= 0xffff); // Must fit in two bytes
|
||||
|
||||
size_t header_size;
|
||||
@@ -150,7 +150,7 @@ Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
|
||||
EncodeFixed32(buf, crc);
|
||||
|
||||
// Write the header and the payload
|
||||
Status s = dest_->Append(Slice(buf, header_size));
|
||||
IOStatus s = dest_->Append(Slice(buf, header_size));
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(Slice(ptr, n));
|
||||
}
|
||||
|
||||
+5
-4
@@ -13,6 +13,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "db/log_format.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
@@ -79,16 +80,16 @@ class Writer {
|
||||
|
||||
~Writer();
|
||||
|
||||
Status AddRecord(const Slice& slice);
|
||||
IOStatus AddRecord(const Slice& slice);
|
||||
|
||||
WritableFileWriter* file() { return dest_.get(); }
|
||||
const WritableFileWriter* file() const { return dest_.get(); }
|
||||
|
||||
uint64_t get_log_number() const { return log_number_; }
|
||||
|
||||
Status WriteBuffer();
|
||||
IOStatus WriteBuffer();
|
||||
|
||||
Status Close();
|
||||
IOStatus Close();
|
||||
|
||||
bool TEST_BufferIsEmpty();
|
||||
|
||||
@@ -103,7 +104,7 @@ class Writer {
|
||||
// record type stored in the header.
|
||||
uint32_t type_crc_[kMaxRecordType + 1];
|
||||
|
||||
Status EmitPhysicalRecord(RecordType type, const char* ptr, size_t length);
|
||||
IOStatus EmitPhysicalRecord(RecordType type, const char* ptr, size_t length);
|
||||
|
||||
// If true, it does not flush after each write. Instead it relies on the upper
|
||||
// layer to manually does the flush by calling ::WriteBuffer()
|
||||
|
||||
+3
-3
@@ -919,9 +919,9 @@ void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
range_del_iter->MaxCoveringTombstoneSeqnum(iter->lkey->user_key()));
|
||||
}
|
||||
GetFromTable(*(iter->lkey), iter->max_covering_tombstone_seq, true,
|
||||
callback, is_blob, iter->value->GetSelf(),
|
||||
/*timestamp=*/nullptr, iter->s, &(iter->merge_context), &seq,
|
||||
&found_final_value, &merge_in_progress);
|
||||
callback, is_blob, iter->value->GetSelf(), iter->timestamp,
|
||||
iter->s, &(iter->merge_context), &seq, &found_final_value,
|
||||
&merge_in_progress);
|
||||
|
||||
if (!found_final_value && merge_in_progress) {
|
||||
*(iter->s) = Status::MergeInProgress();
|
||||
|
||||
+4
-1
@@ -390,7 +390,8 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer,
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info) {
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info,
|
||||
IOStatus* io_s) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS);
|
||||
mu->AssertHeld();
|
||||
@@ -471,8 +472,10 @@ 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();
|
||||
|
||||
// we will be changing the version in the next code path,
|
||||
// so we better create a new one, since versions are immutable
|
||||
|
||||
+2
-1
@@ -266,7 +266,8 @@ class MemTableList {
|
||||
VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer,
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info);
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info,
|
||||
IOStatus* io_s);
|
||||
|
||||
// New memtables are inserted at the front of the list.
|
||||
// Takes ownership of the referenced held on *m by the caller of Add().
|
||||
|
||||
@@ -92,7 +92,6 @@ class MemTableListTest : public testing::Test {
|
||||
CreateDB();
|
||||
// Create a mock VersionSet
|
||||
DBOptions db_options;
|
||||
db_options.file_system = FileSystem::Default();
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
EnvOptions env_options;
|
||||
std::shared_ptr<Cache> table_cache(NewLRUCache(50000, 16));
|
||||
@@ -115,13 +114,14 @@ class MemTableListTest : public testing::Test {
|
||||
auto cfd = column_family_set->GetDefault();
|
||||
EXPECT_TRUE(nullptr != cfd);
|
||||
uint64_t file_num = file_number.fetch_add(1);
|
||||
IOStatus io_s;
|
||||
// Create dummy mutex.
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedMutexLock l(&mutex);
|
||||
std::list<std::unique_ptr<FlushJobInfo>> flush_jobs_info;
|
||||
Status s = list->TryInstallMemtableFlushResults(
|
||||
cfd, mutable_cf_options, m, &dummy_prep_tracker, &versions, &mutex,
|
||||
file_num, to_delete, nullptr, &log_buffer, &flush_jobs_info);
|
||||
file_num, to_delete, nullptr, &log_buffer, &flush_jobs_info, &io_s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -139,7 +139,6 @@ class MemTableListTest : public testing::Test {
|
||||
CreateDB();
|
||||
// Create a mock VersionSet
|
||||
DBOptions db_options;
|
||||
db_options.file_system.reset(new LegacyFileSystemWrapper(db_options.env));
|
||||
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
EnvOptions env_options;
|
||||
|
||||
+2
-5
@@ -424,6 +424,7 @@ class Repairer {
|
||||
}
|
||||
|
||||
LegacyFileSystemWrapper fs(env_);
|
||||
IOStatus io_s;
|
||||
status = BuildTable(
|
||||
dbname_, env_, &fs, *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(), env_options_, table_cache_,
|
||||
@@ -432,7 +433,7 @@ class Repairer {
|
||||
cfd->GetID(), cfd->GetName(), {}, kMaxSequenceNumber,
|
||||
snapshot_checker, kNoCompression, 0 /* sample_for_compression */,
|
||||
CompressionOptions(), false, nullptr /* internal_stats */,
|
||||
TableFileCreationReason::kRecovery, nullptr /* event_logger */,
|
||||
TableFileCreationReason::kRecovery, &io_s, nullptr /* event_logger */,
|
||||
0 /* job_id */, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
-1 /* level */, current_time, write_hint);
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
@@ -672,10 +673,6 @@ Status RepairDB(const std::string& dbname, const DBOptions& db_options,
|
||||
|
||||
Status RepairDB(const std::string& dbname, const Options& options) {
|
||||
Options opts(options);
|
||||
if (opts.file_system == nullptr) {
|
||||
opts.file_system.reset(new LegacyFileSystemWrapper(opts.env));
|
||||
;
|
||||
}
|
||||
|
||||
DBOptions db_options(opts);
|
||||
ColumnFamilyOptions cf_options(opts);
|
||||
|
||||
+273
-3
@@ -14,13 +14,16 @@
|
||||
#include <cinttypes>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/table_cache.h"
|
||||
@@ -100,6 +103,9 @@ class VersionBuilder::Rep {
|
||||
FileComparator level_zero_cmp_;
|
||||
FileComparator level_nonzero_cmp_;
|
||||
|
||||
// Metadata for all blob files affected by the series of version edits.
|
||||
std::map<uint64_t, std::shared_ptr<BlobFileMetaData>> changed_blob_files_;
|
||||
|
||||
public:
|
||||
Rep(const FileOptions& file_options, Logger* info_log,
|
||||
TableCache* table_cache,
|
||||
@@ -140,6 +146,57 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<BlobFileMetaData> GetBlobFileMetaData(
|
||||
uint64_t blob_file_number) const {
|
||||
auto changed_it = changed_blob_files_.find(blob_file_number);
|
||||
if (changed_it != changed_blob_files_.end()) {
|
||||
const auto& meta = changed_it->second;
|
||||
assert(meta);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
assert(base_vstorage_);
|
||||
|
||||
const auto& base_blob_files = base_vstorage_->GetBlobFiles();
|
||||
|
||||
auto base_it = base_blob_files.find(blob_file_number);
|
||||
if (base_it != base_blob_files.end()) {
|
||||
const auto& meta = base_it->second;
|
||||
assert(meta);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
return std::shared_ptr<BlobFileMetaData>();
|
||||
}
|
||||
|
||||
Status CheckConsistencyOfOldestBlobFileReference(
|
||||
const VersionStorageInfo* vstorage, uint64_t blob_file_number) const {
|
||||
assert(vstorage);
|
||||
|
||||
// TODO: remove this check once we actually start recoding metadata for
|
||||
// blob files in the MANIFEST.
|
||||
if (vstorage->GetBlobFiles().empty()) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
if (blob_file_number == kInvalidBlobFileNumber) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const auto meta = GetBlobFileMetaData(blob_file_number);
|
||||
if (!meta) {
|
||||
std::ostringstream oss;
|
||||
oss << "Blob file #" << blob_file_number
|
||||
<< " is not part of this version";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CheckConsistency(VersionStorageInfo* vstorage) {
|
||||
#ifdef NDEBUG
|
||||
if (!vstorage->force_consistency_checks()) {
|
||||
@@ -148,10 +205,31 @@ class VersionBuilder::Rep {
|
||||
return Status::OK();
|
||||
}
|
||||
#endif
|
||||
// make sure the files are sorted correctly
|
||||
// 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.
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
auto& level_files = vstorage->LevelFiles(level);
|
||||
|
||||
if (level_files.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(level_files[0]);
|
||||
Status s = CheckConsistencyOfOldestBlobFileReference(
|
||||
vstorage, level_files[0]->oldest_blob_file_number);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
auto f1 = level_files[i - 1];
|
||||
auto f2 = level_files[i];
|
||||
#ifndef NDEBUG
|
||||
@@ -217,7 +295,27 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
|
||||
// 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 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()
|
||||
<< " consists entirely of garbage";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
}
|
||||
|
||||
Status ret_s;
|
||||
TEST_SYNC_POINT_CALLBACK("VersionBuilder::CheckConsistencyBeforeReturn",
|
||||
&ret_s);
|
||||
return ret_s;
|
||||
}
|
||||
|
||||
Status CheckConsistencyForDeletes(VersionEdit* /*edit*/, uint64_t number,
|
||||
@@ -282,6 +380,56 @@ class VersionBuilder::Rep {
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ApplyBlobFileAddition(const BlobFileAddition& blob_file_addition) {
|
||||
const uint64_t blob_file_number = blob_file_addition.GetBlobFileNumber();
|
||||
|
||||
auto meta = GetBlobFileMetaData(blob_file_number);
|
||||
if (meta) {
|
||||
std::ostringstream oss;
|
||||
oss << "Blob file #" << blob_file_number << " already added";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
auto shared_meta = std::make_shared<SharedBlobFileMetaData>(
|
||||
blob_file_number, blob_file_addition.GetTotalBlobCount(),
|
||||
blob_file_addition.GetTotalBlobBytes(),
|
||||
blob_file_addition.GetChecksumMethod(),
|
||||
blob_file_addition.GetChecksumValue());
|
||||
|
||||
constexpr uint64_t garbage_blob_count = 0;
|
||||
constexpr uint64_t garbage_blob_bytes = 0;
|
||||
auto new_meta = std::make_shared<BlobFileMetaData>(
|
||||
std::move(shared_meta), garbage_blob_count, garbage_blob_bytes);
|
||||
|
||||
changed_blob_files_.emplace(blob_file_number, std::move(new_meta));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status ApplyBlobFileGarbage(const BlobFileGarbage& blob_file_garbage) {
|
||||
const uint64_t blob_file_number = blob_file_garbage.GetBlobFileNumber();
|
||||
|
||||
auto meta = GetBlobFileMetaData(blob_file_number);
|
||||
if (!meta) {
|
||||
std::ostringstream oss;
|
||||
oss << "Blob file #" << blob_file_number << " not found";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
assert(meta->GetBlobFileNumber() == blob_file_number);
|
||||
|
||||
auto new_meta = std::make_shared<BlobFileMetaData>(
|
||||
meta->GetSharedMeta(),
|
||||
meta->GetGarbageBlobCount() + blob_file_garbage.GetGarbageBlobCount(),
|
||||
meta->GetGarbageBlobBytes() + blob_file_garbage.GetGarbageBlobBytes());
|
||||
|
||||
changed_blob_files_[blob_file_number] = std::move(new_meta);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Apply all of the edits in *edit to the current state.
|
||||
Status Apply(VersionEdit* edit) {
|
||||
Status s = CheckConsistency(base_vstorage_);
|
||||
@@ -296,7 +444,10 @@ class VersionBuilder::Rep {
|
||||
const auto number = del_file.second;
|
||||
if (level < num_levels_) {
|
||||
levels_[level].deleted_files.insert(number);
|
||||
CheckConsistencyForDeletes(edit, number, level);
|
||||
s = CheckConsistencyForDeletes(edit, number, level);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
auto exising = levels_[level].added_files.find(number);
|
||||
if (exising != levels_[level].added_files.end()) {
|
||||
@@ -333,9 +484,104 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add new blob files
|
||||
for (const auto& blob_file_addition : edit->GetBlobFileAdditions()) {
|
||||
s = ApplyBlobFileAddition(blob_file_addition);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase the amount of garbage for blob files affected by GC
|
||||
for (const auto& blob_file_garbage : edit->GetBlobFileGarbages()) {
|
||||
s = ApplyBlobFileGarbage(blob_file_garbage);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void AddBlobFileIfNeeded(
|
||||
VersionStorageInfo* vstorage,
|
||||
const std::shared_ptr<BlobFileMetaData>& meta) const {
|
||||
assert(vstorage);
|
||||
assert(meta);
|
||||
|
||||
if (meta->GetGarbageBlobCount() < meta->GetTotalBlobCount()) {
|
||||
vstorage->AddBlobFile(meta);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the blob file metadata from the base version with the changes (edits)
|
||||
// applied, and save the result into *vstorage.
|
||||
void SaveBlobFilesTo(VersionStorageInfo* vstorage) const {
|
||||
assert(base_vstorage_);
|
||||
assert(vstorage);
|
||||
|
||||
const auto& base_blob_files = base_vstorage_->GetBlobFiles();
|
||||
auto base_it = base_blob_files.begin();
|
||||
const auto base_it_end = base_blob_files.end();
|
||||
|
||||
auto changed_it = changed_blob_files_.begin();
|
||||
const auto changed_it_end = changed_blob_files_.end();
|
||||
|
||||
while (base_it != base_it_end && changed_it != changed_it_end) {
|
||||
const uint64_t base_blob_file_number = base_it->first;
|
||||
const uint64_t changed_blob_file_number = changed_it->first;
|
||||
|
||||
const auto& base_meta = base_it->second;
|
||||
const auto& changed_meta = changed_it->second;
|
||||
|
||||
assert(base_meta);
|
||||
assert(changed_meta);
|
||||
|
||||
if (base_blob_file_number < changed_blob_file_number) {
|
||||
assert(base_meta->GetGarbageBlobCount() <
|
||||
base_meta->GetTotalBlobCount());
|
||||
|
||||
vstorage->AddBlobFile(base_meta);
|
||||
|
||||
++base_it;
|
||||
} else if (changed_blob_file_number < base_blob_file_number) {
|
||||
AddBlobFileIfNeeded(vstorage, changed_meta);
|
||||
|
||||
++changed_it;
|
||||
} else {
|
||||
assert(base_blob_file_number == changed_blob_file_number);
|
||||
assert(base_meta->GetSharedMeta() == changed_meta->GetSharedMeta());
|
||||
assert(base_meta->GetGarbageBlobCount() <=
|
||||
changed_meta->GetGarbageBlobCount());
|
||||
assert(base_meta->GetGarbageBlobBytes() <=
|
||||
changed_meta->GetGarbageBlobBytes());
|
||||
|
||||
AddBlobFileIfNeeded(vstorage, changed_meta);
|
||||
|
||||
++base_it;
|
||||
++changed_it;
|
||||
}
|
||||
}
|
||||
|
||||
while (base_it != base_it_end) {
|
||||
const auto& base_meta = base_it->second;
|
||||
assert(base_meta);
|
||||
assert(base_meta->GetGarbageBlobCount() < base_meta->GetTotalBlobCount());
|
||||
|
||||
vstorage->AddBlobFile(base_meta);
|
||||
++base_it;
|
||||
}
|
||||
|
||||
while (changed_it != changed_it_end) {
|
||||
const auto& changed_meta = changed_it->second;
|
||||
assert(changed_meta);
|
||||
|
||||
AddBlobFileIfNeeded(vstorage, changed_meta);
|
||||
++changed_it;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the current state in *v.
|
||||
Status SaveTo(VersionStorageInfo* vstorage) {
|
||||
Status s = CheckConsistency(base_vstorage_);
|
||||
@@ -390,6 +636,8 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
SaveBlobFilesTo(vstorage);
|
||||
|
||||
s = CheckConsistency(vstorage);
|
||||
return s;
|
||||
}
|
||||
@@ -528,4 +776,26 @@ Status VersionBuilder::LoadTableHandlers(
|
||||
is_initial_load, prefix_extractor);
|
||||
}
|
||||
|
||||
BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd)
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->table_cache(),
|
||||
cfd->current()->storage_info(), cfd->ioptions()->info_log)),
|
||||
version_(cfd->current()) {
|
||||
version_->Ref();
|
||||
}
|
||||
|
||||
BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd, Version* v)
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->table_cache(),
|
||||
v->storage_info(), cfd->ioptions()->info_log)),
|
||||
version_(v) {
|
||||
assert(version_ != cfd->current());
|
||||
}
|
||||
|
||||
BaseReferencedVersionBuilder::~BaseReferencedVersionBuilder() {
|
||||
version_->Unref();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -21,6 +21,8 @@ class VersionStorageInfo;
|
||||
class VersionEdit;
|
||||
struct FileMetaData;
|
||||
class InternalStats;
|
||||
class Version;
|
||||
class ColumnFamilyData;
|
||||
|
||||
// A helper class so we can efficiently apply a whole sequence
|
||||
// of edits to a particular state without creating intermediate
|
||||
@@ -44,5 +46,20 @@ class VersionBuilder {
|
||||
std::unique_ptr<Rep> rep_;
|
||||
};
|
||||
|
||||
// A wrapper of version builder which references the current version in
|
||||
// constructor and unref it in the destructor.
|
||||
// Both of the constructor and destructor need to be called inside DB Mutex.
|
||||
class BaseReferencedVersionBuilder {
|
||||
public:
|
||||
explicit BaseReferencedVersionBuilder(ColumnFamilyData* cfd);
|
||||
BaseReferencedVersionBuilder(ColumnFamilyData* cfd, Version* v);
|
||||
~BaseReferencedVersionBuilder();
|
||||
VersionBuilder* version_builder() const { return version_builder_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<VersionBuilder> version_builder_;
|
||||
Version* version_;
|
||||
};
|
||||
|
||||
extern bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+502
-2
@@ -3,6 +3,8 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "db/version_edit.h"
|
||||
#include "db/version_set.h"
|
||||
@@ -57,12 +59,13 @@ class VersionBuilderTest : public testing::Test {
|
||||
SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100,
|
||||
uint64_t num_entries = 0, uint64_t num_deletions = 0,
|
||||
bool sampled = false, SequenceNumber smallest_seqno = 0,
|
||||
SequenceNumber largest_seqno = 0) {
|
||||
SequenceNumber largest_seqno = 0,
|
||||
uint64_t oldest_blob_file_number = kInvalidBlobFileNumber) {
|
||||
assert(level < vstorage_.num_levels());
|
||||
FileMetaData* f = new FileMetaData(
|
||||
file_number, path_id, file_size, GetInternalKey(smallest, smallest_seq),
|
||||
GetInternalKey(largest, largest_seq), smallest_seqno, largest_seqno,
|
||||
/* marked_for_compact */ false, kInvalidBlobFileNumber,
|
||||
/* marked_for_compact */ false, oldest_blob_file_number,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
f->compensated_file_size = file_size;
|
||||
@@ -75,6 +78,34 @@ 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) {
|
||||
auto shared_meta = std::make_shared<SharedBlobFileMetaData>(
|
||||
blob_file_number, total_blob_count, total_blob_bytes,
|
||||
std::move(checksum_method), std::move(checksum_value));
|
||||
auto meta = std::make_shared<BlobFileMetaData>(
|
||||
std::move(shared_meta), garbage_blob_count, garbage_blob_bytes);
|
||||
|
||||
vstorage_.AddBlobFile(std::move(meta));
|
||||
}
|
||||
|
||||
static std::shared_ptr<BlobFileMetaData> GetBlobFileMetaData(
|
||||
const VersionStorageInfo::BlobFiles& blob_files,
|
||||
uint64_t blob_file_number) {
|
||||
const auto it = blob_files.find(blob_file_number);
|
||||
|
||||
if (it == blob_files.end()) {
|
||||
return std::shared_ptr<BlobFileMetaData>();
|
||||
}
|
||||
|
||||
const auto& meta = it->second;
|
||||
assert(meta);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
void UpdateVersionStorageInfo() {
|
||||
vstorage_.UpdateFilesByCompactionPri(ioptions_.compaction_pri);
|
||||
vstorage_.UpdateNumNonEmptyLevels();
|
||||
@@ -319,6 +350,475 @@ TEST_F(VersionBuilderTest, ApplyDeleteAndSaveTo) {
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileAddition) {
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
constexpr bool force_consistency_checks = false;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
const auto& new_blob_files = new_vstorage.GetBlobFiles();
|
||||
ASSERT_EQ(new_blob_files.size(), 1);
|
||||
|
||||
const auto new_meta = GetBlobFileMetaData(new_blob_files, blob_file_number);
|
||||
|
||||
ASSERT_NE(new_meta, nullptr);
|
||||
ASSERT_EQ(new_meta->GetBlobFileNumber(), blob_file_number);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobCount(), total_blob_count);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobCount(), 0);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobBytes(), 0);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileAdditionAlreadyInBase) {
|
||||
// Attempt to add a blob file that is already present in the base version.
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr uint64_t garbage_blob_count = 123;
|
||||
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);
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
const Status s = builder.Apply(&edit);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "Blob file #1234 already added"));
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileAdditionAlreadyApplied) {
|
||||
// Attempt to add the same blob file twice using version edits.
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
const Status s = builder.Apply(&edit);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "Blob file #1234 already added"));
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileInBase) {
|
||||
// Increase the amount of garbage for a blob file present in the base version.
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr uint64_t garbage_blob_count = 123;
|
||||
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);
|
||||
|
||||
const auto meta =
|
||||
GetBlobFileMetaData(vstorage_.GetBlobFiles(), blob_file_number);
|
||||
ASSERT_NE(meta, nullptr);
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
constexpr uint64_t new_garbage_blob_count = 456;
|
||||
constexpr uint64_t new_garbage_blob_bytes = 111111;
|
||||
|
||||
edit.AddBlobFileGarbage(blob_file_number, new_garbage_blob_count,
|
||||
new_garbage_blob_bytes);
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
constexpr bool force_consistency_checks = false;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
const auto& new_blob_files = new_vstorage.GetBlobFiles();
|
||||
ASSERT_EQ(new_blob_files.size(), 1);
|
||||
|
||||
const auto new_meta = GetBlobFileMetaData(new_blob_files, blob_file_number);
|
||||
|
||||
ASSERT_NE(new_meta, nullptr);
|
||||
ASSERT_EQ(new_meta->GetSharedMeta(), meta->GetSharedMeta());
|
||||
ASSERT_EQ(new_meta->GetBlobFileNumber(), blob_file_number);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobCount(), total_blob_count);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobCount(),
|
||||
garbage_blob_count + new_garbage_blob_count);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobBytes(),
|
||||
garbage_blob_bytes + new_garbage_blob_bytes);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileAdditionApplied) {
|
||||
// Increase the amount of garbage for a blob file added using a version edit.
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit addition;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
|
||||
addition.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
ASSERT_OK(builder.Apply(&addition));
|
||||
|
||||
constexpr uint64_t garbage_blob_count = 123;
|
||||
constexpr uint64_t garbage_blob_bytes = 456789;
|
||||
|
||||
VersionEdit garbage;
|
||||
|
||||
garbage.AddBlobFileGarbage(blob_file_number, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
|
||||
ASSERT_OK(builder.Apply(&garbage));
|
||||
|
||||
constexpr bool force_consistency_checks = false;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
const auto& new_blob_files = new_vstorage.GetBlobFiles();
|
||||
ASSERT_EQ(new_blob_files.size(), 1);
|
||||
|
||||
const auto new_meta = GetBlobFileMetaData(new_blob_files, blob_file_number);
|
||||
|
||||
ASSERT_NE(new_meta, nullptr);
|
||||
ASSERT_EQ(new_meta->GetBlobFileNumber(), blob_file_number);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobCount(), total_blob_count);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobCount(), garbage_blob_count);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobBytes(), garbage_blob_bytes);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileNotFound) {
|
||||
// Attempt to increase the amount of garbage for a blob file that is
|
||||
// neither in the base version, nor was it added using a version edit.
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t garbage_blob_count = 5678;
|
||||
constexpr uint64_t garbage_blob_bytes = 999999;
|
||||
|
||||
edit.AddBlobFileGarbage(blob_file_number, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
|
||||
const Status s = builder.Apply(&edit);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "Blob file #1234 not found"));
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, SaveBlobFilesTo) {
|
||||
// Add three blob files to base version.
|
||||
for (uint64_t i = 1; i <= 3; ++i) {
|
||||
const uint64_t blob_file_number = i;
|
||||
const uint64_t total_blob_count = i * 1000;
|
||||
const uint64_t total_blob_bytes = i * 1000000;
|
||||
const uint64_t garbage_blob_count = i * 100;
|
||||
const uint64_t garbage_blob_bytes = i * 20000;
|
||||
|
||||
AddBlob(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(), garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
}
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
// Add some garbage to the second and third blob files. The second blob file
|
||||
// remains valid since it does not consist entirely of garbage yet. The third
|
||||
// blob file is all garbage after the edit and will not be part of the new
|
||||
// version.
|
||||
edit.AddBlobFileGarbage(/* blob_file_number */ 2,
|
||||
/* garbage_blob_count */ 200,
|
||||
/* garbage_blob_bytes */ 100000);
|
||||
edit.AddBlobFileGarbage(/* blob_file_number */ 3,
|
||||
/* garbage_blob_count */ 2700,
|
||||
/* garbage_blob_bytes */ 2940000);
|
||||
|
||||
// Add a fourth blob file.
|
||||
edit.AddBlobFile(/* blob_file_number */ 4, /* total_blob_count */ 4000,
|
||||
/* total_blob_bytes */ 4000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string());
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
constexpr bool force_consistency_checks = false;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
const auto& new_blob_files = new_vstorage.GetBlobFiles();
|
||||
ASSERT_EQ(new_blob_files.size(), 3);
|
||||
|
||||
const auto meta1 = GetBlobFileMetaData(new_blob_files, 1);
|
||||
|
||||
ASSERT_NE(meta1, nullptr);
|
||||
ASSERT_EQ(meta1->GetBlobFileNumber(), 1);
|
||||
ASSERT_EQ(meta1->GetTotalBlobCount(), 1000);
|
||||
ASSERT_EQ(meta1->GetTotalBlobBytes(), 1000000);
|
||||
ASSERT_EQ(meta1->GetGarbageBlobCount(), 100);
|
||||
ASSERT_EQ(meta1->GetGarbageBlobBytes(), 20000);
|
||||
|
||||
const auto meta2 = GetBlobFileMetaData(new_blob_files, 2);
|
||||
|
||||
ASSERT_NE(meta2, nullptr);
|
||||
ASSERT_EQ(meta2->GetBlobFileNumber(), 2);
|
||||
ASSERT_EQ(meta2->GetTotalBlobCount(), 2000);
|
||||
ASSERT_EQ(meta2->GetTotalBlobBytes(), 2000000);
|
||||
ASSERT_EQ(meta2->GetGarbageBlobCount(), 400);
|
||||
ASSERT_EQ(meta2->GetGarbageBlobBytes(), 140000);
|
||||
|
||||
const auto meta4 = GetBlobFileMetaData(new_blob_files, 4);
|
||||
|
||||
ASSERT_NE(meta4, nullptr);
|
||||
ASSERT_EQ(meta4->GetBlobFileNumber(), 4);
|
||||
ASSERT_EQ(meta4->GetTotalBlobCount(), 4000);
|
||||
ASSERT_EQ(meta4->GetTotalBlobBytes(), 4000000);
|
||||
ASSERT_EQ(meta4->GetGarbageBlobCount(), 0);
|
||||
ASSERT_EQ(meta4->GetGarbageBlobBytes(), 0);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForBlobFiles) {
|
||||
// Initialize base version. The first table file points to a valid blob file
|
||||
// in this version; the second one does not refer to any blob files.
|
||||
|
||||
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);
|
||||
Add(/* level */ 1, /* file_number */ 23, /* smallest */ "201",
|
||||
/* largest */ "300", /* file_size */ 100,
|
||||
/* path_id */ 0, /* smallest_seq */ 200, /* largest_seq */ 200,
|
||||
/* num_entries */ 0, /* num_deletions */ 0,
|
||||
/* sampled */ false, /* smallest_seqno */ 200, /* largest_seqno */ 200,
|
||||
kInvalidBlobFileNumber);
|
||||
|
||||
AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000,
|
||||
/* total_blob_bytes */ 1000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(),
|
||||
/* garbage_blob_count */ 500, /* garbage_blob_bytes */ 300000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
// Add a new table file that points to the existing blob file, and add a
|
||||
// new table file--blob file pair.
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
edit.AddFile(/* level */ 1, /* file_number */ 606, /* path_id */ 0,
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("701"),
|
||||
/* largest */ GetInternalKey("750"), /* smallest_seqno */ 200,
|
||||
/* largest_seqno */ 200, /* marked_for_compaction */ false,
|
||||
/* oldest_blob_file_number */ 16, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
|
||||
edit.AddFile(/* level */ 1, /* file_number */ 700, /* path_id */ 0,
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("801"),
|
||||
/* largest */ GetInternalKey("850"), /* smallest_seqno */ 200,
|
||||
/* largest_seqno */ 200, /* marked_for_compaction */ false,
|
||||
/* oldest_blob_file_number */ 1000, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
edit.AddBlobFile(/* blob_file_number */ 1000, /* total_blob_count */ 2000,
|
||||
/* total_blob_bytes */ 200000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string());
|
||||
|
||||
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);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesNotInVersion) {
|
||||
// Initialize base version. The table file points to a blob file that is
|
||||
// not in this version.
|
||||
|
||||
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 */ 256);
|
||||
|
||||
AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000,
|
||||
/* total_blob_bytes */ 1000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(),
|
||||
/* garbage_blob_count */ 500, /* garbage_blob_bytes */ 300000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
// 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 #256 is not part of this version"));
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbage) {
|
||||
// Initialize base version. The table file points to a blob file that is
|
||||
// all garbage.
|
||||
|
||||
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(),
|
||||
/* garbage_blob_count */ 1000, /* garbage_blob_bytes */ 1000000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
// 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, CheckConsistencyForFileDeletedTwice) {
|
||||
Add(0, 1U, "150", "200", 100U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
VersionEdit version_edit;
|
||||
version_edit.DeleteFile(0, 1U);
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
|
||||
VersionBuilder version_builder(env_options, table_cache, &vstorage_);
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, nullptr,
|
||||
true /* force_consistency_checks */);
|
||||
ASSERT_OK(version_builder.Apply(&version_edit));
|
||||
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
|
||||
|
||||
VersionBuilder version_builder2(env_options, table_cache, &new_vstorage);
|
||||
VersionStorageInfo new_vstorage2(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, nullptr,
|
||||
true /* force_consistency_checks */);
|
||||
ASSERT_NOK(version_builder2.Apply(&version_edit));
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
UnrefFilesInVersion(&new_vstorage2);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, EstimatedActiveKeys) {
|
||||
const uint32_t kTotalSamples = 20;
|
||||
const uint32_t kNumLevels = 5;
|
||||
|
||||
@@ -59,8 +59,6 @@ enum Tag : uint32_t {
|
||||
kDbId,
|
||||
kBlobFileAddition,
|
||||
kBlobFileGarbage,
|
||||
kStateUponManifestSwitch,
|
||||
kManifestSwitched,
|
||||
};
|
||||
|
||||
enum NewFileCustomTag : uint32_t {
|
||||
@@ -162,8 +160,6 @@ void VersionEdit::Clear() {
|
||||
column_family_name_.clear();
|
||||
is_in_atomic_group_ = false;
|
||||
remaining_entries_ = 0;
|
||||
state_upon_manifest_switch_ = false;
|
||||
manifest_switched_ = false;
|
||||
}
|
||||
|
||||
bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
@@ -297,14 +293,6 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
PutVarint32Varint32(dst, kColumnFamily, column_family_);
|
||||
}
|
||||
|
||||
if (state_upon_manifest_switch_) {
|
||||
PutVarint32(dst, kStateUponManifestSwitch);
|
||||
}
|
||||
|
||||
if (manifest_switched_) {
|
||||
PutVarint32(dst, kManifestSwitched);
|
||||
}
|
||||
|
||||
if (is_column_family_add_) {
|
||||
PutVarint32(dst, kColumnFamilyAdd);
|
||||
PutLengthPrefixedSlice(dst, Slice(column_family_name_));
|
||||
@@ -647,14 +635,6 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
is_column_family_drop_ = true;
|
||||
break;
|
||||
|
||||
case kStateUponManifestSwitch:
|
||||
state_upon_manifest_switch_ = true;
|
||||
break;
|
||||
|
||||
case kManifestSwitched:
|
||||
manifest_switched_ = true;
|
||||
break;
|
||||
|
||||
case kInAtomicGroup:
|
||||
is_in_atomic_group_ = true;
|
||||
if (!GetVarint32(&input, &remaining_entries_)) {
|
||||
|
||||
+2
-20
@@ -423,21 +423,10 @@ class VersionEdit {
|
||||
std::string DebugString(bool hex_key = false) const;
|
||||
std::string DebugJSON(int edit_num, bool hex_key = false) const;
|
||||
|
||||
void SetStateUponManifestSwitch(bool tag) {
|
||||
state_upon_manifest_switch_ = tag;
|
||||
}
|
||||
bool GetStateUponManifestSwitch() const {
|
||||
return state_upon_manifest_switch_;
|
||||
}
|
||||
void SetManifestSwitched(bool tag) {
|
||||
manifest_switched_ = tag;
|
||||
}
|
||||
bool GetManifestSwitched() const {
|
||||
return manifest_switched_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ReactiveVersionSet;
|
||||
friend class VersionEditHandler;
|
||||
friend class VersionEditHandlerPointInTime;
|
||||
friend class VersionSet;
|
||||
friend class Version;
|
||||
friend class AtomicGroupReadBuffer;
|
||||
@@ -483,13 +472,6 @@ class VersionEdit {
|
||||
|
||||
bool is_in_atomic_group_ = false;
|
||||
uint32_t remaining_entries_ = 0;
|
||||
// To distinguish the version edit written by WriteCurrentStateToManifest
|
||||
// and other regular writes. Default is false.
|
||||
bool state_upon_manifest_switch_ = false;
|
||||
// To indicate when WriteCurrentStateToManifest is successful. When both
|
||||
// state_upon_manifest_switch and manifest_switch_finished are true, it can
|
||||
// ensure the manifest switch is finished.
|
||||
bool manifest_switched_ = false;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -0,0 +1,586 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/version_edit_handler.h"
|
||||
|
||||
#include "monitoring/persistent_stats_history.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
VersionEditHandler::VersionEditHandler(
|
||||
bool read_only, const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
VersionSet* version_set, bool track_missing_files,
|
||||
bool no_error_if_table_files_missing)
|
||||
: read_only_(read_only),
|
||||
column_families_(column_families),
|
||||
status_(),
|
||||
version_set_(version_set),
|
||||
track_missing_files_(track_missing_files),
|
||||
no_error_if_table_files_missing_(no_error_if_table_files_missing),
|
||||
initialized_(false) {
|
||||
assert(version_set_ != nullptr);
|
||||
}
|
||||
|
||||
Status VersionEditHandler::Iterate(log::Reader& reader, std::string* db_id) {
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
size_t recovered_edits = 0;
|
||||
Status s = Initialize();
|
||||
while (reader.ReadRecord(&record, &scratch) && s.ok()) {
|
||||
VersionEdit edit;
|
||||
s = edit.DecodeFrom(record);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
if (edit.has_db_id_) {
|
||||
version_set_->db_id_ = edit.GetDbId();
|
||||
if (db_id != nullptr) {
|
||||
*db_id = version_set_->db_id_;
|
||||
}
|
||||
}
|
||||
s = read_buffer_.AddEdit(&edit);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
if (edit.is_in_atomic_group_) {
|
||||
if (read_buffer_.IsFull()) {
|
||||
for (auto& e : read_buffer_.replay_buffer()) {
|
||||
s = ApplyVersionEdit(e, &cfd);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
++recovered_edits;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
read_buffer_.Clear();
|
||||
}
|
||||
} else {
|
||||
s = ApplyVersionEdit(edit, &cfd);
|
||||
if (s.ok()) {
|
||||
++recovered_edits;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CheckIterationResult(reader, &s);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::Initialize() {
|
||||
Status s;
|
||||
if (!initialized_) {
|
||||
for (const auto& cf_desc : column_families_) {
|
||||
name_to_options_.emplace(cf_desc.name, cf_desc.options);
|
||||
}
|
||||
auto default_cf_iter = name_to_options_.find(kDefaultColumnFamilyName);
|
||||
if (default_cf_iter == name_to_options_.end()) {
|
||||
s = Status::InvalidArgument("Default column family not specified");
|
||||
}
|
||||
if (s.ok()) {
|
||||
VersionEdit default_cf_edit;
|
||||
default_cf_edit.AddColumnFamily(kDefaultColumnFamilyName);
|
||||
default_cf_edit.SetColumnFamily(0);
|
||||
ColumnFamilyData* cfd =
|
||||
CreateCfAndInit(default_cf_iter->second, default_cf_edit);
|
||||
assert(cfd != nullptr);
|
||||
#ifdef NDEBUG
|
||||
(void)cfd;
|
||||
#endif
|
||||
initialized_ = true;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::ApplyVersionEdit(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
Status s;
|
||||
if (edit.is_column_family_add_) {
|
||||
s = OnColumnFamilyAdd(edit, cfd);
|
||||
} else if (edit.is_column_family_drop_) {
|
||||
s = OnColumnFamilyDrop(edit, cfd);
|
||||
} else {
|
||||
s = OnNonCfOperation(edit, cfd);
|
||||
}
|
||||
if (s.ok()) {
|
||||
assert(cfd != nullptr);
|
||||
s = ExtractInfoFromVersionEdit(*cfd, edit);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::OnColumnFamilyAdd(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
bool cf_in_not_found = false;
|
||||
bool cf_in_builders = false;
|
||||
CheckColumnFamilyId(edit, &cf_in_not_found, &cf_in_builders);
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
Status s;
|
||||
if (cf_in_builders || cf_in_not_found) {
|
||||
s = Status::Corruption("MANIFEST adding the same column family twice: " +
|
||||
edit.column_family_name_);
|
||||
}
|
||||
if (s.ok()) {
|
||||
auto cf_options = name_to_options_.find(edit.column_family_name_);
|
||||
// implicitly add persistent_stats column family without requiring user
|
||||
// to specify
|
||||
ColumnFamilyData* tmp_cfd = nullptr;
|
||||
bool is_persistent_stats_column_family =
|
||||
edit.column_family_name_.compare(kPersistentStatsColumnFamilyName) == 0;
|
||||
if (cf_options == name_to_options_.end() &&
|
||||
!is_persistent_stats_column_family) {
|
||||
column_families_not_found_.emplace(edit.column_family_,
|
||||
edit.column_family_name_);
|
||||
} else {
|
||||
if (is_persistent_stats_column_family) {
|
||||
ColumnFamilyOptions cfo;
|
||||
OptimizeForPersistentStats(&cfo);
|
||||
tmp_cfd = CreateCfAndInit(cfo, edit);
|
||||
} else {
|
||||
tmp_cfd = CreateCfAndInit(cf_options->second, edit);
|
||||
}
|
||||
*cfd = tmp_cfd;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::OnColumnFamilyDrop(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
bool cf_in_not_found = false;
|
||||
bool cf_in_builders = false;
|
||||
CheckColumnFamilyId(edit, &cf_in_not_found, &cf_in_builders);
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
ColumnFamilyData* tmp_cfd = nullptr;
|
||||
Status s;
|
||||
if (cf_in_builders) {
|
||||
tmp_cfd = DestroyCfAndCleanup(edit);
|
||||
} else if (cf_in_not_found) {
|
||||
column_families_not_found_.erase(edit.column_family_);
|
||||
} else {
|
||||
s = Status::Corruption("MANIFEST - dropping non-existing column family");
|
||||
}
|
||||
*cfd = tmp_cfd;
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::OnNonCfOperation(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
bool cf_in_not_found = false;
|
||||
bool cf_in_builders = false;
|
||||
CheckColumnFamilyId(edit, &cf_in_not_found, &cf_in_builders);
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
Status s;
|
||||
if (!cf_in_not_found) {
|
||||
if (!cf_in_builders) {
|
||||
s = Status::Corruption(
|
||||
"MANIFEST record referencing unknown column family");
|
||||
}
|
||||
ColumnFamilyData* tmp_cfd = nullptr;
|
||||
if (s.ok()) {
|
||||
auto builder_iter = builders_.find(edit.column_family_);
|
||||
assert(builder_iter != builders_.end());
|
||||
tmp_cfd = version_set_->GetColumnFamilySet()->GetColumnFamily(
|
||||
edit.column_family_);
|
||||
assert(tmp_cfd != nullptr);
|
||||
s = MaybeCreateVersion(edit, tmp_cfd, /*force_create_version=*/false);
|
||||
if (s.ok()) {
|
||||
s = builder_iter->second->version_builder()->Apply(&edit);
|
||||
}
|
||||
}
|
||||
*cfd = tmp_cfd;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// TODO maybe cache the computation result
|
||||
bool VersionEditHandler::HasMissingFiles() const {
|
||||
bool ret = false;
|
||||
for (const auto& elem : cf_to_missing_files_) {
|
||||
const auto& missing_files = elem.second;
|
||||
if (!missing_files.empty()) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VersionEditHandler::CheckColumnFamilyId(const VersionEdit& edit,
|
||||
bool* cf_in_not_found,
|
||||
bool* cf_in_builders) const {
|
||||
assert(cf_in_not_found != nullptr);
|
||||
assert(cf_in_builders != nullptr);
|
||||
// Not found means that user didn't supply that column
|
||||
// family option AND we encountered column family add
|
||||
// record. Once we encounter column family drop record,
|
||||
// we will delete the column family from
|
||||
// column_families_not_found.
|
||||
bool in_not_found = column_families_not_found_.find(edit.column_family_) !=
|
||||
column_families_not_found_.end();
|
||||
// in builders means that user supplied that column family
|
||||
// option AND that we encountered column family add record
|
||||
bool in_builders = builders_.find(edit.column_family_) != builders_.end();
|
||||
// They cannot both be true
|
||||
assert(!(in_not_found && in_builders));
|
||||
*cf_in_not_found = in_not_found;
|
||||
*cf_in_builders = in_builders;
|
||||
}
|
||||
|
||||
void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
Status* s) {
|
||||
assert(s != nullptr);
|
||||
if (!s->ok()) {
|
||||
read_buffer_.Clear();
|
||||
} else if (!version_edit_params_.has_log_number_ ||
|
||||
!version_edit_params_.has_next_file_number_ ||
|
||||
!version_edit_params_.has_last_sequence_) {
|
||||
std::string msg("no ");
|
||||
if (!version_edit_params_.has_log_number_) {
|
||||
msg.append("log_file_number, ");
|
||||
}
|
||||
if (!version_edit_params_.has_next_file_number_) {
|
||||
msg.append("next_file_number, ");
|
||||
}
|
||||
if (!version_edit_params_.has_last_sequence_) {
|
||||
msg.append("last_sequence, ");
|
||||
}
|
||||
msg = msg.substr(0, msg.size() - 2);
|
||||
msg.append(" entry in MANIFEST");
|
||||
*s = Status::Corruption(msg);
|
||||
}
|
||||
if (s->ok() && !read_only_ && !column_families_not_found_.empty()) {
|
||||
std::string msg;
|
||||
for (const auto& cf : column_families_not_found_) {
|
||||
msg.append(", ");
|
||||
msg.append(cf.second);
|
||||
}
|
||||
msg = msg.substr(2);
|
||||
*s = Status::InvalidArgument("Column families not opened: " + msg);
|
||||
}
|
||||
if (s->ok()) {
|
||||
version_set_->GetColumnFamilySet()->UpdateMaxColumnFamily(
|
||||
version_edit_params_.max_column_family_);
|
||||
version_set_->MarkMinLogNumberToKeep2PC(
|
||||
version_edit_params_.min_log_number_to_keep_);
|
||||
version_set_->MarkFileNumberUsed(version_edit_params_.prev_log_number_);
|
||||
version_set_->MarkFileNumberUsed(version_edit_params_.log_number_);
|
||||
for (auto* cfd : *(version_set_->GetColumnFamilySet())) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
if (!builder->CheckConsistencyForNumLevels()) {
|
||||
*s = Status::InvalidArgument(
|
||||
"db has more levels than options.num_levels");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s->ok()) {
|
||||
for (auto* cfd : *(version_set_->GetColumnFamilySet())) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (read_only_) {
|
||||
cfd->table_cache()->SetTablesAreImmortal();
|
||||
}
|
||||
*s = LoadTables(cfd, /*prefetch_index_and_filter_in_cache=*/false,
|
||||
/*is_initial_load=*/true);
|
||||
if (!s->ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s->ok()) {
|
||||
for (auto* cfd : *(version_set_->column_family_set_)) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
assert(cfd->initialized());
|
||||
VersionEdit edit;
|
||||
*s = MaybeCreateVersion(edit, cfd, /*force_create_version=*/true);
|
||||
if (!s->ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s->ok()) {
|
||||
version_set_->manifest_file_size_ = reader.GetReadOffset();
|
||||
assert(version_set_->manifest_file_size_ > 0);
|
||||
version_set_->next_file_number_.store(
|
||||
version_edit_params_.next_file_number_ + 1);
|
||||
version_set_->last_allocated_sequence_ =
|
||||
version_edit_params_.last_sequence_;
|
||||
version_set_->last_published_sequence_ =
|
||||
version_edit_params_.last_sequence_;
|
||||
version_set_->last_sequence_ = version_edit_params_.last_sequence_;
|
||||
version_set_->prev_log_number_ = version_edit_params_.prev_log_number_;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionEditHandler::CreateCfAndInit(
|
||||
const ColumnFamilyOptions& cf_options, const VersionEdit& edit) {
|
||||
ColumnFamilyData* cfd = version_set_->CreateColumnFamily(cf_options, &edit);
|
||||
assert(cfd != nullptr);
|
||||
cfd->set_initialized();
|
||||
assert(builders_.find(edit.column_family_) == builders_.end());
|
||||
builders_.emplace(edit.column_family_,
|
||||
VersionBuilderUPtr(new BaseReferencedVersionBuilder(cfd)));
|
||||
if (track_missing_files_) {
|
||||
cf_to_missing_files_.emplace(edit.column_family_,
|
||||
std::unordered_set<uint64_t>());
|
||||
}
|
||||
return cfd;
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionEditHandler::DestroyCfAndCleanup(
|
||||
const VersionEdit& edit) {
|
||||
auto builder_iter = builders_.find(edit.column_family_);
|
||||
assert(builder_iter != builders_.end());
|
||||
builders_.erase(builder_iter);
|
||||
if (track_missing_files_) {
|
||||
auto missing_files_iter = cf_to_missing_files_.find(edit.column_family_);
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
cf_to_missing_files_.erase(missing_files_iter);
|
||||
}
|
||||
ColumnFamilyData* ret =
|
||||
version_set_->GetColumnFamilySet()->GetColumnFamily(edit.column_family_);
|
||||
assert(ret != nullptr);
|
||||
if (ret->UnrefAndTryDelete()) {
|
||||
ret = nullptr;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::MaybeCreateVersion(const VersionEdit& /*edit*/,
|
||||
ColumnFamilyData* cfd,
|
||||
bool force_create_version) {
|
||||
assert(cfd->initialized());
|
||||
Status s;
|
||||
if (force_create_version) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
auto* v = new Version(cfd, version_set_, version_set_->file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
version_set_->current_version_number_++);
|
||||
s = builder->SaveTo(v->storage_info());
|
||||
if (s.ok()) {
|
||||
// Install new version
|
||||
v->PrepareApply(
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
!(version_set_->db_options_->skip_stats_update_on_db_open));
|
||||
version_set_->AppendVersion(cfd, v);
|
||||
} else {
|
||||
delete v;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::LoadTables(ColumnFamilyData* cfd,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
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(
|
||||
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());
|
||||
if (s.IsPathNotFound() && no_error_if_table_files_missing_) {
|
||||
s = Status::OK();
|
||||
}
|
||||
if (!s.ok() && !version_set_->db_options_->paranoid_checks) {
|
||||
s = Status::OK();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
const VersionEdit& edit) {
|
||||
Status s;
|
||||
if (cfd != nullptr) {
|
||||
if (edit.has_db_id_) {
|
||||
version_edit_params_.SetDBId(edit.db_id_);
|
||||
}
|
||||
if (edit.has_log_number_) {
|
||||
if (cfd->GetLogNumber() > edit.log_number_) {
|
||||
ROCKS_LOG_WARN(
|
||||
version_set_->db_options()->info_log,
|
||||
"MANIFEST corruption detected, but ignored - Log numbers in "
|
||||
"records NOT monotonically increasing");
|
||||
} else {
|
||||
cfd->SetLogNumber(edit.log_number_);
|
||||
version_edit_params_.SetLogNumber(edit.log_number_);
|
||||
}
|
||||
}
|
||||
if (edit.has_comparator_ &&
|
||||
edit.comparator_ != cfd->user_comparator()->Name()) {
|
||||
s = Status::InvalidArgument(
|
||||
cfd->user_comparator()->Name(),
|
||||
"does not match existing comparator " + edit.comparator_);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
if (edit.has_prev_log_number_) {
|
||||
version_edit_params_.SetPrevLogNumber(edit.prev_log_number_);
|
||||
}
|
||||
if (edit.has_next_file_number_) {
|
||||
version_edit_params_.SetNextFile(edit.next_file_number_);
|
||||
}
|
||||
if (edit.has_max_column_family_) {
|
||||
version_edit_params_.SetMaxColumnFamily(edit.max_column_family_);
|
||||
}
|
||||
if (edit.has_min_log_number_to_keep_) {
|
||||
version_edit_params_.min_log_number_to_keep_ =
|
||||
std::max(version_edit_params_.min_log_number_to_keep_,
|
||||
edit.min_log_number_to_keep_);
|
||||
}
|
||||
if (edit.has_last_sequence_) {
|
||||
version_edit_params_.SetLastSequence(edit.last_sequence_);
|
||||
}
|
||||
if (!version_edit_params_.has_prev_log_number_) {
|
||||
version_edit_params_.SetPrevLogNumber(0);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
VersionEditHandlerPointInTime::VersionEditHandlerPointInTime(
|
||||
bool read_only, const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
VersionSet* version_set)
|
||||
: VersionEditHandler(read_only, column_families, version_set,
|
||||
/*track_missing_files=*/true,
|
||||
/*no_error_if_table_files_missing=*/true) {}
|
||||
|
||||
VersionEditHandlerPointInTime::~VersionEditHandlerPointInTime() {
|
||||
for (const auto& elem : versions_) {
|
||||
delete elem.second;
|
||||
}
|
||||
versions_.clear();
|
||||
}
|
||||
|
||||
void VersionEditHandlerPointInTime::CheckIterationResult(
|
||||
const log::Reader& reader, Status* s) {
|
||||
VersionEditHandler::CheckIterationResult(reader, s);
|
||||
assert(s != nullptr);
|
||||
if (s->ok()) {
|
||||
for (auto* cfd : *(version_set_->column_family_set_)) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
assert(cfd->initialized());
|
||||
auto v_iter = versions_.find(cfd->GetID());
|
||||
if (v_iter != versions_.end()) {
|
||||
assert(v_iter->second != nullptr);
|
||||
|
||||
version_set_->AppendVersion(cfd, v_iter->second);
|
||||
versions_.erase(v_iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionEditHandlerPointInTime::DestroyCfAndCleanup(
|
||||
const VersionEdit& edit) {
|
||||
ColumnFamilyData* cfd = VersionEditHandler::DestroyCfAndCleanup(edit);
|
||||
auto v_iter = versions_.find(edit.column_family_);
|
||||
if (v_iter != versions_.end()) {
|
||||
delete v_iter->second;
|
||||
versions_.erase(v_iter);
|
||||
}
|
||||
return cfd;
|
||||
}
|
||||
|
||||
Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
const VersionEdit& edit, ColumnFamilyData* cfd, bool force_create_version) {
|
||||
assert(cfd != nullptr);
|
||||
if (!force_create_version) {
|
||||
assert(edit.column_family_ == cfd->GetID());
|
||||
}
|
||||
auto missing_files_iter = cf_to_missing_files_.find(cfd->GetID());
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
std::unordered_set<uint64_t>& missing_files = missing_files_iter->second;
|
||||
const bool prev_has_missing_files = !missing_files.empty();
|
||||
for (const auto& file : edit.GetDeletedFiles()) {
|
||||
uint64_t file_num = file.second;
|
||||
auto fiter = missing_files.find(file_num);
|
||||
if (fiter != missing_files.end()) {
|
||||
missing_files.erase(fiter);
|
||||
}
|
||||
}
|
||||
Status s;
|
||||
for (const auto& elem : edit.GetNewFiles()) {
|
||||
const FileMetaData& meta = elem.second;
|
||||
const FileDescriptor& fd = meta.fd;
|
||||
uint64_t file_num = fd.GetNumber();
|
||||
const std::string fpath =
|
||||
MakeTableFileName(cfd->ioptions()->cf_paths[0].path, file_num);
|
||||
s = version_set_->VerifyFileMetadata(fpath, meta);
|
||||
if (s.IsPathNotFound() || s.IsNotFound()) {
|
||||
missing_files.insert(file_num);
|
||||
s = Status::OK();
|
||||
}
|
||||
}
|
||||
bool missing_info = !version_edit_params_.has_log_number_ ||
|
||||
!version_edit_params_.has_next_file_number_ ||
|
||||
!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))) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
auto* version = new Version(cfd, version_set_, version_set_->file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
version_set_->current_version_number_++);
|
||||
s = builder->SaveTo(version->storage_info());
|
||||
if (s.ok()) {
|
||||
version->PrepareApply(
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
!version_set_->db_options_->skip_stats_update_on_db_open);
|
||||
auto v_iter = versions_.find(cfd->GetID());
|
||||
if (v_iter != versions_.end()) {
|
||||
delete v_iter->second;
|
||||
v_iter->second = version;
|
||||
} else {
|
||||
versions_.emplace(cfd->GetID(), version);
|
||||
}
|
||||
} else {
|
||||
delete version;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "db/version_builder.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "db/version_set.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
typedef std::unique_ptr<BaseReferencedVersionBuilder> VersionBuilderUPtr;
|
||||
|
||||
// A class used for scanning MANIFEST file.
|
||||
// VersionEditHandler reads a MANIFEST file, parses the version edits, and
|
||||
// builds the version set's in-memory state, e.g. the version storage info for
|
||||
// the versions of column families.
|
||||
// To use this class and its subclasses,
|
||||
// 1. Create an object of VersionEditHandler or its subclasses.
|
||||
// VersionEditHandler handler(read_only, column_families, version_set,
|
||||
// track_missing_files, ignore_missing_files);
|
||||
// 2. Status s = handler.Iterate(reader, &db_id);
|
||||
// 3. Check s and handle possible errors.
|
||||
//
|
||||
// Not thread-safe, external synchronization is necessary if an object of
|
||||
// VersionEditHandler is shared by multiple threads.
|
||||
class VersionEditHandler {
|
||||
public:
|
||||
explicit VersionEditHandler(
|
||||
bool read_only,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
VersionSet* version_set, bool track_missing_files,
|
||||
bool ignore_missing_files);
|
||||
|
||||
virtual ~VersionEditHandler() {}
|
||||
|
||||
Status Iterate(log::Reader& reader, std::string* db_id);
|
||||
|
||||
const Status& status() const { return status_; }
|
||||
|
||||
bool HasMissingFiles() const;
|
||||
|
||||
protected:
|
||||
Status ApplyVersionEdit(VersionEdit& edit, ColumnFamilyData** cfd);
|
||||
|
||||
Status OnColumnFamilyAdd(VersionEdit& edit, ColumnFamilyData** cfd);
|
||||
|
||||
Status OnColumnFamilyDrop(VersionEdit& edit, ColumnFamilyData** cfd);
|
||||
|
||||
Status OnNonCfOperation(VersionEdit& edit, ColumnFamilyData** cfd);
|
||||
|
||||
Status Initialize();
|
||||
|
||||
void CheckColumnFamilyId(const VersionEdit& edit, bool* cf_in_not_found,
|
||||
bool* cf_in_builders) const;
|
||||
|
||||
virtual void CheckIterationResult(const log::Reader& reader, Status* s);
|
||||
|
||||
ColumnFamilyData* CreateCfAndInit(const ColumnFamilyOptions& cf_options,
|
||||
const VersionEdit& edit);
|
||||
|
||||
virtual ColumnFamilyData* DestroyCfAndCleanup(const VersionEdit& edit);
|
||||
|
||||
virtual Status MaybeCreateVersion(const VersionEdit& edit,
|
||||
ColumnFamilyData* cfd,
|
||||
bool force_create_version);
|
||||
|
||||
Status LoadTables(ColumnFamilyData* cfd,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load);
|
||||
|
||||
const bool read_only_;
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families_;
|
||||
Status status_;
|
||||
VersionSet* version_set_;
|
||||
AtomicGroupReadBuffer read_buffer_;
|
||||
std::unordered_map<uint32_t, VersionBuilderUPtr> builders_;
|
||||
std::unordered_map<std::string, ColumnFamilyOptions> name_to_options_;
|
||||
std::unordered_map<uint32_t, std::string> column_families_not_found_;
|
||||
VersionEditParams version_edit_params_;
|
||||
const bool track_missing_files_;
|
||||
std::unordered_map<uint32_t, std::unordered_set<uint64_t>>
|
||||
cf_to_missing_files_;
|
||||
bool no_error_if_table_files_missing_;
|
||||
|
||||
private:
|
||||
Status ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
const VersionEdit& edit);
|
||||
|
||||
bool initialized_;
|
||||
};
|
||||
|
||||
// A class similar to its base class, i.e. VersionEditHandler.
|
||||
// VersionEditHandlerPointInTime restores the versions to the most recent point
|
||||
// in time such that at this point, the version does not have missing files.
|
||||
//
|
||||
// Not thread-safe, external synchronization is necessary if an object of
|
||||
// VersionEditHandlerPointInTime is shared by multiple threads.
|
||||
class VersionEditHandlerPointInTime : public VersionEditHandler {
|
||||
public:
|
||||
VersionEditHandlerPointInTime(
|
||||
bool read_only,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
VersionSet* version_set);
|
||||
~VersionEditHandlerPointInTime() override;
|
||||
|
||||
protected:
|
||||
void CheckIterationResult(const log::Reader& reader, Status* s) override;
|
||||
ColumnFamilyData* DestroyCfAndCleanup(const VersionEdit& edit) override;
|
||||
Status MaybeCreateVersion(const VersionEdit& edit, ColumnFamilyData* cfd,
|
||||
bool force_create_version) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, Version*> versions_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -46,7 +46,6 @@ TEST_F(VersionEditTest, EncodeDecode) {
|
||||
edit.SetLogNumber(kBig + 100);
|
||||
edit.SetNextFile(kBig + 200);
|
||||
edit.SetLastSequence(kBig + 1000);
|
||||
edit.SetStateUponManifestSwitch(true);
|
||||
TestEncodeDecode(edit);
|
||||
}
|
||||
|
||||
@@ -81,7 +80,6 @@ TEST_F(VersionEditTest, EncodeDecodeNewFile4) {
|
||||
edit.SetLogNumber(kBig + 100);
|
||||
edit.SetNextFile(kBig + 200);
|
||||
edit.SetLastSequence(kBig + 1000);
|
||||
edit.SetStateUponManifestSwitch(true);
|
||||
TestEncodeDecode(edit);
|
||||
|
||||
std::string encoded, encoded2;
|
||||
@@ -105,7 +103,6 @@ TEST_F(VersionEditTest, EncodeDecodeNewFile4) {
|
||||
ASSERT_EQ(kInvalidBlobFileNumber,
|
||||
new_files[2].second.oldest_blob_file_number);
|
||||
ASSERT_EQ(1001, new_files[3].second.oldest_blob_file_number);
|
||||
ASSERT_TRUE(parsed.GetStateUponManifestSwitch());
|
||||
}
|
||||
|
||||
TEST_F(VersionEditTest, ForwardCompatibleNewFile4) {
|
||||
@@ -282,38 +279,6 @@ TEST_F(VersionEditTest, DbId) {
|
||||
TestEncodeDecode(edit);
|
||||
}
|
||||
|
||||
TEST_F(VersionEditTest, ManifestSwitchTag) {
|
||||
VersionEdit edit1, decode1;
|
||||
edit1.SetStateUponManifestSwitch(true);
|
||||
TestEncodeDecode(edit1);
|
||||
std::string encoded1;
|
||||
edit1.EncodeTo(&encoded1);
|
||||
ASSERT_OK(decode1.DecodeFrom(encoded1));
|
||||
ASSERT_TRUE(decode1.GetStateUponManifestSwitch());
|
||||
ASSERT_TRUE(!decode1.GetManifestSwitched());
|
||||
|
||||
VersionEdit edit2, decode2;
|
||||
edit2.SetManifestSwitched(true);
|
||||
TestEncodeDecode(edit2);
|
||||
std::string encoded2;
|
||||
edit2.EncodeTo(&encoded2);
|
||||
ASSERT_OK(decode2.DecodeFrom(encoded2));
|
||||
ASSERT_TRUE(!decode2.GetStateUponManifestSwitch());
|
||||
ASSERT_TRUE(decode2.GetManifestSwitched());
|
||||
|
||||
VersionEdit edit3, decode3;
|
||||
edit3.SetStateUponManifestSwitch(true);
|
||||
edit3.SetManifestSwitched(true);
|
||||
TestEncodeDecode(edit3);
|
||||
std::string encoded3;
|
||||
edit3.EncodeTo(&encoded3);
|
||||
ASSERT_OK(decode3.DecodeFrom(encoded3));
|
||||
ASSERT_TRUE(decode3.GetStateUponManifestSwitch());
|
||||
ASSERT_TRUE(decode3.GetManifestSwitched());
|
||||
|
||||
|
||||
}
|
||||
|
||||
TEST_F(VersionEditTest, BlobFileAdditionAndGarbage) {
|
||||
VersionEdit edit;
|
||||
|
||||
|
||||
+316
-89
@@ -29,6 +29,7 @@
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_builder.h"
|
||||
#include "db/version_edit_handler.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "file/read_write_util.h"
|
||||
@@ -1202,28 +1203,6 @@ void LevelIterator::InitFileIterator(size_t new_file_index) {
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
// A wrapper of version builder which references the current version in
|
||||
// constructor and unref it in the destructor.
|
||||
// Both of the constructor and destructor need to be called inside DB Mutex.
|
||||
class BaseReferencedVersionBuilder {
|
||||
public:
|
||||
explicit BaseReferencedVersionBuilder(ColumnFamilyData* cfd)
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->table_cache(),
|
||||
cfd->current()->storage_info(), cfd->ioptions()->info_log)),
|
||||
version_(cfd->current()) {
|
||||
version_->Ref();
|
||||
}
|
||||
~BaseReferencedVersionBuilder() {
|
||||
version_->Unref();
|
||||
}
|
||||
VersionBuilder* version_builder() { return version_builder_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<VersionBuilder> version_builder_;
|
||||
Version* version_;
|
||||
};
|
||||
|
||||
Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
|
||||
const FileMetaData* file_meta,
|
||||
const std::string* fname) const {
|
||||
@@ -1918,8 +1897,8 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
get_ctx.emplace_back(
|
||||
user_comparator(), merge_operator_, info_log_, db_statistics_,
|
||||
iter->s->ok() ? GetContext::kNotFound : GetContext::kMerge, iter->ukey,
|
||||
iter->value, /*timestamp*/ nullptr, nullptr, &(iter->merge_context),
|
||||
true, &iter->max_covering_tombstone_seq, this->env_, nullptr,
|
||||
iter->value, iter->timestamp, nullptr, &(iter->merge_context), true,
|
||||
&iter->max_covering_tombstone_seq, this->env_, nullptr,
|
||||
merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob,
|
||||
tracing_mget_id);
|
||||
// MergeInProgress status, if set, has been transferred to the get_context
|
||||
@@ -2396,6 +2375,11 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
// compaction score for the whole DB. Adding other levels as if
|
||||
// they are L0 files.
|
||||
for (int i = 1; i < num_levels(); i++) {
|
||||
// Its possible that a subset of the files in a level may be in a
|
||||
// compaction, due to delete triggered compaction or trivial move.
|
||||
// In that case, the below check may not catch a level being
|
||||
// compacted as it only checks the first file. The worst that can
|
||||
// happen is a scheduled compaction thread will find nothing to do.
|
||||
if (!files_[i].empty() && !files_[i][0]->being_compacted) {
|
||||
num_sorted_runs++;
|
||||
}
|
||||
@@ -2620,6 +2604,19 @@ void VersionStorageInfo::AddFile(int level, FileMetaData* f, Logger* info_log) {
|
||||
level_files->push_back(f);
|
||||
}
|
||||
|
||||
void VersionStorageInfo::AddBlobFile(
|
||||
std::shared_ptr<BlobFileMetaData> blob_file_meta) {
|
||||
assert(blob_file_meta);
|
||||
|
||||
const uint64_t blob_file_number = blob_file_meta->GetBlobFileNumber();
|
||||
|
||||
auto it = blob_files_.lower_bound(blob_file_number);
|
||||
assert(it == blob_files_.end() || it->first != blob_file_number);
|
||||
|
||||
blob_files_.insert(
|
||||
it, BlobFiles::value_type(blob_file_number, std::move(blob_file_meta)));
|
||||
}
|
||||
|
||||
// Version::PrepareApply() need to be called before calling the function, or
|
||||
// following functions called:
|
||||
// 1. UpdateNumNonEmptyLevels();
|
||||
@@ -3462,6 +3459,21 @@ std::string Version::DebugString(bool hex, bool print_stats) const {
|
||||
r.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
const auto& blob_files = storage_info_.GetBlobFiles();
|
||||
if (!blob_files.empty()) {
|
||||
r.append("--- blob files --- version# ");
|
||||
AppendNumberTo(&r, version_number_);
|
||||
r.append(" ---\n");
|
||||
for (const auto& pair : blob_files) {
|
||||
const auto& blob_file_meta = pair.second;
|
||||
assert(blob_file_meta);
|
||||
|
||||
r.append(blob_file_meta->DebugString());
|
||||
r.push_back('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -3570,6 +3582,33 @@ VersionSet::~VersionSet() {
|
||||
obsolete_files_.clear();
|
||||
}
|
||||
|
||||
void VersionSet::Reset() {
|
||||
if (column_family_set_) {
|
||||
Cache* table_cache = column_family_set_->get_table_cache();
|
||||
WriteBufferManager* wbm = column_family_set_->write_buffer_manager();
|
||||
WriteController* wc = column_family_set_->write_controller();
|
||||
column_family_set_.reset(new ColumnFamilySet(dbname_, db_options_,
|
||||
file_options_, table_cache,
|
||||
wbm, wc, block_cache_tracer_));
|
||||
}
|
||||
db_id_.clear();
|
||||
next_file_number_.store(2);
|
||||
min_log_number_to_keep_2pc_.store(0);
|
||||
manifest_file_number_ = 0;
|
||||
options_file_number_ = 0;
|
||||
pending_manifest_file_number_ = 0;
|
||||
last_sequence_.store(0);
|
||||
last_allocated_sequence_.store(0);
|
||||
last_published_sequence_.store(0);
|
||||
prev_log_number_ = 0;
|
||||
descriptor_log_.reset();
|
||||
current_version_number_ = 0;
|
||||
manifest_writers_.clear();
|
||||
manifest_file_size_ = 0;
|
||||
obsolete_files_.clear();
|
||||
obsolete_manifests_.clear();
|
||||
}
|
||||
|
||||
void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
|
||||
Version* v) {
|
||||
// compute new compaction score
|
||||
@@ -3762,6 +3801,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
|
||||
uint64_t new_manifest_file_size = 0;
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
|
||||
assert(pending_manifest_file_number_ == 0);
|
||||
if (!descriptor_log_ ||
|
||||
@@ -3805,7 +3845,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
builder_guards.size() == versions.size());
|
||||
ColumnFamilyData* cfd = versions[i]->cfd_;
|
||||
s = builder_guards[i]->version_builder()->LoadTableHandlers(
|
||||
cfd->internal_stats(), cfd->ioptions()->optimize_filters_for_hits,
|
||||
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());
|
||||
@@ -3872,15 +3912,19 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
++idx;
|
||||
#endif /* !NDEBUG */
|
||||
s = descriptor_log_->AddRecord(record);
|
||||
if (!s.ok()) {
|
||||
io_s = descriptor_log_->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
s = io_s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = SyncManifest(env_, db_options_, descriptor_log_->file());
|
||||
io_s = SyncManifest(env_, db_options_, descriptor_log_->file());
|
||||
}
|
||||
if (!s.ok()) {
|
||||
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());
|
||||
}
|
||||
@@ -3889,8 +3933,12 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// If we just created a new descriptor file, install it by writing a
|
||||
// new CURRENT file that points to it.
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
s = SetCurrentFile(env_, dbname_, pending_manifest_file_number_,
|
||||
db_directory);
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -4452,8 +4500,6 @@ Status VersionSet::Recover(
|
||||
reporter.status = &s;
|
||||
log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter,
|
||||
true /* checksum */, 0 /* log_number */);
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
AtomicGroupReadBuffer read_buffer;
|
||||
s = ReadAndRecover(&reader, &read_buffer, cf_name_to_options,
|
||||
column_families_not_found, builders,
|
||||
@@ -4540,7 +4586,11 @@ Status VersionSet::Recover(
|
||||
Version* v = new Version(cfd, this, file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
current_version_number_++);
|
||||
builder->SaveTo(v->storage_info());
|
||||
s = builder->SaveTo(v->storage_info());
|
||||
if (!s.ok()) {
|
||||
delete v;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Install recovered version
|
||||
v->PrepareApply(*cfd->GetLatestMutableCFOptions(),
|
||||
@@ -4581,6 +4631,159 @@ Status VersionSet::Recover(
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class ManifestPicker {
|
||||
public:
|
||||
explicit ManifestPicker(const std::string& dbname, FileSystem* fs);
|
||||
void SeekToFirstManifest();
|
||||
// REQUIRES Valid() == true
|
||||
std::string GetNextManifest(uint64_t* file_number, std::string* file_name);
|
||||
bool Valid() const { return manifest_file_iter_ != manifest_files_.end(); }
|
||||
const Status& status() const { return status_; }
|
||||
|
||||
private:
|
||||
const std::string& dbname_;
|
||||
FileSystem* const fs_;
|
||||
// MANIFEST file names(s)
|
||||
std::vector<std::string> manifest_files_;
|
||||
std::vector<std::string>::const_iterator manifest_file_iter_;
|
||||
Status status_;
|
||||
};
|
||||
|
||||
ManifestPicker::ManifestPicker(const std::string& dbname, FileSystem* fs)
|
||||
: dbname_(dbname), fs_(fs) {}
|
||||
|
||||
void ManifestPicker::SeekToFirstManifest() {
|
||||
assert(fs_ != nullptr);
|
||||
std::vector<std::string> children;
|
||||
Status s = fs_->GetChildren(dbname_, IOOptions(), &children, /*dbg=*/nullptr);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
return;
|
||||
}
|
||||
for (const auto& fname : children) {
|
||||
uint64_t file_num = 0;
|
||||
FileType file_type;
|
||||
bool parse_ok = ParseFileName(fname, &file_num, &file_type);
|
||||
if (parse_ok && file_type == kDescriptorFile) {
|
||||
manifest_files_.push_back(fname);
|
||||
}
|
||||
}
|
||||
std::sort(manifest_files_.begin(), manifest_files_.end(),
|
||||
[](const std::string& lhs, const std::string& rhs) {
|
||||
uint64_t num1 = 0;
|
||||
uint64_t num2 = 0;
|
||||
FileType type1;
|
||||
FileType type2;
|
||||
bool parse_ok1 = ParseFileName(lhs, &num1, &type1);
|
||||
bool parse_ok2 = ParseFileName(rhs, &num2, &type2);
|
||||
#ifndef NDEBUG
|
||||
assert(parse_ok1);
|
||||
assert(parse_ok2);
|
||||
#else
|
||||
(void)parse_ok1;
|
||||
(void)parse_ok2;
|
||||
#endif
|
||||
return num1 > num2;
|
||||
});
|
||||
manifest_file_iter_ = manifest_files_.begin();
|
||||
}
|
||||
|
||||
std::string ManifestPicker::GetNextManifest(uint64_t* number,
|
||||
std::string* file_name) {
|
||||
assert(status_.ok());
|
||||
assert(Valid());
|
||||
std::string ret;
|
||||
if (manifest_file_iter_ != manifest_files_.end()) {
|
||||
ret.assign(dbname_);
|
||||
if (ret.back() != kFilePathSeparator) {
|
||||
ret.push_back(kFilePathSeparator);
|
||||
}
|
||||
ret.append(*manifest_file_iter_);
|
||||
if (number) {
|
||||
FileType type;
|
||||
bool parse = ParseFileName(*manifest_file_iter_, number, &type);
|
||||
assert(type == kDescriptorFile);
|
||||
#ifndef NDEBUG
|
||||
assert(parse);
|
||||
#else
|
||||
(void)parse;
|
||||
#endif
|
||||
}
|
||||
if (file_name) {
|
||||
*file_name = *manifest_file_iter_;
|
||||
}
|
||||
++manifest_file_iter_;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status VersionSet::TryRecover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
|
||||
std::string* db_id, bool* has_missing_table_file) {
|
||||
ManifestPicker manifest_picker(dbname_, fs_);
|
||||
manifest_picker.SeekToFirstManifest();
|
||||
Status s = manifest_picker.status();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (!manifest_picker.Valid()) {
|
||||
return Status::Corruption("Cannot locate MANIFEST file in " + dbname_);
|
||||
}
|
||||
std::string manifest_path =
|
||||
manifest_picker.GetNextManifest(&manifest_file_number_, nullptr);
|
||||
while (!manifest_path.empty()) {
|
||||
s = TryRecoverFromOneManifest(manifest_path, column_families, read_only,
|
||||
db_id, has_missing_table_file);
|
||||
if (s.ok() || !manifest_picker.Valid()) {
|
||||
break;
|
||||
}
|
||||
Reset();
|
||||
manifest_path =
|
||||
manifest_picker.GetNextManifest(&manifest_file_number_, nullptr);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionSet::TryRecoverFromOneManifest(
|
||||
const std::string& manifest_path,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
|
||||
std::string* db_id, bool* has_missing_table_file) {
|
||||
ROCKS_LOG_INFO(db_options_->info_log, "Trying to recover from manifest: %s\n",
|
||||
manifest_path.c_str());
|
||||
std::unique_ptr<SequentialFileReader> manifest_file_reader;
|
||||
Status s;
|
||||
{
|
||||
std::unique_ptr<FSSequentialFile> manifest_file;
|
||||
s = fs_->NewSequentialFile(manifest_path,
|
||||
fs_->OptimizeForManifestRead(file_options_),
|
||||
&manifest_file, nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
manifest_file_reader.reset(
|
||||
new SequentialFileReader(std::move(manifest_file), manifest_path,
|
||||
db_options_->log_readahead_size));
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
s = handler_pit.Iterate(reader, db_id);
|
||||
|
||||
assert(nullptr != has_missing_table_file);
|
||||
*has_missing_table_file = handler_pit.HasMissingFiles();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
const std::string& dbname,
|
||||
FileSystem* fs) {
|
||||
@@ -4771,9 +4974,10 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
Status s;
|
||||
{
|
||||
std::unique_ptr<FSSequentialFile> file;
|
||||
s = options.file_system->NewSequentialFile(
|
||||
const std::shared_ptr<FileSystem>& fs = options.env->GetFileSystem();
|
||||
s = fs->NewSequentialFile(
|
||||
dscname,
|
||||
options.file_system->OptimizeForManifestRead(file_options_), &file,
|
||||
fs->OptimizeForManifestRead(file_options_), &file,
|
||||
nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -4937,7 +5141,7 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
Version* v = new Version(cfd, this, file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
current_version_number_++);
|
||||
builder->SaveTo(v->storage_info());
|
||||
s = builder->SaveTo(v->storage_info());
|
||||
v->PrepareApply(*cfd->GetLatestMutableCFOptions(), false);
|
||||
|
||||
printf("--------------- Column family \"%s\" (ID %" PRIu32
|
||||
@@ -5003,15 +5207,15 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
VersionEdit edit_for_db_id;
|
||||
assert(!db_id_.empty());
|
||||
edit_for_db_id.SetDBId(db_id_);
|
||||
edit_for_db_id.SetStateUponManifestSwitch(true);
|
||||
std::string db_id_record;
|
||||
if (!edit_for_db_id.EncodeTo(&db_id_record)) {
|
||||
return Status::Corruption("Unable to Encode VersionEdit:" +
|
||||
edit_for_db_id.DebugString(true));
|
||||
}
|
||||
Status add_record = log->AddRecord(db_id_record);
|
||||
if (!add_record.ok()) {
|
||||
return add_record;
|
||||
IOStatus io_s = log->AddRecord(db_id_record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5029,7 +5233,6 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
edit.AddColumnFamily(cfd->GetName());
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
}
|
||||
edit.SetStateUponManifestSwitch(true);
|
||||
edit.SetComparatorName(
|
||||
cfd->internal_comparator().user_comparator()->Name());
|
||||
std::string record;
|
||||
@@ -5037,9 +5240,10 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
return Status::Corruption(
|
||||
"Unable to Encode VersionEdit:" + edit.DebugString(true));
|
||||
}
|
||||
Status s = log->AddRecord(record);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
IOStatus io_s = log->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5047,7 +5251,6 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
// Save files
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
edit.SetStateUponManifestSwitch(true);
|
||||
|
||||
for (int level = 0; level < cfd->NumberLevels(); level++) {
|
||||
for (const auto& f :
|
||||
@@ -5069,22 +5272,14 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
return Status::Corruption(
|
||||
"Unable to Encode VersionEdit:" + edit.DebugString(true));
|
||||
}
|
||||
Status s = log->AddRecord(record);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
IOStatus io_s = log->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
}
|
||||
}
|
||||
VersionEdit end_flag;
|
||||
end_flag.SetStateUponManifestSwitch(true);
|
||||
end_flag.SetManifestSwitched(true);
|
||||
std::string end_record;
|
||||
if (!end_flag.EncodeTo(&end_record)) {
|
||||
return Status::Corruption("Unable to Encode VersionEdit:" +
|
||||
end_flag.DebugString(true));
|
||||
}
|
||||
Status s_end_record = log->AddRecord(end_record);
|
||||
return s_end_record;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// TODO(aekmekji): in CompactionJob::GenSubcompactionBoundaries(), this
|
||||
@@ -5518,7 +5713,7 @@ void VersionSet::GetObsoleteFiles(std::vector<ObsoleteFileInfo>* files,
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionSet::CreateColumnFamily(
|
||||
const ColumnFamilyOptions& cf_options, VersionEdit* edit) {
|
||||
const ColumnFamilyOptions& cf_options, const VersionEdit* edit) {
|
||||
assert(edit->is_column_family_add_);
|
||||
|
||||
MutableCFOptions dummy_cf_options;
|
||||
@@ -5573,6 +5768,18 @@ uint64_t VersionSet::GetTotalSstFilesSize(Version* dummy_versions) {
|
||||
return total_files_size;
|
||||
}
|
||||
|
||||
Status VersionSet::VerifyFileMetadata(const std::string& fpath,
|
||||
const FileMetaData& meta) const {
|
||||
uint64_t fsize = 0;
|
||||
Status status = fs_->GetFileSize(fpath, IOOptions(), &fsize, nullptr);
|
||||
if (status.ok()) {
|
||||
if (fsize != meta.fd.GetFileSize()) {
|
||||
status = Status::Corruption("File size mismatch: " + fpath);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
ReactiveVersionSet::ReactiveVersionSet(const std::string& dbname,
|
||||
const ImmutableDBOptions* _db_options,
|
||||
const FileOptions& _file_options,
|
||||
@@ -5713,13 +5920,23 @@ Status ReactiveVersionSet::Recover(
|
||||
Version* v = new Version(cfd, this, file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
current_version_number_++);
|
||||
builder->SaveTo(v->storage_info());
|
||||
s = builder->SaveTo(v->storage_info());
|
||||
|
||||
// Install recovered version
|
||||
v->PrepareApply(*cfd->GetLatestMutableCFOptions(),
|
||||
!(db_options_->skip_stats_update_on_db_open));
|
||||
AppendVersion(cfd, v);
|
||||
if (s.ok()) {
|
||||
// Install recovered version
|
||||
v->PrepareApply(*cfd->GetLatestMutableCFOptions(),
|
||||
!(db_options_->skip_stats_update_on_db_open));
|
||||
AppendVersion(cfd, v);
|
||||
} else {
|
||||
ROCKS_LOG_ERROR(db_options_->info_log,
|
||||
"[%s]: inconsistent version: %s\n",
|
||||
cfd->GetName().c_str(), s.ToString().c_str());
|
||||
delete v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
next_file_number_.store(version_edit.next_file_number_ + 1);
|
||||
last_allocated_sequence_ = version_edit.last_sequence_;
|
||||
last_published_sequence_ = version_edit.last_sequence_;
|
||||
@@ -5796,6 +6013,8 @@ Status ReactiveVersionSet::ReadAndApply(
|
||||
s = ApplyOneVersionEditToBuilder(edit, cfds_changed, &temp_edit);
|
||||
if (s.ok()) {
|
||||
applied_edits++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5805,13 +6024,14 @@ Status ReactiveVersionSet::ReadAndApply(
|
||||
}
|
||||
// It's possible that:
|
||||
// 1) s.IsCorruption(), indicating the current MANIFEST is corrupted.
|
||||
// Or the version(s) rebuilt from tailing the MANIFEST is inconsistent.
|
||||
// 2) we have finished reading the current MANIFEST.
|
||||
// 3) we have encountered an IOError reading the current MANIFEST.
|
||||
// We need to look for the next MANIFEST and start from there. If we cannot
|
||||
// find the next MANIFEST, we should exit the loop.
|
||||
s = MaybeSwitchManifest(reader->GetReporter(), manifest_reader);
|
||||
Status tmp_s = MaybeSwitchManifest(reader->GetReporter(), manifest_reader);
|
||||
reader = manifest_reader->get();
|
||||
if (s.ok()) {
|
||||
if (tmp_s.ok()) {
|
||||
if (reader->file()->file_name() == old_manifest_path) {
|
||||
// Still processing the same MANIFEST, thus no need to continue this
|
||||
// loop since no record is available if we have reached here.
|
||||
@@ -5841,6 +6061,7 @@ Status ReactiveVersionSet::ReadAndApply(
|
||||
number_of_edits_to_skip_ += 2;
|
||||
}
|
||||
}
|
||||
s = tmp_s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5933,12 +6154,16 @@ Status ReactiveVersionSet::ApplyOneVersionEditToBuilder(
|
||||
auto version = new Version(cfd, this, file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
current_version_number_++);
|
||||
builder->SaveTo(version->storage_info());
|
||||
version->PrepareApply(*cfd->GetLatestMutableCFOptions(), true);
|
||||
AppendVersion(cfd, version);
|
||||
active_version_builders_.erase(builder_iter);
|
||||
if (cfds_changed->count(cfd) == 0) {
|
||||
cfds_changed->insert(cfd);
|
||||
s = builder->SaveTo(version->storage_info());
|
||||
if (s.ok()) {
|
||||
version->PrepareApply(*cfd->GetLatestMutableCFOptions(), true);
|
||||
AppendVersion(cfd, version);
|
||||
active_version_builders_.erase(builder_iter);
|
||||
if (cfds_changed->count(cfd) == 0) {
|
||||
cfds_changed->insert(cfd);
|
||||
}
|
||||
} else {
|
||||
delete version;
|
||||
}
|
||||
} else if (s.IsPathNotFound()) {
|
||||
s = Status::OK();
|
||||
@@ -5946,23 +6171,25 @@ Status ReactiveVersionSet::ApplyOneVersionEditToBuilder(
|
||||
// Some other error has occurred during LoadTableHandlers.
|
||||
}
|
||||
|
||||
if (version_edit->HasNextFile()) {
|
||||
next_file_number_.store(version_edit->next_file_number_ + 1);
|
||||
if (s.ok()) {
|
||||
if (version_edit->HasNextFile()) {
|
||||
next_file_number_.store(version_edit->next_file_number_ + 1);
|
||||
}
|
||||
if (version_edit->has_last_sequence_) {
|
||||
last_allocated_sequence_ = version_edit->last_sequence_;
|
||||
last_published_sequence_ = version_edit->last_sequence_;
|
||||
last_sequence_ = version_edit->last_sequence_;
|
||||
}
|
||||
if (version_edit->has_prev_log_number_) {
|
||||
prev_log_number_ = version_edit->prev_log_number_;
|
||||
MarkFileNumberUsed(version_edit->prev_log_number_);
|
||||
}
|
||||
if (version_edit->has_log_number_) {
|
||||
MarkFileNumberUsed(version_edit->log_number_);
|
||||
}
|
||||
column_family_set_->UpdateMaxColumnFamily(version_edit->max_column_family_);
|
||||
MarkMinLogNumberToKeep2PC(version_edit->min_log_number_to_keep_);
|
||||
}
|
||||
if (version_edit->has_last_sequence_) {
|
||||
last_allocated_sequence_ = version_edit->last_sequence_;
|
||||
last_published_sequence_ = version_edit->last_sequence_;
|
||||
last_sequence_ = version_edit->last_sequence_;
|
||||
}
|
||||
if (version_edit->has_prev_log_number_) {
|
||||
prev_log_number_ = version_edit->prev_log_number_;
|
||||
MarkFileNumberUsed(version_edit->prev_log_number_);
|
||||
}
|
||||
if (version_edit->has_log_number_) {
|
||||
MarkFileNumberUsed(version_edit->log_number_);
|
||||
}
|
||||
column_family_set_->UpdateMaxColumnFamily(version_edit->max_column_family_);
|
||||
MarkMinLogNumberToKeep2PC(version_edit->min_log_number_to_keep_);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+46
-11
@@ -11,8 +11,9 @@
|
||||
// newest version is called "current". Older versions may be kept
|
||||
// around to provide a consistent view to live iterators.
|
||||
//
|
||||
// Each Version keeps track of a set of Table files per level. The
|
||||
// entire set of versions is maintained in a VersionSet.
|
||||
// Each Version keeps track of a set of table files per level, as well as a
|
||||
// set of blob files. The entire set of versions is maintained in a
|
||||
// VersionSet.
|
||||
//
|
||||
// Version,VersionSet are thread-compatible, but require external
|
||||
// synchronization on all accesses.
|
||||
@@ -28,6 +29,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction.h"
|
||||
#include "db/compaction/compaction_picker.h"
|
||||
@@ -102,7 +104,7 @@ extern void DoGenerateLevelFilesBrief(LevelFilesBrief* file_level,
|
||||
|
||||
// Information of the storage associated with each Version, including number of
|
||||
// levels of LSM tree, files information at each level, files marked for
|
||||
// compaction, etc.
|
||||
// compaction, blob files, etc.
|
||||
class VersionStorageInfo {
|
||||
public:
|
||||
VersionStorageInfo(const InternalKeyComparator* internal_comparator,
|
||||
@@ -119,6 +121,8 @@ class VersionStorageInfo {
|
||||
|
||||
void AddFile(int level, FileMetaData* f, Logger* info_log = nullptr);
|
||||
|
||||
void AddBlobFile(std::shared_ptr<BlobFileMetaData> blob_file_meta);
|
||||
|
||||
void SetFinalized();
|
||||
|
||||
// Update num_non_empty_levels_.
|
||||
@@ -279,6 +283,10 @@ class VersionStorageInfo {
|
||||
return files_[level];
|
||||
}
|
||||
|
||||
// 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_; }
|
||||
|
||||
const ROCKSDB_NAMESPACE::LevelFilesBrief& LevelFilesBrief(int level) const {
|
||||
assert(level < static_cast<int>(level_files_brief_.size()));
|
||||
return level_files_brief_[level];
|
||||
@@ -453,6 +461,9 @@ class VersionStorageInfo {
|
||||
// in increasing order of keys
|
||||
std::vector<FileMetaData*>* files_;
|
||||
|
||||
// Map of blob files in version by number.
|
||||
BlobFiles blob_files_;
|
||||
|
||||
// Level that L0 data should be compacted to. All levels < base_level_ should
|
||||
// be empty. -1 if it is not level-compaction so it's not applicable.
|
||||
int base_level_;
|
||||
@@ -553,8 +564,8 @@ class VersionStorageInfo {
|
||||
};
|
||||
|
||||
using MultiGetRange = MultiGetContext::Range;
|
||||
// A column family's version consists of the SST files owned by the column
|
||||
// family at a certain point in time.
|
||||
// A column family's version consists of the table and blob files owned by
|
||||
// the column family at a certain point in time.
|
||||
class Version {
|
||||
public:
|
||||
// Append to *iters a sequence of iterators that will
|
||||
@@ -688,6 +699,8 @@ class Version {
|
||||
FileSystem* fs_;
|
||||
friend class ReactiveVersionSet;
|
||||
friend class VersionSet;
|
||||
friend class VersionEditHandler;
|
||||
friend class VersionEditHandlerPointInTime;
|
||||
|
||||
const InternalKeyComparator* internal_comparator() const {
|
||||
return storage_info_.internal_comparator_;
|
||||
@@ -696,10 +709,6 @@ class Version {
|
||||
return storage_info_.user_comparator_;
|
||||
}
|
||||
|
||||
bool PrefixMayMatch(const ReadOptions& read_options,
|
||||
InternalIterator* level_iter,
|
||||
const Slice& internal_prefix) const;
|
||||
|
||||
// Returns true if the filter blocks in the specified level will not be
|
||||
// checked during read operations. In certain cases (trivial move or preload),
|
||||
// the filter block may already be cached, but we still do not access it such
|
||||
@@ -876,6 +885,17 @@ class VersionSet {
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, std::string* db_id = nullptr);
|
||||
|
||||
Status TryRecover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only, std::string* db_id,
|
||||
bool* has_missing_table_file);
|
||||
|
||||
// Try to recover the version set to the most recent consistent state
|
||||
// recorded in the specified manifest.
|
||||
Status TryRecoverFromOneManifest(
|
||||
const std::string& manifest_path,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only, std::string* db_id, bool* has_missing_table_file);
|
||||
|
||||
// Reads a manifest file and returns a list of column families in
|
||||
// column_families.
|
||||
static Status ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
@@ -1055,10 +1075,18 @@ 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(); }
|
||||
|
||||
protected:
|
||||
struct ManifestWriter;
|
||||
|
||||
friend class Version;
|
||||
friend class VersionEditHandler;
|
||||
friend class VersionEditHandlerPointInTime;
|
||||
friend class DBImpl;
|
||||
friend class DBImplReadOnly;
|
||||
|
||||
@@ -1069,6 +1097,8 @@ class VersionSet {
|
||||
}
|
||||
};
|
||||
|
||||
void Reset();
|
||||
|
||||
// Returns approximated offset of a key in a file for a given version.
|
||||
uint64_t ApproximateOffsetOf(Version* v, const FdWithKeyRange& f,
|
||||
const Slice& key, TableReaderCaller caller);
|
||||
@@ -1091,7 +1121,7 @@ class VersionSet {
|
||||
void AppendVersion(ColumnFamilyData* column_family_data, Version* v);
|
||||
|
||||
ColumnFamilyData* CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
VersionEdit* edit);
|
||||
const VersionEdit* edit);
|
||||
|
||||
Status ReadAndRecover(
|
||||
log::Reader* reader, AtomicGroupReadBuffer* read_buffer,
|
||||
@@ -1115,8 +1145,10 @@ class VersionSet {
|
||||
const VersionEdit& from_edit,
|
||||
VersionEditParams* version_edit_params);
|
||||
|
||||
std::unique_ptr<ColumnFamilySet> column_family_set_;
|
||||
Status VerifyFileMetadata(const std::string& fpath,
|
||||
const FileMetaData& meta) const;
|
||||
|
||||
std::unique_ptr<ColumnFamilySet> column_family_set_;
|
||||
Env* const env_;
|
||||
FileSystem* const fs_;
|
||||
const std::string dbname_;
|
||||
@@ -1167,6 +1199,9 @@ class VersionSet {
|
||||
|
||||
BlockCacheTracer* const block_cache_tracer_;
|
||||
|
||||
// Store the IO status when Manifest is written
|
||||
IOStatus io_status_;
|
||||
|
||||
private:
|
||||
// REQUIRES db mutex at beginning. may release and re-acquire db mutex
|
||||
Status ProcessManifestWrites(std::deque<ManifestWriter>& writers,
|
||||
|
||||
+778
-24
@@ -10,6 +10,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "logging/logging.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "test_util/testharness.h"
|
||||
@@ -611,16 +612,37 @@ class VersionSetTestBase {
|
||||
const static std::string kColumnFamilyName3;
|
||||
int num_initial_edits_;
|
||||
|
||||
VersionSetTestBase()
|
||||
: env_(Env::Default()),
|
||||
fs_(std::make_shared<LegacyFileSystemWrapper>(env_)),
|
||||
dbname_(test::PerThreadDBPath("version_set_test")),
|
||||
db_options_(),
|
||||
explicit VersionSetTestBase(const std::string& name)
|
||||
: mem_env_(nullptr),
|
||||
env_(nullptr),
|
||||
env_guard_(),
|
||||
fs_(),
|
||||
dbname_(test::PerThreadDBPath(name)),
|
||||
options_(),
|
||||
db_options_(options_),
|
||||
cf_options_(options_),
|
||||
immutable_cf_options_(db_options_, cf_options_),
|
||||
mutable_cf_options_(cf_options_),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(std::make_shared<mock::MockTableFactory>()) {
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
Env* base_env = nullptr;
|
||||
if (test_env_uri) {
|
||||
Status s = Env::LoadEnv(test_env_uri, &base_env, &env_guard_);
|
||||
EXPECT_OK(s);
|
||||
EXPECT_NE(Env::Default(), base_env);
|
||||
} else {
|
||||
base_env = Env::Default();
|
||||
}
|
||||
EXPECT_NE(nullptr, base_env);
|
||||
if (getenv("MEM_ENV")) {
|
||||
mem_env_ = new MockEnv(base_env);
|
||||
}
|
||||
env_ = mem_env_ ? mem_env_ : base_env;
|
||||
|
||||
fs_ = std::make_shared<LegacyFileSystemWrapper>(env_);
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
|
||||
db_options_.env = env_;
|
||||
@@ -628,23 +650,38 @@ class VersionSetTestBase {
|
||||
versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_,
|
||||
/*block_cache_tracer=*/nullptr)),
|
||||
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_);
|
||||
/*block_cache_tracer=*/nullptr));
|
||||
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_);
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
|
||||
void PrepareManifest(std::vector<ColumnFamilyDescriptor>* column_families,
|
||||
SequenceNumber* last_seqno,
|
||||
std::unique_ptr<log::Writer>* log_writer) {
|
||||
virtual ~VersionSetTestBase() {
|
||||
if (getenv("KEEP_DB")) {
|
||||
fprintf(stdout, "DB is still at %s\n", dbname_.c_str());
|
||||
} else {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
EXPECT_OK(DestroyDB(dbname_, options));
|
||||
}
|
||||
if (mem_env_) {
|
||||
delete mem_env_;
|
||||
mem_env_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void PrepareManifest(
|
||||
std::vector<ColumnFamilyDescriptor>* column_families,
|
||||
SequenceNumber* last_seqno, std::unique_ptr<log::Writer>* log_writer) {
|
||||
assert(column_families != nullptr);
|
||||
assert(last_seqno != nullptr);
|
||||
assert(log_writer != nullptr);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
DBImpl* impl = new DBImpl(DBOptions(), dbname_);
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(DBOptions(), dbname_));
|
||||
std::string db_id;
|
||||
impl->GetDbIdentityFromIdentityFile(&db_id);
|
||||
new_db.SetDBId(db_id);
|
||||
@@ -707,7 +744,7 @@ class VersionSetTestBase {
|
||||
PrepareManifest(&column_families, &last_seqno, &log_writer);
|
||||
log_writer.reset();
|
||||
// Make "CURRENT" file point to the new manifest file.
|
||||
Status s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
@@ -715,12 +752,25 @@ class VersionSetTestBase {
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies());
|
||||
}
|
||||
|
||||
void VerifyManifest(std::string* manifest_path) const {
|
||||
assert(manifest_path != nullptr);
|
||||
uint64_t manifest_file_number = 0;
|
||||
Status s = versions_->GetCurrentManifestPath(
|
||||
dbname_, fs_.get(), manifest_path, &manifest_file_number);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(1, manifest_file_number);
|
||||
}
|
||||
|
||||
MockEnv* mem_env_;
|
||||
Env* env_;
|
||||
std::shared_ptr<Env> env_guard_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
const std::string dbname_;
|
||||
EnvOptions env_options_;
|
||||
Options options_;
|
||||
ImmutableDBOptions db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
ImmutableCFOptions immutable_cf_options_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
WriteController write_controller_;
|
||||
@@ -738,7 +788,7 @@ const std::string VersionSetTestBase::kColumnFamilyName3 = "charles";
|
||||
|
||||
class VersionSetTest : public VersionSetTestBase, public testing::Test {
|
||||
public:
|
||||
VersionSetTest() : VersionSetTestBase() {}
|
||||
VersionSetTest() : VersionSetTestBase("version_set_test") {}
|
||||
};
|
||||
|
||||
TEST_F(VersionSetTest, SameColumnFamilyGroupCommit) {
|
||||
@@ -780,7 +830,8 @@ TEST_F(VersionSetTest, SameColumnFamilyGroupCommit) {
|
||||
class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
public testing::Test {
|
||||
public:
|
||||
VersionSetAtomicGroupTest() : VersionSetTestBase() {}
|
||||
VersionSetAtomicGroupTest()
|
||||
: VersionSetTestBase("version_set_atomic_group_test") {}
|
||||
|
||||
void SetUp() override {
|
||||
PrepareManifest(&column_families_, &last_seqno_, &log_writer_);
|
||||
@@ -796,7 +847,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(env_, dbname_, 1, nullptr));
|
||||
ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr));
|
||||
}
|
||||
|
||||
void SetupIncompleteTrailingAtomicGroup(int atomic_group_size) {
|
||||
@@ -808,7 +859,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(env_, dbname_, 1, nullptr));
|
||||
ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr));
|
||||
}
|
||||
|
||||
void SetupCorruptedAtomicGroup(int atomic_group_size) {
|
||||
@@ -822,7 +873,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(env_, dbname_, 1, nullptr));
|
||||
ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr));
|
||||
}
|
||||
|
||||
void SetupIncorrectAtomicGroup(int atomic_group_size) {
|
||||
@@ -838,7 +889,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(env_, dbname_, 1, nullptr));
|
||||
ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr));
|
||||
}
|
||||
|
||||
void SetupTestSyncPoints() {
|
||||
@@ -1104,7 +1155,7 @@ TEST_F(VersionSetAtomicGroupTest,
|
||||
// Write the corrupted edits.
|
||||
AddNewEditsToLog(kAtomicGroupSize);
|
||||
mu.Lock();
|
||||
EXPECT_OK(
|
||||
EXPECT_NOK(
|
||||
reactive_versions_->ReadAndApply(&mu, &manifest_reader, &cfds_changed));
|
||||
mu.Unlock();
|
||||
EXPECT_EQ(edits_[kAtomicGroupSize / 2].DebugString(),
|
||||
@@ -1154,7 +1205,7 @@ TEST_F(VersionSetAtomicGroupTest,
|
||||
&manifest_reader_status));
|
||||
AddNewEditsToLog(kAtomicGroupSize);
|
||||
mu.Lock();
|
||||
EXPECT_OK(
|
||||
EXPECT_NOK(
|
||||
reactive_versions_->ReadAndApply(&mu, &manifest_reader, &cfds_changed));
|
||||
mu.Unlock();
|
||||
EXPECT_EQ(edits_[1].DebugString(),
|
||||
@@ -1164,7 +1215,8 @@ TEST_F(VersionSetAtomicGroupTest,
|
||||
class VersionSetTestDropOneCF : public VersionSetTestBase,
|
||||
public testing::TestWithParam<std::string> {
|
||||
public:
|
||||
VersionSetTestDropOneCF() : VersionSetTestBase() {}
|
||||
VersionSetTestDropOneCF()
|
||||
: VersionSetTestBase("version_set_test_drop_one_cf") {}
|
||||
};
|
||||
|
||||
// This test simulates the following execution sequence
|
||||
@@ -1189,7 +1241,7 @@ TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) {
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
PrepareManifest(&column_families, &last_seqno, &log_writer);
|
||||
Status s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false /* read_only */));
|
||||
@@ -1279,6 +1331,708 @@ INSTANTIATE_TEST_CASE_P(
|
||||
testing::Values(VersionSetTestBase::kColumnFamilyName1,
|
||||
VersionSetTestBase::kColumnFamilyName2,
|
||||
VersionSetTestBase::kColumnFamilyName3));
|
||||
|
||||
class EmptyDefaultCfNewManifest : public VersionSetTestBase,
|
||||
public testing::Test {
|
||||
public:
|
||||
EmptyDefaultCfNewManifest() : VersionSetTestBase("version_set_new_db_test") {}
|
||||
// Emulate DBImpl::NewDB()
|
||||
void PrepareManifest(std::vector<ColumnFamilyDescriptor>* /*column_families*/,
|
||||
SequenceNumber* /*last_seqno*/,
|
||||
std::unique_ptr<log::Writer>* log_writer) override {
|
||||
assert(log_writer != nullptr);
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
std::unique_ptr<WritableFile> file;
|
||||
const std::string manifest_path = DescriptorFileName(dbname_, 1);
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest_path, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(file)),
|
||||
manifest_path, env_options_));
|
||||
log_writer->reset(new log::Writer(std::move(file_writer), 0, true));
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_db.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
// Create new column family
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(VersionSetTestBase::kColumnFamilyName1);
|
||||
new_cf.SetColumnFamily(1);
|
||||
new_cf.SetLastSequence(2);
|
||||
new_cf.SetNextFile(2);
|
||||
record.clear();
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool write_dbid_to_manifest_ = false;
|
||||
std::unique_ptr<log::Writer> log_writer_;
|
||||
};
|
||||
|
||||
// Create db, create column family. Cf creation will switch to a new MANIFEST.
|
||||
// Then reopen db, trying to recover.
|
||||
TEST_F(EmptyDefaultCfNewManifest, Recover) {
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s =
|
||||
SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options_);
|
||||
column_families.emplace_back(VersionSetTestBase::kColumnFamilyName1,
|
||||
cf_options_);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families, false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
}
|
||||
|
||||
class VersionSetTestEmptyDb
|
||||
: public VersionSetTestBase,
|
||||
public testing::TestWithParam<
|
||||
std::tuple<bool, bool, std::vector<std::string>>> {
|
||||
public:
|
||||
static const std::string kUnknownColumnFamilyName;
|
||||
VersionSetTestEmptyDb() : VersionSetTestBase("version_set_test_empty_db") {}
|
||||
|
||||
protected:
|
||||
void PrepareManifest(std::vector<ColumnFamilyDescriptor>* /*column_families*/,
|
||||
SequenceNumber* /*last_seqno*/,
|
||||
std::unique_ptr<log::Writer>* log_writer) override {
|
||||
assert(nullptr != log_writer);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(DBOptions(), dbname_));
|
||||
std::string db_id;
|
||||
impl->GetDbIdentityFromIdentityFile(&db_id);
|
||||
new_db.SetDBId(db_id);
|
||||
}
|
||||
const std::string manifest_path = DescriptorFileName(dbname_, 1);
|
||||
std::unique_ptr<WritableFile> file;
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest_path, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(file)),
|
||||
manifest_path, env_options_));
|
||||
{
|
||||
log_writer->reset(new log::Writer(std::move(file_writer), 0, false));
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<log::Writer> log_writer_;
|
||||
};
|
||||
|
||||
const std::string VersionSetTestEmptyDb::kUnknownColumnFamilyName = "unknown";
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s =
|
||||
SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string> cf_names = std::get<2>(GetParam());
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Only a subset of column families in the MANIFEST.
|
||||
VersionEdit new_cf1;
|
||||
new_cf1.AddColumnFamily(VersionSetTestBase::kColumnFamilyName1);
|
||||
new_cf1.SetColumnFamily(1);
|
||||
Status s;
|
||||
{
|
||||
std::string record;
|
||||
new_cf1.EncodeTo(&record);
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string>& cf_names = std::get<2>(GetParam());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
const std::vector<std::string> all_cf_names = {
|
||||
kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
|
||||
kColumnFamilyName3};
|
||||
uint32_t cf_id = 1;
|
||||
Status s;
|
||||
for (size_t i = 1; i != all_cf_names.size(); ++i) {
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(all_cf_names[i]);
|
||||
new_cf.SetColumnFamily(cf_id++);
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string>& cf_names = std::get<2>(GetParam());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
const std::vector<std::string> all_cf_names = {
|
||||
kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
|
||||
kColumnFamilyName3};
|
||||
uint32_t cf_id = 1;
|
||||
Status s;
|
||||
for (size_t i = 1; i != all_cf_names.size(); ++i) {
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(all_cf_names[i]);
|
||||
new_cf.SetColumnFamily(cf_id++);
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
VersionEdit tmp_edit;
|
||||
tmp_edit.SetColumnFamily(4);
|
||||
tmp_edit.SetLogNumber(0);
|
||||
tmp_edit.SetNextFile(2);
|
||||
tmp_edit.SetLastSequence(0);
|
||||
std::string record;
|
||||
ASSERT_TRUE(tmp_edit.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string>& cf_names = std::get<2>(GetParam());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
const std::vector<std::string> all_cf_names = {
|
||||
kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
|
||||
kColumnFamilyName3};
|
||||
uint32_t cf_id = 1;
|
||||
Status s;
|
||||
for (size_t i = 1; i != all_cf_names.size(); ++i) {
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(all_cf_names[i]);
|
||||
new_cf.SetColumnFamily(cf_id++);
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
VersionEdit tmp_edit;
|
||||
tmp_edit.SetLogNumber(0);
|
||||
tmp_edit.SetNextFile(2);
|
||||
tmp_edit.SetLastSequence(0);
|
||||
std::string record;
|
||||
ASSERT_TRUE(tmp_edit.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string>& cf_names = std::get<2>(GetParam());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else if (read_only) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
} else if (cf_names.size() == all_cf_names.size()) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
} else if (cf_names.size() < all_cf_names.size()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetColumnFamily(
|
||||
kUnknownColumnFamilyName);
|
||||
ASSERT_EQ(nullptr, cfd);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
BestEffortRecovery, VersionSetTestEmptyDb,
|
||||
testing::Combine(
|
||||
/*write_dbid_to_manifest=*/testing::Bool(),
|
||||
/*read_only=*/testing::Bool(),
|
||||
/*cf_names=*/
|
||||
testing::Values(
|
||||
std::vector<std::string>(),
|
||||
std::vector<std::string>({kDefaultColumnFamilyName}),
|
||||
std::vector<std::string>({VersionSetTestBase::kColumnFamilyName1,
|
||||
VersionSetTestBase::kColumnFamilyName2,
|
||||
VersionSetTestBase::kColumnFamilyName3}),
|
||||
std::vector<std::string>({kDefaultColumnFamilyName,
|
||||
VersionSetTestBase::kColumnFamilyName1}),
|
||||
std::vector<std::string>({kDefaultColumnFamilyName,
|
||||
VersionSetTestBase::kColumnFamilyName1,
|
||||
VersionSetTestBase::kColumnFamilyName2,
|
||||
VersionSetTestBase::kColumnFamilyName3}),
|
||||
std::vector<std::string>(
|
||||
{kDefaultColumnFamilyName,
|
||||
VersionSetTestBase::kColumnFamilyName1,
|
||||
VersionSetTestBase::kColumnFamilyName2,
|
||||
VersionSetTestBase::kColumnFamilyName3,
|
||||
VersionSetTestEmptyDb::kUnknownColumnFamilyName}))));
|
||||
|
||||
class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
public testing::Test {
|
||||
public:
|
||||
VersionSetTestMissingFiles()
|
||||
: VersionSetTestBase("version_set_test_missing_files"),
|
||||
block_based_table_options_(),
|
||||
table_factory_(std::make_shared<BlockBasedTableFactory>(
|
||||
block_based_table_options_)),
|
||||
internal_comparator_(
|
||||
std::make_shared<InternalKeyComparator>(options_.comparator)) {}
|
||||
|
||||
protected:
|
||||
void PrepareManifest(std::vector<ColumnFamilyDescriptor>* column_families,
|
||||
SequenceNumber* last_seqno,
|
||||
std::unique_ptr<log::Writer>* log_writer) override {
|
||||
assert(column_families != nullptr);
|
||||
assert(last_seqno != nullptr);
|
||||
assert(log_writer != nullptr);
|
||||
const std::string manifest = DescriptorFileName(dbname_, 1);
|
||||
std::unique_ptr<WritableFile> file;
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
NewLegacyWritableFileWrapper(std::move(file)), manifest, env_options_));
|
||||
log_writer->reset(new log::Writer(std::move(file_writer), 0, false));
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(DBOptions(), dbname_));
|
||||
std::string db_id;
|
||||
impl->GetDbIdentityFromIdentityFile(&db_id);
|
||||
new_db.SetDBId(db_id);
|
||||
}
|
||||
{
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_db.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
const std::vector<std::string> cf_names = {
|
||||
kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
|
||||
kColumnFamilyName3};
|
||||
uint32_t cf_id = 1; // default cf id is 0
|
||||
cf_options_.table_factory = table_factory_;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families->emplace_back(cf_name, cf_options_);
|
||||
if (cf_name == kDefaultColumnFamilyName) {
|
||||
continue;
|
||||
}
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(cf_name);
|
||||
new_cf.SetColumnFamily(cf_id);
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
|
||||
VersionEdit cf_files;
|
||||
cf_files.SetColumnFamily(cf_id);
|
||||
cf_files.SetLogNumber(0);
|
||||
record.clear();
|
||||
ASSERT_TRUE(cf_files.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
++cf_id;
|
||||
}
|
||||
SequenceNumber seq = 2;
|
||||
{
|
||||
VersionEdit edit;
|
||||
edit.SetNextFile(7);
|
||||
edit.SetLastSequence(seq);
|
||||
std::string record;
|
||||
ASSERT_TRUE(edit.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
*last_seqno = seq + 1;
|
||||
}
|
||||
|
||||
struct SstInfo {
|
||||
uint64_t file_number;
|
||||
std::string column_family;
|
||||
std::string key; // the only key
|
||||
int level = 0;
|
||||
SstInfo(uint64_t file_num, const std::string& cf_name,
|
||||
const std::string& _key)
|
||||
: SstInfo(file_num, cf_name, _key, 0) {}
|
||||
SstInfo(uint64_t file_num, const std::string& cf_name,
|
||||
const std::string& _key, int lvl)
|
||||
: file_number(file_num),
|
||||
column_family(cf_name),
|
||||
key(_key),
|
||||
level(lvl) {}
|
||||
};
|
||||
|
||||
// Create dummy sst, return their metadata. Note that only file name and size
|
||||
// are used.
|
||||
void CreateDummyTableFiles(const std::vector<SstInfo>& file_infos,
|
||||
std::vector<FileMetaData>* file_metas) {
|
||||
assert(file_metas != nullptr);
|
||||
for (const auto& info : file_infos) {
|
||||
uint64_t file_num = info.file_number;
|
||||
std::string fname = MakeTableFileName(dbname_, file_num);
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
Status s = fs_->NewWritableFile(fname, FileOptions(), &file, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> fwriter(
|
||||
new WritableFileWriter(std::move(file), fname, FileOptions(), env_));
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
|
||||
int_tbl_prop_collector_factories;
|
||||
|
||||
std::unique_ptr<TableBuilder> builder(table_factory_->NewTableBuilder(
|
||||
TableBuilderOptions(
|
||||
immutable_cf_options_, mutable_cf_options_, *internal_comparator_,
|
||||
&int_tbl_prop_collector_factories, kNoCompression,
|
||||
/*_sample_for_compression=*/0, CompressionOptions(),
|
||||
/*_skip_filters=*/false, info.column_family, info.level),
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
|
||||
fwriter.get()));
|
||||
InternalKey ikey(info.key, 0, ValueType::kTypeValue);
|
||||
builder->Add(ikey.Encode(), "value");
|
||||
ASSERT_OK(builder->Finish());
|
||||
fwriter->Flush();
|
||||
uint64_t file_size = 0;
|
||||
s = fs_->GetFileSize(fname, IOOptions(), &file_size, nullptr);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(0, file_size);
|
||||
FileMetaData meta;
|
||||
meta = FileMetaData(file_num, /*file_path_id=*/0, file_size, ikey, ikey,
|
||||
0, 0, false, 0, 0, 0, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
file_metas->emplace_back(meta);
|
||||
}
|
||||
}
|
||||
|
||||
// This method updates last_sequence_.
|
||||
void WriteFileAdditionAndDeletionToManifest(
|
||||
uint32_t cf, const std::vector<std::pair<int, FileMetaData>>& added_files,
|
||||
const std::vector<std::pair<int, uint64_t>>& deleted_files) {
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cf);
|
||||
for (const auto& elem : added_files) {
|
||||
int level = elem.first;
|
||||
edit.AddFile(level, elem.second);
|
||||
}
|
||||
for (const auto& elem : deleted_files) {
|
||||
int level = elem.first;
|
||||
edit.DeleteFile(level, elem.second);
|
||||
}
|
||||
edit.SetLastSequence(last_seqno_);
|
||||
++last_seqno_;
|
||||
assert(log_writer_.get() != nullptr);
|
||||
std::string record;
|
||||
ASSERT_TRUE(edit.EncodeTo(&record));
|
||||
Status s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
BlockBasedTableOptions block_based_table_options_;
|
||||
std::shared_ptr<TableFactory> table_factory_;
|
||||
std::shared_ptr<InternalKeyComparator> internal_comparator_;
|
||||
std::vector<ColumnFamilyDescriptor> column_families_;
|
||||
SequenceNumber last_seqno_;
|
||||
std::unique_ptr<log::Writer> log_writer_;
|
||||
};
|
||||
|
||||
TEST_F(VersionSetTestMissingFiles, ManifestFarBehindSst) {
|
||||
std::vector<SstInfo> existing_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a"),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "b"),
|
||||
SstInfo(103, kDefaultColumnFamilyName, "c"),
|
||||
SstInfo(107, kDefaultColumnFamilyName, "d"),
|
||||
SstInfo(110, kDefaultColumnFamilyName, "e")};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(existing_files, &file_metas);
|
||||
|
||||
PrepareManifest(&column_families_, &last_seqno_, &log_writer_);
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (uint64_t file_num = 10; file_num < 15; ++file_num) {
|
||||
std::string smallest_ukey = "a";
|
||||
std::string largest_ukey = "b";
|
||||
InternalKey smallest_ikey(smallest_ukey, 1, ValueType::kTypeValue);
|
||||
InternalKey largest_ikey(largest_ukey, 1, ValueType::kTypeValue);
|
||||
FileMetaData meta =
|
||||
FileMetaData(file_num, /*file_path_id=*/0, /*file_size=*/12,
|
||||
smallest_ikey, largest_ikey, 0, 0, false, 0, 0, 0,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
added_files.emplace_back(0, meta);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
std::vector<std::pair<int, uint64_t>> deleted_files;
|
||||
deleted_files.emplace_back(0, 10);
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
const std::vector<FileMetaData*>& files = vstorage->LevelFiles(0);
|
||||
ASSERT_TRUE(files.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VersionSetTestMissingFiles, ManifestAheadofSst) {
|
||||
std::vector<SstInfo> existing_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a"),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "b"),
|
||||
SstInfo(103, kDefaultColumnFamilyName, "c"),
|
||||
SstInfo(107, kDefaultColumnFamilyName, "d"),
|
||||
SstInfo(110, kDefaultColumnFamilyName, "e")};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(existing_files, &file_metas);
|
||||
|
||||
PrepareManifest(&column_families_, &last_seqno_, &log_writer_);
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (size_t i = 3; i != 5; ++i) {
|
||||
added_files.emplace_back(0, file_metas[i]);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
|
||||
added_files.clear();
|
||||
for (uint64_t file_num = 120; file_num < 130; ++file_num) {
|
||||
std::string smallest_ukey = "a";
|
||||
std::string largest_ukey = "b";
|
||||
InternalKey smallest_ikey(smallest_ukey, 1, ValueType::kTypeValue);
|
||||
InternalKey largest_ikey(largest_ukey, 1, ValueType::kTypeValue);
|
||||
FileMetaData meta =
|
||||
FileMetaData(file_num, /*file_path_id=*/0, /*file_size=*/12,
|
||||
smallest_ikey, largest_ikey, 0, 0, false, 0, 0, 0,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
added_files.emplace_back(0, meta);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
const std::vector<FileMetaData*>& files = vstorage->LevelFiles(0);
|
||||
if (cfd->GetName() == kDefaultColumnFamilyName) {
|
||||
ASSERT_EQ(2, files.size());
|
||||
for (const auto* fmeta : files) {
|
||||
if (fmeta->fd.GetNumber() != 107 && fmeta->fd.GetNumber() != 110) {
|
||||
ASSERT_FALSE(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ASSERT_TRUE(files.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VersionSetTestMissingFiles, NoFileMissing) {
|
||||
std::vector<SstInfo> existing_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a"),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "b"),
|
||||
SstInfo(103, kDefaultColumnFamilyName, "c"),
|
||||
SstInfo(107, kDefaultColumnFamilyName, "d"),
|
||||
SstInfo(110, kDefaultColumnFamilyName, "e")};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(existing_files, &file_metas);
|
||||
|
||||
PrepareManifest(&column_families_, &last_seqno_, &log_writer_);
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (const auto& meta : file_metas) {
|
||||
added_files.emplace_back(0, meta);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
std::vector<std::pair<int, uint64_t>> deleted_files;
|
||||
deleted_files.emplace_back(/*level=*/0, 100);
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
const std::vector<FileMetaData*>& files = vstorage->LevelFiles(0);
|
||||
if (cfd->GetName() == kDefaultColumnFamilyName) {
|
||||
ASSERT_EQ(existing_files.size() - deleted_files.size(), files.size());
|
||||
bool has_deleted_file = false;
|
||||
for (const auto* fmeta : files) {
|
||||
if (fmeta->fd.GetNumber() == 100) {
|
||||
has_deleted_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_FALSE(has_deleted_file);
|
||||
} else {
|
||||
ASSERT_TRUE(files.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# defs.bzl - Definitions for Facebook-specific buck build integration
|
||||
# in TARGETS
|
||||
|
||||
load("@fbcode_macros//build_defs:coverage.bzl", "coverage")
|
||||
load("@fbcode_macros//build_defs:cpp_binary.bzl", "cpp_binary")
|
||||
load("@fbcode_macros//build_defs:custom_unittest.bzl", "custom_unittest")
|
||||
|
||||
@@ -37,9 +38,19 @@ def test_binary(
|
||||
|
||||
binary_path = "$(location :{})".format(test_bin)
|
||||
|
||||
base_path = native.package_name()
|
||||
tags = []
|
||||
if coverage.is_coverage_enabled(base_path):
|
||||
# This tag instructs testpilot to use
|
||||
# the lower-memory coverage runner
|
||||
# (e.g. it tells testpilot that the binary
|
||||
# is actually instrumented with coverage info)
|
||||
tags = ["coverage"]
|
||||
|
||||
custom_unittest(
|
||||
name = test_name,
|
||||
command = [TEST_RUNNER, binary_path],
|
||||
type = ttype,
|
||||
env = {"BUCK_BASE_BINARY": binary_path},
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
Vendored
+55
-72
@@ -20,22 +20,6 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// Options::env only, whereas in the latter case, the user will specify
|
||||
// Options::env and Options::file_system.
|
||||
|
||||
inline IOStatus status_to_io_status(Status&& status) {
|
||||
if (status.ok()) {
|
||||
// Fast path
|
||||
return IOStatus::OK();
|
||||
} else {
|
||||
const char* state = status.getState();
|
||||
if (state) {
|
||||
return IOStatus(status.code(), status.subcode(),
|
||||
Slice(state, strlen(status.getState()) + 1),
|
||||
Slice());
|
||||
} else {
|
||||
return IOStatus(status.code(), status.subcode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CompositeSequentialFileWrapper : public SequentialFile {
|
||||
public:
|
||||
explicit CompositeSequentialFileWrapper(
|
||||
@@ -291,20 +275,18 @@ class CompositeEnvWrapper : public Env {
|
||||
public:
|
||||
// Initialize a CompositeEnvWrapper that delegates all thread/time related
|
||||
// calls to env, and all file operations to fs
|
||||
explicit CompositeEnvWrapper(Env* env, FileSystem* fs)
|
||||
: env_target_(env), fs_env_target_(fs) {}
|
||||
explicit CompositeEnvWrapper(Env* env, std::shared_ptr<FileSystem> fs)
|
||||
: Env(fs), env_target_(env) {}
|
||||
~CompositeEnvWrapper() {}
|
||||
|
||||
// Return the target to which this Env forwards all calls
|
||||
Env* env_target() const { return env_target_; }
|
||||
|
||||
FileSystem* fs_env_target() const { return fs_env_target_; }
|
||||
|
||||
Status RegisterDbPaths(const std::vector<std::string>& paths) override {
|
||||
return fs_env_target_->RegisterDbPaths(paths);
|
||||
return file_system_->RegisterDbPaths(paths);
|
||||
}
|
||||
Status UnregisterDbPaths(const std::vector<std::string>& paths) override {
|
||||
return fs_env_target_->UnregisterDbPaths(paths);
|
||||
return file_system_->UnregisterDbPaths(paths);
|
||||
}
|
||||
|
||||
// The following text is boilerplate that forwards all methods to target()
|
||||
@@ -315,7 +297,7 @@ class CompositeEnvWrapper : public Env {
|
||||
std::unique_ptr<FSSequentialFile> file;
|
||||
Status status;
|
||||
status =
|
||||
fs_env_target_->NewSequentialFile(f, FileOptions(options), &file, &dbg);
|
||||
file_system_->NewSequentialFile(f, FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
r->reset(new CompositeSequentialFileWrapper(file));
|
||||
}
|
||||
@@ -327,8 +309,8 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
Status status;
|
||||
status = fs_env_target_->NewRandomAccessFile(f, FileOptions(options), &file,
|
||||
&dbg);
|
||||
status =
|
||||
file_system_->NewRandomAccessFile(f, FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
r->reset(new CompositeRandomAccessFileWrapper(file));
|
||||
}
|
||||
@@ -340,7 +322,7 @@ class CompositeEnvWrapper : public Env {
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
Status status;
|
||||
status =
|
||||
fs_env_target_->NewWritableFile(f, FileOptions(options), &file, &dbg);
|
||||
file_system_->NewWritableFile(f, FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
r->reset(new CompositeWritableFileWrapper(file));
|
||||
}
|
||||
@@ -352,8 +334,8 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
Status status;
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
status = fs_env_target_->ReopenWritableFile(fname, FileOptions(options),
|
||||
&file, &dbg);
|
||||
status = file_system_->ReopenWritableFile(fname, FileOptions(options),
|
||||
&file, &dbg);
|
||||
if (status.ok()) {
|
||||
result->reset(new CompositeWritableFileWrapper(file));
|
||||
}
|
||||
@@ -366,8 +348,8 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
Status status;
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
status = fs_env_target_->ReuseWritableFile(
|
||||
fname, old_fname, FileOptions(options), &file, &dbg);
|
||||
status = file_system_->ReuseWritableFile(fname, old_fname,
|
||||
FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
r->reset(new CompositeWritableFileWrapper(file));
|
||||
}
|
||||
@@ -379,8 +361,8 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
std::unique_ptr<FSRandomRWFile> file;
|
||||
Status status;
|
||||
status = fs_env_target_->NewRandomRWFile(fname, FileOptions(options), &file,
|
||||
&dbg);
|
||||
status =
|
||||
file_system_->NewRandomRWFile(fname, FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
result->reset(new CompositeRandomRWFileWrapper(file));
|
||||
}
|
||||
@@ -389,7 +371,7 @@ class CompositeEnvWrapper : public Env {
|
||||
Status NewMemoryMappedFileBuffer(
|
||||
const std::string& fname,
|
||||
std::unique_ptr<MemoryMappedFileBuffer>* result) override {
|
||||
return fs_env_target_->NewMemoryMappedFileBuffer(fname, result);
|
||||
return file_system_->NewMemoryMappedFileBuffer(fname, result);
|
||||
}
|
||||
Status NewDirectory(const std::string& name,
|
||||
std::unique_ptr<Directory>* result) override {
|
||||
@@ -397,7 +379,7 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
std::unique_ptr<FSDirectory> dir;
|
||||
Status status;
|
||||
status = fs_env_target_->NewDirectory(name, io_opts, &dir, &dbg);
|
||||
status = file_system_->NewDirectory(name, io_opts, &dir, &dbg);
|
||||
if (status.ok()) {
|
||||
result->reset(new CompositeDirectoryWrapper(dir));
|
||||
}
|
||||
@@ -406,102 +388,108 @@ class CompositeEnvWrapper : public Env {
|
||||
Status FileExists(const std::string& f) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->FileExists(f, io_opts, &dbg);
|
||||
return file_system_->FileExists(f, io_opts, &dbg);
|
||||
}
|
||||
Status GetChildren(const std::string& dir,
|
||||
std::vector<std::string>* r) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetChildren(dir, io_opts, r, &dbg);
|
||||
return file_system_->GetChildren(dir, io_opts, r, &dbg);
|
||||
}
|
||||
Status GetChildrenFileAttributes(
|
||||
const std::string& dir, std::vector<FileAttributes>* result) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetChildrenFileAttributes(dir, io_opts, result,
|
||||
&dbg);
|
||||
return file_system_->GetChildrenFileAttributes(dir, io_opts, result, &dbg);
|
||||
}
|
||||
Status DeleteFile(const std::string& f) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->DeleteFile(f, io_opts, &dbg);
|
||||
return file_system_->DeleteFile(f, io_opts, &dbg);
|
||||
}
|
||||
Status Truncate(const std::string& fname, size_t size) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->Truncate(fname, size, io_opts, &dbg);
|
||||
return file_system_->Truncate(fname, size, io_opts, &dbg);
|
||||
}
|
||||
Status CreateDir(const std::string& d) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->CreateDir(d, io_opts, &dbg);
|
||||
return file_system_->CreateDir(d, io_opts, &dbg);
|
||||
}
|
||||
Status CreateDirIfMissing(const std::string& d) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->CreateDirIfMissing(d, io_opts, &dbg);
|
||||
return file_system_->CreateDirIfMissing(d, io_opts, &dbg);
|
||||
}
|
||||
Status DeleteDir(const std::string& d) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->DeleteDir(d, io_opts, &dbg);
|
||||
return file_system_->DeleteDir(d, io_opts, &dbg);
|
||||
}
|
||||
Status GetFileSize(const std::string& f, uint64_t* s) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetFileSize(f, io_opts, s, &dbg);
|
||||
return file_system_->GetFileSize(f, io_opts, s, &dbg);
|
||||
}
|
||||
|
||||
Status GetFileModificationTime(const std::string& fname,
|
||||
uint64_t* file_mtime) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetFileModificationTime(fname, io_opts, file_mtime,
|
||||
&dbg);
|
||||
return file_system_->GetFileModificationTime(fname, io_opts, file_mtime,
|
||||
&dbg);
|
||||
}
|
||||
|
||||
Status RenameFile(const std::string& s, const std::string& t) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->RenameFile(s, t, io_opts, &dbg);
|
||||
return file_system_->RenameFile(s, t, io_opts, &dbg);
|
||||
}
|
||||
|
||||
Status LinkFile(const std::string& s, const std::string& t) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->LinkFile(s, t, io_opts, &dbg);
|
||||
return file_system_->LinkFile(s, t, io_opts, &dbg);
|
||||
}
|
||||
|
||||
Status NumFileLinks(const std::string& fname, uint64_t* count) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->NumFileLinks(fname, io_opts, count, &dbg);
|
||||
return file_system_->NumFileLinks(fname, io_opts, count, &dbg);
|
||||
}
|
||||
|
||||
Status AreFilesSame(const std::string& first, const std::string& second,
|
||||
bool* res) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->AreFilesSame(first, second, io_opts, res, &dbg);
|
||||
return file_system_->AreFilesSame(first, second, io_opts, res, &dbg);
|
||||
}
|
||||
|
||||
Status LockFile(const std::string& f, FileLock** l) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->LockFile(f, io_opts, l, &dbg);
|
||||
return file_system_->LockFile(f, io_opts, l, &dbg);
|
||||
}
|
||||
|
||||
Status UnlockFile(FileLock* l) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->UnlockFile(l, io_opts, &dbg);
|
||||
return file_system_->UnlockFile(l, io_opts, &dbg);
|
||||
}
|
||||
|
||||
Status GetAbsolutePath(const std::string& db_path,
|
||||
std::string* output_path) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetAbsolutePath(db_path, io_opts, output_path, &dbg);
|
||||
return file_system_->GetAbsolutePath(db_path, io_opts, output_path, &dbg);
|
||||
}
|
||||
|
||||
Status NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return file_system_->NewLogger(fname, io_opts, result, &dbg);
|
||||
}
|
||||
|
||||
#if !defined(OS_WIN) && !defined(ROCKSDB_NO_DYNAMIC_EXTENSION)
|
||||
@@ -531,10 +519,6 @@ class CompositeEnvWrapper : public Env {
|
||||
Status GetTestDirectory(std::string* path) override {
|
||||
return env_target_->GetTestDirectory(path);
|
||||
}
|
||||
Status NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
return env_target_->NewLogger(fname, result);
|
||||
}
|
||||
uint64_t NowMicros() override { return env_target_->NowMicros(); }
|
||||
uint64_t NowNanos() override { return env_target_->NowNanos(); }
|
||||
uint64_t NowCPUNanos() override { return env_target_->NowCPUNanos(); }
|
||||
@@ -590,46 +574,41 @@ class CompositeEnvWrapper : public Env {
|
||||
}
|
||||
|
||||
EnvOptions OptimizeForLogRead(const EnvOptions& env_options) const override {
|
||||
return fs_env_target_->OptimizeForLogRead(FileOptions(env_options));
|
||||
return file_system_->OptimizeForLogRead(FileOptions(env_options));
|
||||
}
|
||||
EnvOptions OptimizeForManifestRead(
|
||||
const EnvOptions& env_options) const override {
|
||||
return fs_env_target_->OptimizeForManifestRead(
|
||||
FileOptions(env_options));
|
||||
return file_system_->OptimizeForManifestRead(FileOptions(env_options));
|
||||
}
|
||||
EnvOptions OptimizeForLogWrite(const EnvOptions& env_options,
|
||||
const DBOptions& db_options) const override {
|
||||
return fs_env_target_->OptimizeForLogWrite(FileOptions(env_options),
|
||||
db_options);
|
||||
return file_system_->OptimizeForLogWrite(FileOptions(env_options),
|
||||
db_options);
|
||||
}
|
||||
EnvOptions OptimizeForManifestWrite(
|
||||
const EnvOptions& env_options) const override {
|
||||
return fs_env_target_->OptimizeForManifestWrite(
|
||||
FileOptions(env_options));
|
||||
return file_system_->OptimizeForManifestWrite(FileOptions(env_options));
|
||||
}
|
||||
EnvOptions OptimizeForCompactionTableWrite(
|
||||
const EnvOptions& env_options,
|
||||
const ImmutableDBOptions& immutable_ops) const override {
|
||||
return fs_env_target_->OptimizeForCompactionTableWrite(
|
||||
FileOptions(env_options),
|
||||
immutable_ops);
|
||||
return file_system_->OptimizeForCompactionTableWrite(
|
||||
FileOptions(env_options), immutable_ops);
|
||||
}
|
||||
EnvOptions OptimizeForCompactionTableRead(
|
||||
const EnvOptions& env_options,
|
||||
const ImmutableDBOptions& db_options) const override {
|
||||
return fs_env_target_->OptimizeForCompactionTableRead(
|
||||
FileOptions(env_options),
|
||||
db_options);
|
||||
return file_system_->OptimizeForCompactionTableRead(
|
||||
FileOptions(env_options), db_options);
|
||||
}
|
||||
Status GetFreeSpace(const std::string& path, uint64_t* diskfree) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetFreeSpace(path, io_opts, diskfree, &dbg);
|
||||
return file_system_->GetFreeSpace(path, io_opts, diskfree, &dbg);
|
||||
}
|
||||
|
||||
private:
|
||||
Env* env_target_;
|
||||
FileSystem* fs_env_target_;
|
||||
};
|
||||
|
||||
class LegacySequentialFileWrapper : public FSSequentialFile {
|
||||
@@ -1067,6 +1046,10 @@ class LegacyFileSystemWrapper : public FileSystem {
|
||||
return status_to_io_status(target_->NewLogger(fname, result));
|
||||
}
|
||||
|
||||
void SanitizeFileOptions(FileOptions* opts) const override {
|
||||
target_->SanitizeEnvOptions(opts);
|
||||
}
|
||||
|
||||
FileOptions OptimizeForLogRead(
|
||||
const FileOptions& file_options) const override {
|
||||
return target_->OptimizeForLogRead(file_options);
|
||||
|
||||
Vendored
+20
-14
@@ -22,6 +22,14 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Env::Env() : thread_status_updater_(nullptr) {
|
||||
file_system_ = std::make_shared<LegacyFileSystemWrapper>(this);
|
||||
}
|
||||
|
||||
Env::Env(std::shared_ptr<FileSystem> fs)
|
||||
: thread_status_updater_(nullptr),
|
||||
file_system_(fs) {}
|
||||
|
||||
Env::~Env() {
|
||||
}
|
||||
|
||||
@@ -361,20 +369,8 @@ void Log(const std::shared_ptr<Logger>& info_log, const char* format, ...) {
|
||||
|
||||
Status WriteStringToFile(Env* env, const Slice& data, const std::string& fname,
|
||||
bool should_sync) {
|
||||
std::unique_ptr<WritableFile> file;
|
||||
EnvOptions soptions;
|
||||
Status s = env->NewWritableFile(fname, &file, soptions);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
s = file->Append(data);
|
||||
if (s.ok() && should_sync) {
|
||||
s = file->Sync();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
env->DeleteFile(fname);
|
||||
}
|
||||
return s;
|
||||
LegacyFileSystemWrapper lfsw(env);
|
||||
return WriteStringToFile(&lfsw, data, fname, should_sync);
|
||||
}
|
||||
|
||||
Status ReadFileToString(Env* env, const std::string& fname, std::string* data) {
|
||||
@@ -472,4 +468,14 @@ Status NewEnvLogger(const std::string& fname, Env* env,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const std::shared_ptr<FileSystem>& Env::GetFileSystem() const {
|
||||
return file_system_;
|
||||
}
|
||||
|
||||
#ifdef OS_WIN
|
||||
std::unique_ptr<Env> NewCompositeEnv(std::shared_ptr<FileSystem> fs) {
|
||||
return std::unique_ptr<Env>(new CompositeEnvWrapper(Env::Default(), fs));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+55
-51
@@ -128,21 +128,25 @@ class PosixDynamicLibrary : public DynamicLibrary {
|
||||
|
||||
class PosixEnv : public CompositeEnvWrapper {
|
||||
public:
|
||||
PosixEnv();
|
||||
// This constructor is for constructing non-default Envs, mainly by
|
||||
// NewCompositeEnv(). It allows new instances to share the same
|
||||
// threadpool and other resources as the default Env, while allowing
|
||||
// a non-default FileSystem implementation
|
||||
PosixEnv(const PosixEnv* default_env, std::shared_ptr<FileSystem> fs);
|
||||
|
||||
~PosixEnv() override {
|
||||
for (const auto tid : threads_to_join_) {
|
||||
pthread_join(tid, nullptr);
|
||||
}
|
||||
for (int pool_id = 0; pool_id < Env::Priority::TOTAL; ++pool_id) {
|
||||
thread_pools_[pool_id].JoinAllThreads();
|
||||
}
|
||||
// Delete the thread_status_updater_ only when the current Env is not
|
||||
// Env::Default(). This is to avoid the free-after-use error when
|
||||
// Env::Default() is destructed while some other child threads are
|
||||
// still trying to update thread status.
|
||||
if (this != Env::Default()) {
|
||||
delete thread_status_updater_;
|
||||
if (this == Env::Default()) {
|
||||
for (const auto tid : threads_to_join_) {
|
||||
pthread_join(tid, nullptr);
|
||||
}
|
||||
for (int pool_id = 0; pool_id < Env::Priority::TOTAL; ++pool_id) {
|
||||
thread_pools_[pool_id].JoinAllThreads();
|
||||
}
|
||||
// Do not delete the thread_status_updater_ in order to avoid the
|
||||
// free after use when Env::Default() is destructed while some other
|
||||
// child threads are still trying to update thread status. All
|
||||
// PosixEnv instances use the same thread_status_updater_, so never
|
||||
// explicitly delete it.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,34 +256,6 @@ class PosixEnv : public CompositeEnvWrapper {
|
||||
|
||||
uint64_t GetThreadID() const override { return gettid(pthread_self()); }
|
||||
|
||||
Status NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
FILE* f;
|
||||
{
|
||||
IOSTATS_TIMER_GUARD(open_nanos);
|
||||
f = fopen(fname.c_str(),
|
||||
"w"
|
||||
#ifdef __GLIBC_PREREQ
|
||||
#if __GLIBC_PREREQ(2, 7)
|
||||
"e" // glibc extension to enable O_CLOEXEC
|
||||
#endif
|
||||
#endif
|
||||
);
|
||||
}
|
||||
if (f == nullptr) {
|
||||
result->reset();
|
||||
return IOError("when fopen a file for new logger", fname, errno);
|
||||
} else {
|
||||
int fd = fileno(f);
|
||||
#ifdef ROCKSDB_FALLOCATE_PRESENT
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 4 * 1024);
|
||||
#endif
|
||||
SetFD_CLOEXEC(fd, nullptr);
|
||||
result->reset(new PosixLogger(f, &PosixEnv::gettid, this));
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t NowMicros() override {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, nullptr);
|
||||
@@ -406,18 +382,34 @@ class PosixEnv : public CompositeEnvWrapper {
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ThreadPoolImpl> thread_pools_;
|
||||
pthread_mutex_t mu_;
|
||||
std::vector<pthread_t> threads_to_join_;
|
||||
friend Env* Env::Default();
|
||||
// Constructs the default Env, a singleton
|
||||
PosixEnv();
|
||||
|
||||
// The below 4 members are only used by the default PosixEnv instance.
|
||||
// Non-default instances simply maintain references to the backing
|
||||
// members in te default instance
|
||||
std::vector<ThreadPoolImpl> thread_pools_storage_;
|
||||
pthread_mutex_t mu_storage_;
|
||||
std::vector<pthread_t> threads_to_join_storage_;
|
||||
bool allow_non_owner_access_storage_;
|
||||
|
||||
std::vector<ThreadPoolImpl>& thread_pools_;
|
||||
pthread_mutex_t& mu_;
|
||||
std::vector<pthread_t>& threads_to_join_;
|
||||
// If true, allow non owner read access for db files. Otherwise, non-owner
|
||||
// has no access to db files.
|
||||
bool allow_non_owner_access_;
|
||||
bool& allow_non_owner_access_;
|
||||
};
|
||||
|
||||
PosixEnv::PosixEnv()
|
||||
: CompositeEnvWrapper(this, FileSystem::Default().get()),
|
||||
thread_pools_(Priority::TOTAL),
|
||||
allow_non_owner_access_(true) {
|
||||
: CompositeEnvWrapper(this, FileSystem::Default()),
|
||||
thread_pools_storage_(Priority::TOTAL),
|
||||
allow_non_owner_access_storage_(true),
|
||||
thread_pools_(thread_pools_storage_),
|
||||
mu_(mu_storage_),
|
||||
threads_to_join_(threads_to_join_storage_),
|
||||
allow_non_owner_access_(allow_non_owner_access_storage_) {
|
||||
ThreadPoolImpl::PthreadCall("mutex_init", pthread_mutex_init(&mu_, nullptr));
|
||||
for (int pool_id = 0; pool_id < Env::Priority::TOTAL; ++pool_id) {
|
||||
thread_pools_[pool_id].SetThreadPriority(
|
||||
@@ -428,6 +420,15 @@ PosixEnv::PosixEnv()
|
||||
thread_status_updater_ = CreateThreadStatusUpdater();
|
||||
}
|
||||
|
||||
PosixEnv::PosixEnv(const PosixEnv* default_env, std::shared_ptr<FileSystem> fs)
|
||||
: CompositeEnvWrapper(this, fs),
|
||||
thread_pools_(default_env->thread_pools_),
|
||||
mu_(default_env->mu_),
|
||||
threads_to_join_(default_env->threads_to_join_),
|
||||
allow_non_owner_access_(default_env->allow_non_owner_access_) {
|
||||
thread_status_updater_ = default_env->thread_status_updater_;
|
||||
}
|
||||
|
||||
void PosixEnv::Schedule(void (*function)(void* arg1), void* arg, Priority pri,
|
||||
void* tag, void (*unschedFunction)(void* arg)) {
|
||||
assert(pri >= Priority::BOTTOM && pri <= Priority::HIGH);
|
||||
@@ -519,9 +520,12 @@ Env* Env::Default() {
|
||||
CompressionContextCache::InitSingleton();
|
||||
INIT_SYNC_POINT_SINGLETONS();
|
||||
static PosixEnv default_env;
|
||||
static CompositeEnvWrapper composite_env(&default_env,
|
||||
FileSystem::Default().get());
|
||||
return &composite_env;
|
||||
return &default_env;
|
||||
}
|
||||
|
||||
std::unique_ptr<Env> NewCompositeEnv(std::shared_ptr<FileSystem> fs) {
|
||||
PosixEnv* default_env = static_cast<PosixEnv*>(Env::Default());
|
||||
return std::unique_ptr<Env>(new PosixEnv(default_env, fs));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+111
-1
@@ -35,6 +35,8 @@
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "test_util/fault_injection_test_env.h"
|
||||
#include "test_util/fault_injection_test_fs.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
@@ -1272,7 +1274,7 @@ TEST_F(EnvPosixTest, MultiReadNonAlignedLargeNum) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Only works in linux platforms
|
||||
#ifdef OS_WIN
|
||||
TEST_P(EnvPosixTestWithParam, DISABLED_InvalidateCache) {
|
||||
@@ -1980,6 +1982,114 @@ INSTANTIATE_TEST_CASE_P(
|
||||
::testing::Values(std::pair<Env*, bool>(chroot_env.get(), true)));
|
||||
#endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
|
||||
|
||||
class EnvFSTestWithParam
|
||||
: public ::testing::Test,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {
|
||||
public:
|
||||
EnvFSTestWithParam() {
|
||||
bool env_non_null = std::get<0>(GetParam());
|
||||
bool env_default = std::get<1>(GetParam());
|
||||
bool fs_default = std::get<2>(GetParam());
|
||||
|
||||
env_ = env_non_null ? (env_default ? Env::Default() : nullptr) : nullptr;
|
||||
fs_ = fs_default
|
||||
? FileSystem::Default()
|
||||
: std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
|
||||
if (env_non_null && env_default && !fs_default) {
|
||||
env_ptr_ = NewCompositeEnv(fs_);
|
||||
}
|
||||
if (env_non_null && !env_default && fs_default) {
|
||||
env_ptr_ = std::unique_ptr<Env>(new FaultInjectionTestEnv(Env::Default()));
|
||||
fs_.reset();
|
||||
}
|
||||
if (env_non_null && !env_default && !fs_default) {
|
||||
env_ptr_.reset(new FaultInjectionTestEnv(Env::Default()));
|
||||
composite_env_ptr_.reset(new CompositeEnvWrapper(env_ptr_.get(), fs_));
|
||||
env_ = composite_env_ptr_.get();
|
||||
} else {
|
||||
env_ = env_ptr_.get();
|
||||
}
|
||||
|
||||
dbname1_ = test::PerThreadDBPath("env_fs_test1");
|
||||
dbname2_ = test::PerThreadDBPath("env_fs_test2");
|
||||
}
|
||||
|
||||
~EnvFSTestWithParam() = default;
|
||||
|
||||
Env* env_;
|
||||
std::unique_ptr<Env> env_ptr_;
|
||||
std::unique_ptr<Env> composite_env_ptr_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
std::string dbname1_;
|
||||
std::string dbname2_;
|
||||
};
|
||||
|
||||
TEST_P(EnvFSTestWithParam, OptionsTest) {
|
||||
Options opts;
|
||||
opts.env = env_;
|
||||
opts.create_if_missing = true;
|
||||
std::string dbname = dbname1_;
|
||||
|
||||
if (env_) {
|
||||
if (fs_) {
|
||||
ASSERT_EQ(fs_.get(), env_->GetFileSystem().get());
|
||||
} else {
|
||||
ASSERT_NE(FileSystem::Default().get(), env_->GetFileSystem().get());
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
DB* db;
|
||||
Status s = DB::Open(opts, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
|
||||
WriteOptions wo;
|
||||
db->Put(wo, "a", "a");
|
||||
db->Flush(FlushOptions());
|
||||
db->Put(wo, "b", "b");
|
||||
db->Flush(FlushOptions());
|
||||
db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
|
||||
std::string val;
|
||||
ASSERT_OK(db->Get(ReadOptions(), "a", &val));
|
||||
ASSERT_EQ("a", val);
|
||||
ASSERT_OK(db->Get(ReadOptions(), "b", &val));
|
||||
ASSERT_EQ("b", val);
|
||||
|
||||
db->Close();
|
||||
delete db;
|
||||
DestroyDB(dbname, opts);
|
||||
|
||||
dbname = dbname2_;
|
||||
}
|
||||
}
|
||||
|
||||
// The parameters are as follows -
|
||||
// 1. True means Options::env is non-null, false means null
|
||||
// 2. True means use Env::Default, false means custom
|
||||
// 3. True means use FileSystem::Default, false means custom
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
EnvFSTest, EnvFSTestWithParam,
|
||||
::testing::Combine(::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool()));
|
||||
|
||||
// This test ensures that default Env and those allocated by
|
||||
// NewCompositeEnv() all share the same threadpool
|
||||
TEST_F(EnvTest, MultipleCompositeEnv) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fs1 =
|
||||
std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
|
||||
std::shared_ptr<FaultInjectionTestFS> fs2 =
|
||||
std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
|
||||
std::unique_ptr<Env> env1 = NewCompositeEnv(fs1);
|
||||
std::unique_ptr<Env> env2 = NewCompositeEnv(fs2);
|
||||
Env::Default()->SetBackgroundThreads(8, Env::HIGH);
|
||||
Env::Default()->SetBackgroundThreads(16, Env::LOW);
|
||||
|
||||
ASSERT_EQ(env1->GetBackgroundThreads(Env::LOW), 16);
|
||||
ASSERT_EQ(env1->GetBackgroundThreads(Env::HIGH), 8);
|
||||
ASSERT_EQ(env2->GetBackgroundThreads(Env::LOW), 16);
|
||||
ASSERT_EQ(env2->GetBackgroundThreads(Env::HIGH), 8);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Vendored
+34
-3
@@ -26,6 +26,18 @@ Status FileSystem::Load(const std::string& value,
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus FileSystem::ReuseWritableFile(const std::string& fname,
|
||||
const std::string& old_fname,
|
||||
const FileOptions& opts,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) {
|
||||
IOStatus s = RenameFile(old_fname, fname, opts.io_options, dbg);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return NewWritableFile(fname, opts, result, dbg);
|
||||
}
|
||||
|
||||
FileOptions FileSystem::OptimizeForLogRead(
|
||||
const FileOptions& file_options) const {
|
||||
FileOptions optimized_file_options(file_options);
|
||||
@@ -71,12 +83,31 @@ FileOptions FileSystem::OptimizeForCompactionTableRead(
|
||||
return optimized_file_options;
|
||||
}
|
||||
|
||||
Status ReadFileToString(FileSystem* fs, const std::string& fname,
|
||||
std::string* data) {
|
||||
IOStatus WriteStringToFile(FileSystem* fs, const Slice& data,
|
||||
const std::string& fname, bool should_sync) {
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
EnvOptions soptions;
|
||||
IOStatus s = fs->NewWritableFile(fname, soptions, &file, nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
s = file->Append(data, IOOptions(), nullptr);
|
||||
if (s.ok() && should_sync) {
|
||||
s = file->Sync(IOOptions(), nullptr);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fs->DeleteFile(fname, IOOptions(), nullptr);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus ReadFileToString(FileSystem* fs, const std::string& fname,
|
||||
std::string* data) {
|
||||
FileOptions soptions;
|
||||
data->clear();
|
||||
std::unique_ptr<FSSequentialFile> file;
|
||||
Status s = fs->NewSequentialFile(fname, soptions, &file, nullptr);
|
||||
IOStatus s = status_to_io_status(
|
||||
fs->NewSequentialFile(fname, soptions, &file, nullptr));
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Vendored
+74
-13
@@ -47,6 +47,7 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "env/composite_env_wrapper.h"
|
||||
#include "env/io_posix.h"
|
||||
#include "logging/logging.h"
|
||||
#include "logging/posix_logger.h"
|
||||
@@ -81,6 +82,10 @@ inline mode_t GetDBFileMode(bool allow_non_owner_access) {
|
||||
return allow_non_owner_access ? 0644 : 0600;
|
||||
}
|
||||
|
||||
static uint64_t gettid() {
|
||||
return Env::Default()->GetThreadID();
|
||||
}
|
||||
|
||||
// list of pathnames that are locked
|
||||
// Only used for error message.
|
||||
struct LockHoldingInfo {
|
||||
@@ -184,7 +189,8 @@ class PosixFileSystem : public FileSystem {
|
||||
}
|
||||
}
|
||||
result->reset(new PosixSequentialFile(
|
||||
fname, file, fd, GetLogicalBlockSize(fname, fd), options));
|
||||
fname, file, fd, GetLogicalBlockSizeForReadIfNeeded(options, fname, fd),
|
||||
options));
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
@@ -244,7 +250,8 @@ class PosixFileSystem : public FileSystem {
|
||||
#endif
|
||||
}
|
||||
result->reset(new PosixRandomAccessFile(
|
||||
fname, fd, GetLogicalBlockSize(fname, fd), options
|
||||
fname, fd, GetLogicalBlockSizeForReadIfNeeded(options, fname, fd),
|
||||
options
|
||||
#if defined(ROCKSDB_IOURING_PRESENT)
|
||||
,
|
||||
thread_local_io_urings_.get()
|
||||
@@ -331,13 +338,17 @@ class PosixFileSystem : public FileSystem {
|
||||
}
|
||||
#endif
|
||||
result->reset(new PosixWritableFile(
|
||||
fname, fd, GetLogicalBlockSize(fname, fd), options));
|
||||
fname, fd, GetLogicalBlockSizeForWriteIfNeeded(options, fname, fd),
|
||||
options));
|
||||
} else {
|
||||
// disable mmap writes
|
||||
EnvOptions no_mmap_writes_options = options;
|
||||
no_mmap_writes_options.use_mmap_writes = false;
|
||||
result->reset(new PosixWritableFile(
|
||||
fname, fd, GetLogicalBlockSize(fname, fd), no_mmap_writes_options));
|
||||
result->reset(
|
||||
new PosixWritableFile(fname, fd,
|
||||
GetLogicalBlockSizeForWriteIfNeeded(
|
||||
no_mmap_writes_options, fname, fd),
|
||||
no_mmap_writes_options));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -433,13 +444,17 @@ class PosixFileSystem : public FileSystem {
|
||||
}
|
||||
#endif
|
||||
result->reset(new PosixWritableFile(
|
||||
fname, fd, GetLogicalBlockSize(fname, fd), options));
|
||||
fname, fd, GetLogicalBlockSizeForWriteIfNeeded(options, fname, fd),
|
||||
options));
|
||||
} else {
|
||||
// disable mmap writes
|
||||
FileOptions no_mmap_writes_options = options;
|
||||
no_mmap_writes_options.use_mmap_writes = false;
|
||||
result->reset(new PosixWritableFile(
|
||||
fname, fd, GetLogicalBlockSize(fname, fd), no_mmap_writes_options));
|
||||
result->reset(
|
||||
new PosixWritableFile(fname, fd,
|
||||
GetLogicalBlockSizeForWriteIfNeeded(
|
||||
no_mmap_writes_options, fname, fd),
|
||||
no_mmap_writes_options));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -530,10 +545,34 @@ class PosixFileSystem : public FileSystem {
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
IOStatus NewLogger(const std::string& /*fname*/, const IOOptions& /*opts*/,
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::Logger>* /*ptr*/,
|
||||
IODebugContext* /*dbg*/) override {
|
||||
return IOStatus::NotSupported();
|
||||
IOStatus NewLogger(const std::string& fname, const IOOptions& /*opts*/,
|
||||
std::shared_ptr<Logger>* result,
|
||||
IODebugContext* /*dbg*/) override {
|
||||
FILE* f;
|
||||
{
|
||||
IOSTATS_TIMER_GUARD(open_nanos);
|
||||
f = fopen(fname.c_str(),
|
||||
"w"
|
||||
#ifdef __GLIBC_PREREQ
|
||||
#if __GLIBC_PREREQ(2, 7)
|
||||
"e" // glibc extension to enable O_CLOEXEC
|
||||
#endif
|
||||
#endif
|
||||
);
|
||||
}
|
||||
if (f == nullptr) {
|
||||
result->reset();
|
||||
return status_to_io_status(
|
||||
IOError("when fopen a file for new logger", fname, errno));
|
||||
} else {
|
||||
int fd = fileno(f);
|
||||
#ifdef ROCKSDB_FALLOCATE_PRESENT
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 4 * 1024);
|
||||
#endif
|
||||
SetFD_CLOEXEC(fd, nullptr);
|
||||
result->reset(new PosixLogger(f, &gettid, Env::Default()));
|
||||
return IOStatus::OK();
|
||||
}
|
||||
}
|
||||
|
||||
IOStatus FileExists(const std::string& fname, const IOOptions& /*opts*/,
|
||||
@@ -918,6 +957,14 @@ class PosixFileSystem : public FileSystem {
|
||||
static LogicalBlockSizeCache logical_block_size_cache_;
|
||||
#endif
|
||||
static size_t GetLogicalBlockSize(const std::string& fname, int fd);
|
||||
// In non-direct IO mode, this directly returns kDefaultPageSize.
|
||||
// Otherwise call GetLogicalBlockSize.
|
||||
static size_t GetLogicalBlockSizeForReadIfNeeded(const EnvOptions& options,
|
||||
const std::string& fname,
|
||||
int fd);
|
||||
static size_t GetLogicalBlockSizeForWriteIfNeeded(const EnvOptions& options,
|
||||
const std::string& fname,
|
||||
int fd);
|
||||
};
|
||||
|
||||
#ifdef OS_LINUX
|
||||
@@ -928,11 +975,25 @@ size_t PosixFileSystem::GetLogicalBlockSize(const std::string& fname, int fd) {
|
||||
#ifdef OS_LINUX
|
||||
return logical_block_size_cache_.GetLogicalBlockSize(fname, fd);
|
||||
#else
|
||||
(void) fname;
|
||||
(void)fname;
|
||||
return PosixHelper::GetLogicalBlockSizeOfFd(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t PosixFileSystem::GetLogicalBlockSizeForReadIfNeeded(
|
||||
const EnvOptions& options, const std::string& fname, int fd) {
|
||||
return options.use_direct_reads
|
||||
? PosixFileSystem::GetLogicalBlockSize(fname, fd)
|
||||
: kDefaultPageSize;
|
||||
}
|
||||
|
||||
size_t PosixFileSystem::GetLogicalBlockSizeForWriteIfNeeded(
|
||||
const EnvOptions& options, const std::string& fname, int fd) {
|
||||
return options.use_direct_writes
|
||||
? PosixFileSystem::GetLogicalBlockSize(fname, fd)
|
||||
: kDefaultPageSize;
|
||||
}
|
||||
|
||||
PosixFileSystem::PosixFileSystem()
|
||||
: checkedDiskForMmap_(false),
|
||||
forceMmapOff_(false),
|
||||
|
||||
Vendored
+1
@@ -234,6 +234,7 @@ IOStatus PosixSequentialFile::Read(size_t n, const IOOptions& /*opts*/,
|
||||
IOStatus s;
|
||||
size_t r = 0;
|
||||
do {
|
||||
clearerr(file_);
|
||||
r = fread_unlocked(scratch, 1, n, file_);
|
||||
} while (r == 0 && ferror(file_) && errno == EINTR);
|
||||
*result = Slice(scratch, r);
|
||||
|
||||
Vendored
+1
-11
@@ -10,6 +10,7 @@
|
||||
#include "env/mock_env.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include "file/filename.h"
|
||||
#include "port/sys_time.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/murmurhash.h"
|
||||
@@ -747,17 +748,6 @@ Status MockEnv::CorruptBuffer(const std::string& fname) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string MockEnv::NormalizePath(const std::string path) {
|
||||
std::string dst;
|
||||
for (auto c : path) {
|
||||
if (!dst.empty() && c == '/' && dst.back() == '/') {
|
||||
continue;
|
||||
}
|
||||
dst.push_back(c);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
void MockEnv::FakeSleepForMicroseconds(int64_t micros) {
|
||||
fake_sleep_micros_.fetch_add(micros);
|
||||
}
|
||||
|
||||
Vendored
-2
@@ -106,8 +106,6 @@ class MockEnv : public EnvWrapper {
|
||||
void FakeSleepForMicroseconds(int64_t micros);
|
||||
|
||||
private:
|
||||
std::string NormalizePath(const std::string path);
|
||||
|
||||
// Map from filenames to MemFile objects, representing a simple file system.
|
||||
typedef std::map<std::string, MemFile*> FileSystem;
|
||||
port::Mutex mutex_;
|
||||
|
||||
@@ -32,13 +32,13 @@ DeleteScheduler::DeleteScheduler(Env* env, FileSystem* fs,
|
||||
bytes_max_delete_chunk_(bytes_max_delete_chunk),
|
||||
closing_(false),
|
||||
cv_(&mu_),
|
||||
bg_thread_(nullptr),
|
||||
info_log_(info_log),
|
||||
sst_file_manager_(sst_file_manager),
|
||||
max_trash_db_ratio_(max_trash_db_ratio) {
|
||||
assert(sst_file_manager != nullptr);
|
||||
assert(max_trash_db_ratio >= 0);
|
||||
bg_thread_.reset(
|
||||
new port::Thread(&DeleteScheduler::BackgroundEmptyTrash, this));
|
||||
MaybeCreateBackgroundThread();
|
||||
}
|
||||
|
||||
DeleteScheduler::~DeleteScheduler() {
|
||||
@@ -352,6 +352,13 @@ void DeleteScheduler::WaitForEmptyTrash() {
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteScheduler::MaybeCreateBackgroundThread() {
|
||||
if(bg_thread_ == nullptr && rate_bytes_per_sec_.load() > 0) {
|
||||
bg_thread_.reset(
|
||||
new port::Thread(&DeleteScheduler::BackgroundEmptyTrash, this));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -45,6 +45,7 @@ class DeleteScheduler {
|
||||
// Set delete rate limit in bytes per second
|
||||
void SetRateBytesPerSecond(int64_t bytes_per_sec) {
|
||||
rate_bytes_per_sec_.store(bytes_per_sec);
|
||||
MaybeCreateBackgroundThread();
|
||||
}
|
||||
|
||||
// Mark file as trash directory and schedule it's deletion. If force_bg is
|
||||
@@ -91,6 +92,8 @@ class DeleteScheduler {
|
||||
|
||||
void BackgroundEmptyTrash();
|
||||
|
||||
void MaybeCreateBackgroundThread();
|
||||
|
||||
Env* env_;
|
||||
FileSystem* fs_;
|
||||
|
||||
|
||||
+20
-8
@@ -368,19 +368,19 @@ bool ParseFileName(const std::string& fname, uint64_t* number,
|
||||
return true;
|
||||
}
|
||||
|
||||
Status SetCurrentFile(Env* env, const std::string& dbname,
|
||||
uint64_t descriptor_number,
|
||||
FSDirectory* directory_to_fsync) {
|
||||
IOStatus SetCurrentFile(FileSystem* fs, const std::string& dbname,
|
||||
uint64_t descriptor_number,
|
||||
FSDirectory* directory_to_fsync) {
|
||||
// Remove leading "dbname/" and add newline to manifest file name
|
||||
std::string manifest = DescriptorFileName(dbname, descriptor_number);
|
||||
Slice contents = manifest;
|
||||
assert(contents.starts_with(dbname + "/"));
|
||||
contents.remove_prefix(dbname.size() + 1);
|
||||
std::string tmp = TempFileName(dbname, descriptor_number);
|
||||
Status s = WriteStringToFile(env, contents.ToString() + "\n", tmp, true);
|
||||
IOStatus s = WriteStringToFile(fs, contents.ToString() + "\n", tmp, true);
|
||||
if (s.ok()) {
|
||||
TEST_KILL_RANDOM("SetCurrentFile:0", rocksdb_kill_odds * REDUCE_ODDS2);
|
||||
s = env->RenameFile(tmp, CurrentFileName(dbname));
|
||||
s = fs->RenameFile(tmp, CurrentFileName(dbname), IOOptions(), nullptr);
|
||||
TEST_KILL_RANDOM("SetCurrentFile:1", rocksdb_kill_odds * REDUCE_ODDS2);
|
||||
}
|
||||
if (s.ok()) {
|
||||
@@ -388,7 +388,7 @@ Status SetCurrentFile(Env* env, const std::string& dbname,
|
||||
s = directory_to_fsync->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
} else {
|
||||
env->DeleteFile(tmp);
|
||||
fs->DeleteFile(tmp, IOOptions(), nullptr);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -414,8 +414,8 @@ Status SetIdentityFile(Env* env, const std::string& dbname,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status SyncManifest(Env* env, const ImmutableDBOptions* db_options,
|
||||
WritableFileWriter* file) {
|
||||
IOStatus SyncManifest(Env* env, const ImmutableDBOptions* db_options,
|
||||
WritableFileWriter* file) {
|
||||
TEST_KILL_RANDOM("SyncManifest:0", rocksdb_kill_odds * REDUCE_ODDS2);
|
||||
StopWatch sw(env, db_options->statistics.get(), MANIFEST_FILE_SYNC_MICROS);
|
||||
return file->Sync(db_options->use_fsync);
|
||||
@@ -453,4 +453,16 @@ Status GetInfoLogFiles(Env* env, const std::string& db_log_dir,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string NormalizePath(const std::string& path) {
|
||||
std::string dst;
|
||||
for (auto c : path) {
|
||||
if (!dst.empty() && c == kFilePathSeparator &&
|
||||
dst.back() == kFilePathSeparator) {
|
||||
continue;
|
||||
}
|
||||
dst.push_back(c);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+13
-5
@@ -29,6 +29,12 @@ class Env;
|
||||
class Directory;
|
||||
class WritableFileWriter;
|
||||
|
||||
#ifdef OS_WIN
|
||||
const char kFilePathSeparator = '\\';
|
||||
#else
|
||||
const char kFilePathSeparator = '/';
|
||||
#endif
|
||||
|
||||
enum FileType {
|
||||
kLogFile,
|
||||
kDBLockFile,
|
||||
@@ -163,17 +169,17 @@ extern bool ParseFileName(const std::string& filename, uint64_t* number,
|
||||
|
||||
// Make the CURRENT file point to the descriptor file with the
|
||||
// specified number.
|
||||
extern Status SetCurrentFile(Env* env, const std::string& dbname,
|
||||
uint64_t descriptor_number,
|
||||
FSDirectory* directory_to_fsync);
|
||||
extern IOStatus SetCurrentFile(FileSystem* fs, const std::string& dbname,
|
||||
uint64_t descriptor_number,
|
||||
FSDirectory* directory_to_fsync);
|
||||
|
||||
// Make the IDENTITY file for the db
|
||||
extern Status SetIdentityFile(Env* env, const std::string& dbname,
|
||||
const std::string& db_id = {});
|
||||
|
||||
// Sync manifest file `file`.
|
||||
extern Status SyncManifest(Env* env, const ImmutableDBOptions* db_options,
|
||||
WritableFileWriter* file);
|
||||
extern IOStatus SyncManifest(Env* env, const ImmutableDBOptions* db_options,
|
||||
WritableFileWriter* file);
|
||||
|
||||
// Return list of file names of info logs in `file_names`.
|
||||
// The list only contains file name. The parent directory name is stored
|
||||
@@ -183,4 +189,6 @@ extern Status GetInfoLogFiles(Env* env, const std::string& db_log_dir,
|
||||
const std::string& dbname,
|
||||
std::string* parent_dir,
|
||||
std::vector<std::string>* file_names);
|
||||
|
||||
extern std::string NormalizePath(const std::string& path);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
#include "util/rate_limiter.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status RandomAccessFileReader::Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch,
|
||||
std::unique_ptr<const char[]>* internal_buf,
|
||||
char* scratch, AlignedBuf* aligned_buf,
|
||||
bool for_compaction) const {
|
||||
(void) internal_buf;
|
||||
(void)aligned_buf;
|
||||
Status s;
|
||||
uint64_t elapsed = 0;
|
||||
{
|
||||
@@ -81,11 +81,11 @@ Status RandomAccessFileReader::Read(uint64_t offset, size_t n, Slice* result,
|
||||
size_t res_len = 0;
|
||||
if (s.ok() && offset_advance < buf.CurrentSize()) {
|
||||
res_len = std::min(buf.CurrentSize() - offset_advance, n);
|
||||
if (internal_buf == nullptr) {
|
||||
if (aligned_buf == nullptr) {
|
||||
buf.Read(scratch, offset_advance, res_len);
|
||||
} else {
|
||||
scratch = buf.BufferStart();
|
||||
internal_buf->reset(buf.Release());
|
||||
scratch = buf.BufferStart() + offset_advance;
|
||||
aligned_buf->reset(buf.Release());
|
||||
}
|
||||
}
|
||||
*result = Slice(scratch, res_len);
|
||||
@@ -154,11 +154,45 @@ Status RandomAccessFileReader::Read(uint64_t offset, size_t n, Slice* result,
|
||||
return s;
|
||||
}
|
||||
|
||||
size_t End(const FSReadRequest& r) {
|
||||
return static_cast<size_t>(r.offset) + r.len;
|
||||
}
|
||||
|
||||
FSReadRequest Align(const FSReadRequest& r, size_t alignment) {
|
||||
FSReadRequest req;
|
||||
req.offset = static_cast<uint64_t>(
|
||||
TruncateToPageBoundary(alignment, static_cast<size_t>(r.offset)));
|
||||
req.len = Roundup(End(r), alignment) - req.offset;
|
||||
req.scratch = nullptr;
|
||||
return req;
|
||||
}
|
||||
|
||||
// Try to merge src to dest if they have overlap.
|
||||
//
|
||||
// Each request represents an inclusive interval [offset, offset + len].
|
||||
// If the intervals have overlap, update offset and len to represent the
|
||||
// merged interval, and return true.
|
||||
// Otherwise, do nothing and return false.
|
||||
bool TryMerge(FSReadRequest* dest, const FSReadRequest& src) {
|
||||
size_t dest_offset = static_cast<size_t>(dest->offset);
|
||||
size_t src_offset = static_cast<size_t>(src.offset);
|
||||
size_t dest_end = End(*dest);
|
||||
size_t src_end = End(src);
|
||||
if (std::max(dest_offset, dest_offset) > std::min(dest_end, src_end)) {
|
||||
return false;
|
||||
}
|
||||
dest->offset = static_cast<uint64_t>(std::min(dest_offset, src_offset));
|
||||
dest->len = std::max(dest_end, src_end) - dest->offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status RandomAccessFileReader::MultiRead(FSReadRequest* read_reqs,
|
||||
size_t num_reqs) const {
|
||||
size_t num_reqs,
|
||||
AlignedBuf* aligned_buf) const {
|
||||
(void)aligned_buf; // suppress warning of unused variable in LITE mode
|
||||
assert(num_reqs > 0);
|
||||
Status s;
|
||||
uint64_t elapsed = 0;
|
||||
assert(!use_direct_io());
|
||||
{
|
||||
StopWatch sw(env_, stats_, hist_type_,
|
||||
(stats_ != nullptr) ? &elapsed : nullptr, true /*overwrite*/,
|
||||
@@ -166,6 +200,44 @@ Status RandomAccessFileReader::MultiRead(FSReadRequest* read_reqs,
|
||||
auto prev_perf_level = GetPerfLevel();
|
||||
IOSTATS_TIMER_GUARD(read_nanos);
|
||||
|
||||
FSReadRequest* fs_reqs = read_reqs;
|
||||
size_t num_fs_reqs = num_reqs;
|
||||
#ifndef ROCKSDB_LITE
|
||||
std::vector<FSReadRequest> aligned_reqs;
|
||||
if (use_direct_io()) {
|
||||
// num_reqs is the max possible size,
|
||||
// this can reduce std::vecector's internal resize operations.
|
||||
aligned_reqs.reserve(num_reqs);
|
||||
// Align and merge the read requests.
|
||||
size_t alignment = file_->GetRequiredBufferAlignment();
|
||||
aligned_reqs.push_back(Align(read_reqs[0], alignment));
|
||||
for (size_t i = 1; i < num_reqs; i++) {
|
||||
const auto& r = Align(read_reqs[i], alignment);
|
||||
if (!TryMerge(&aligned_reqs.back(), r)) {
|
||||
aligned_reqs.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate aligned buffer and let scratch buffers point to it.
|
||||
size_t total_len = 0;
|
||||
for (const auto& r : aligned_reqs) {
|
||||
total_len += r.len;
|
||||
}
|
||||
AlignedBuffer buf;
|
||||
buf.Alignment(alignment);
|
||||
buf.AllocateNewBuffer(total_len);
|
||||
char* scratch = buf.BufferStart();
|
||||
for (auto& r : aligned_reqs) {
|
||||
r.scratch = scratch;
|
||||
scratch += r.len;
|
||||
}
|
||||
|
||||
aligned_buf->reset(buf.Release());
|
||||
fs_reqs = aligned_reqs.data();
|
||||
num_fs_reqs = aligned_reqs.size();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
FileOperationInfo::TimePoint start_ts;
|
||||
if (ShouldNotifyListeners()) {
|
||||
@@ -174,8 +246,31 @@ Status RandomAccessFileReader::MultiRead(FSReadRequest* read_reqs,
|
||||
#endif // ROCKSDB_LITE
|
||||
{
|
||||
IOSTATS_CPU_TIMER_GUARD(cpu_read_nanos, env_);
|
||||
s = file_->MultiRead(read_reqs, num_reqs, IOOptions(), nullptr);
|
||||
s = file_->MultiRead(fs_reqs, num_fs_reqs, IOOptions(), nullptr);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (use_direct_io()) {
|
||||
// Populate results in the unaligned read requests.
|
||||
size_t aligned_i = 0;
|
||||
for (size_t i = 0; i < num_reqs; i++) {
|
||||
auto& r = read_reqs[i];
|
||||
if (static_cast<size_t>(r.offset) > End(aligned_reqs[aligned_i])) {
|
||||
aligned_i++;
|
||||
}
|
||||
const auto& fs_r = fs_reqs[aligned_i];
|
||||
r.status = fs_r.status;
|
||||
if (r.status.ok()) {
|
||||
uint64_t offset = r.offset - fs_r.offset;
|
||||
size_t len = std::min(r.len, static_cast<size_t>(fs_r.len - offset));
|
||||
r.result = Slice(fs_r.scratch + offset, len);
|
||||
} else {
|
||||
r.result = Slice();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
for (size_t i = 0; i < num_reqs; ++i) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (ShouldNotifyListeners()) {
|
||||
@@ -194,4 +289,5 @@ Status RandomAccessFileReader::MultiRead(FSReadRequest* read_reqs,
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class Statistics;
|
||||
class HistogramImpl;
|
||||
|
||||
using AlignedBuf = std::unique_ptr<const char[]>;
|
||||
|
||||
// RandomAccessFileReader is a wrapper on top of Env::RnadomAccessFile. It is
|
||||
// responsible for:
|
||||
// - Handling Buffered and Direct reads appropriately.
|
||||
@@ -59,7 +61,7 @@ class RandomAccessFileReader {
|
||||
|
||||
public:
|
||||
explicit RandomAccessFileReader(
|
||||
std::unique_ptr<FSRandomAccessFile>&& raf, std::string _file_name,
|
||||
std::unique_ptr<FSRandomAccessFile>&& raf, const std::string& _file_name,
|
||||
Env* env = nullptr, Statistics* stats = nullptr, uint32_t hist_type = 0,
|
||||
HistogramImpl* file_read_hist = nullptr,
|
||||
RateLimiter* rate_limiter = nullptr,
|
||||
@@ -106,17 +108,22 @@ class RandomAccessFileReader {
|
||||
// 1. if using mmap, result is stored in a buffer other than scratch;
|
||||
// 2. if not using mmap, result is stored in the buffer starting from scratch.
|
||||
//
|
||||
// In direct IO mode, an internal aligned buffer is allocated.
|
||||
// 1. If internal_buf is null, then results are copied to the buffer
|
||||
// In direct IO mode, an aligned buffer is allocated internally.
|
||||
// 1. If aligned_buf is null, then results are copied to the buffer
|
||||
// starting from scratch;
|
||||
// 2. Otherwise, scratch is not used and can be null, the internal_buf owns
|
||||
// 2. Otherwise, scratch is not used and can be null, the aligned_buf owns
|
||||
// the internally allocated buffer on return, and the result refers to a
|
||||
// region in internal_buf.
|
||||
// region in aligned_buf.
|
||||
Status Read(uint64_t offset, size_t n, Slice* result, char* scratch,
|
||||
std::unique_ptr<const char[]>* internal_buf,
|
||||
bool for_compaction = false) const;
|
||||
AlignedBuf* aligned_buf, bool for_compaction = false) const;
|
||||
|
||||
Status MultiRead(FSReadRequest* reqs, size_t num_reqs) const;
|
||||
// REQUIRES:
|
||||
// num_reqs > 0, reqs do not overlap, and offsets in reqs are increasing.
|
||||
// In non-direct IO mode, aligned_buf should be null;
|
||||
// In direct IO mode, aligned_buf stores the aligned buffer allocated inside
|
||||
// MultiRead, the result Slices in reqs refer to aligned_buf.
|
||||
Status MultiRead(FSReadRequest* reqs, size_t num_reqs,
|
||||
AlignedBuf* aligned_buf) const;
|
||||
|
||||
Status Prefetch(uint64_t offset, size_t n) const {
|
||||
return file_->Prefetch(offset, n, IOOptions(), nullptr);
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class RandomAccessFileReaderTest : public testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
#ifdef OS_LINUX
|
||||
// TEST_TMPDIR may be set to /dev/shm in Makefile,
|
||||
// but /dev/shm does not support direct IO.
|
||||
// The default TEST_TMPDIR is under /tmp, but /tmp might also be a tmpfs
|
||||
// which does not support direct IO neither.
|
||||
unsetenv("TEST_TMPDIR");
|
||||
char* tmpdir = getenv("DISK_TEMP_DIR");
|
||||
if (tmpdir == nullptr) {
|
||||
tmpdir = getenv("HOME");
|
||||
}
|
||||
if (tmpdir != nullptr) {
|
||||
setenv("TEST_TMPDIR", tmpdir, 1);
|
||||
}
|
||||
#endif
|
||||
env_ = Env::Default();
|
||||
fs_ = FileSystem::Default();
|
||||
test_dir_ = test::PerThreadDBPath("random_access_file_reader_test");
|
||||
ASSERT_OK(fs_->CreateDir(test_dir_, IOOptions(), nullptr));
|
||||
alignment_ = GetAlignment();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
EXPECT_OK(test::DestroyDir(env_, test_dir_));
|
||||
}
|
||||
|
||||
bool IsDirectIOSupported() {
|
||||
Write(".direct", "");
|
||||
FileOptions opt;
|
||||
opt.use_direct_reads = true;
|
||||
std::unique_ptr<FSRandomAccessFile> f;
|
||||
auto s = fs_->NewRandomAccessFile(Path(".direct"), opt, &f, nullptr);
|
||||
return s.ok();
|
||||
}
|
||||
|
||||
void Write(const std::string& fname, const std::string& content) {
|
||||
std::unique_ptr<FSWritableFile> f;
|
||||
ASSERT_OK(fs_->NewWritableFile(Path(fname), FileOptions(), &f, nullptr));
|
||||
ASSERT_OK(f->Append(content, IOOptions(), nullptr));
|
||||
ASSERT_OK(f->Close(IOOptions(), nullptr));
|
||||
}
|
||||
|
||||
void Read(const std::string& fname, const FileOptions& opts,
|
||||
std::unique_ptr<RandomAccessFileReader>* reader) {
|
||||
std::string fpath = Path(fname);
|
||||
std::unique_ptr<FSRandomAccessFile> f;
|
||||
ASSERT_OK(fs_->NewRandomAccessFile(fpath, opts, &f, nullptr));
|
||||
(*reader).reset(new RandomAccessFileReader(std::move(f), fpath, env_));
|
||||
}
|
||||
|
||||
void AssertResult(const std::string& content,
|
||||
const std::vector<FSReadRequest>& reqs) {
|
||||
for (const auto& r : reqs) {
|
||||
ASSERT_OK(r.status);
|
||||
ASSERT_EQ(r.len, r.result.size());
|
||||
ASSERT_EQ(content.substr(r.offset, r.len), r.result.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
size_t alignment() const { return alignment_; }
|
||||
|
||||
private:
|
||||
Env* env_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
std::string test_dir_;
|
||||
size_t alignment_;
|
||||
|
||||
std::string Path(const std::string& fname) {
|
||||
return test_dir_ + "/" + fname;
|
||||
}
|
||||
|
||||
size_t GetAlignment() {
|
||||
std::string f = "get_alignment";
|
||||
Write(f, "");
|
||||
std::unique_ptr<RandomAccessFileReader> r;
|
||||
Read(f, FileOptions(), &r);
|
||||
size_t alignment = r->file()->GetRequiredBufferAlignment();
|
||||
EXPECT_OK(fs_->DeleteFile(Path(f), IOOptions(), nullptr));
|
||||
return alignment;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RandomAccessFileReaderTest, ReadDirectIO) {
|
||||
if (!IsDirectIOSupported()) {
|
||||
printf("Direct IO is not supported, skip this test\n");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string fname = "read-direct-io";
|
||||
Random rand(0);
|
||||
std::string content;
|
||||
test::RandomString(&rand, static_cast<int>(alignment()), &content);
|
||||
Write(fname, content);
|
||||
|
||||
FileOptions opts;
|
||||
opts.use_direct_reads = true;
|
||||
std::unique_ptr<RandomAccessFileReader> r;
|
||||
Read(fname, opts, &r);
|
||||
ASSERT_TRUE(r->use_direct_io());
|
||||
|
||||
size_t offset = alignment() / 2;
|
||||
size_t len = alignment() / 3;
|
||||
Slice result;
|
||||
AlignedBuf buf;
|
||||
for (bool for_compaction : {true, false}) {
|
||||
ASSERT_OK(r->Read(offset, len, &result, nullptr, &buf, for_compaction));
|
||||
ASSERT_EQ(result.ToString(), content.substr(offset, len));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RandomAccessFileReaderTest, MultiReadDirectIO) {
|
||||
if (!IsDirectIOSupported()) {
|
||||
printf("Direct IO is not supported, skip this test\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Creates a file with 3 pages.
|
||||
std::string fname = "multi-read-direct-io";
|
||||
Random rand(0);
|
||||
std::string content;
|
||||
test::RandomString(&rand, 3 * static_cast<int>(alignment()), &content);
|
||||
Write(fname, content);
|
||||
|
||||
FileOptions opts;
|
||||
opts.use_direct_reads = true;
|
||||
std::unique_ptr<RandomAccessFileReader> r;
|
||||
Read(fname, opts, &r);
|
||||
ASSERT_TRUE(r->use_direct_io());
|
||||
|
||||
{
|
||||
// Reads 2 blocks in the 1st page.
|
||||
// The results should be SharedSlices of the same underlying buffer.
|
||||
//
|
||||
// Illustration (each x is a 1/4 page)
|
||||
// First page: xxxx
|
||||
// 1st block: x
|
||||
// 2nd block: xx
|
||||
FSReadRequest r0;
|
||||
r0.offset = 0;
|
||||
r0.len = alignment() / 4;
|
||||
r0.scratch = nullptr;
|
||||
|
||||
FSReadRequest r1;
|
||||
r1.offset = alignment() / 2;
|
||||
r1.len = alignment() / 2;
|
||||
r1.scratch = nullptr;
|
||||
|
||||
std::vector<FSReadRequest> reqs;
|
||||
reqs.push_back(std::move(r0));
|
||||
reqs.push_back(std::move(r1));
|
||||
AlignedBuf aligned_buf;
|
||||
ASSERT_OK(r->MultiRead(reqs.data(), reqs.size(), &aligned_buf));
|
||||
|
||||
AssertResult(content, reqs);
|
||||
}
|
||||
|
||||
{
|
||||
// Reads 3 blocks:
|
||||
// 1st block in the 1st page;
|
||||
// 2nd block from the middle of the 1st page to the middle of the 2nd page;
|
||||
// 3rd block in the 2nd page.
|
||||
// The results should be SharedSlices of the same underlying buffer.
|
||||
//
|
||||
// Illustration (each x is a 1/4 page)
|
||||
// 2 pages: xxxxxxxx
|
||||
// 1st block: x
|
||||
// 2nd block: xxxx
|
||||
// 3rd block: x
|
||||
FSReadRequest r0;
|
||||
r0.offset = 0;
|
||||
r0.len = alignment() / 4;
|
||||
r0.scratch = nullptr;
|
||||
|
||||
FSReadRequest r1;
|
||||
r1.offset = alignment() / 2;
|
||||
r1.len = alignment();
|
||||
r1.scratch = nullptr;
|
||||
|
||||
FSReadRequest r2;
|
||||
r2.offset = 2 * alignment() - alignment() / 4;
|
||||
r2.len = alignment() / 4;
|
||||
r2.scratch = nullptr;
|
||||
|
||||
std::vector<FSReadRequest> reqs;
|
||||
reqs.push_back(std::move(r0));
|
||||
reqs.push_back(std::move(r1));
|
||||
reqs.push_back(std::move(r2));
|
||||
AlignedBuf aligned_buf;
|
||||
ASSERT_OK(r->MultiRead(reqs.data(), reqs.size(), &aligned_buf));
|
||||
|
||||
AssertResult(content, reqs);
|
||||
}
|
||||
|
||||
{
|
||||
// Reads 3 blocks:
|
||||
// 1st block in the middle of the 1st page;
|
||||
// 2nd block in the middle of the 2nd page;
|
||||
// 3rd block in the middle of the 3rd page.
|
||||
// The results should be SharedSlices of the same underlying buffer.
|
||||
//
|
||||
// Illustration (each x is a 1/4 page)
|
||||
// 3 pages: xxxxxxxxxxxx
|
||||
// 1st block: xx
|
||||
// 2nd block: xx
|
||||
// 3rd block: xx
|
||||
FSReadRequest r0;
|
||||
r0.offset = alignment() / 4;
|
||||
r0.len = alignment() / 2;
|
||||
r0.scratch = nullptr;
|
||||
|
||||
FSReadRequest r1;
|
||||
r1.offset = alignment() + alignment() / 4;
|
||||
r1.len = alignment() / 2;
|
||||
r1.scratch = nullptr;
|
||||
|
||||
FSReadRequest r2;
|
||||
r2.offset = 2 * alignment() + alignment() / 4;
|
||||
r2.len = alignment() / 2;
|
||||
r2.scratch = nullptr;
|
||||
|
||||
std::vector<FSReadRequest> reqs;
|
||||
reqs.push_back(std::move(r0));
|
||||
reqs.push_back(std::move(r1));
|
||||
reqs.push_back(std::move(r2));
|
||||
AlignedBuf aligned_buf;
|
||||
ASSERT_OK(r->MultiRead(reqs.data(), reqs.size(), &aligned_buf));
|
||||
|
||||
AssertResult(content, reqs);
|
||||
}
|
||||
|
||||
{
|
||||
// Reads 2 blocks:
|
||||
// 1st block in the middle of the 1st page;
|
||||
// 2nd block in the middle of the 3rd page.
|
||||
// The results are two different buffers.
|
||||
//
|
||||
// Illustration (each x is a 1/4 page)
|
||||
// 3 pages: xxxxxxxxxxxx
|
||||
// 1st block: xx
|
||||
// 2nd block: xx
|
||||
FSReadRequest r0;
|
||||
r0.offset = alignment() / 4;
|
||||
r0.len = alignment() / 2;
|
||||
r0.scratch = nullptr;
|
||||
|
||||
FSReadRequest r1;
|
||||
r1.offset = 2 * alignment() + alignment() / 4;
|
||||
r1.len = alignment() / 2;
|
||||
r1.scratch = nullptr;
|
||||
|
||||
std::vector<FSReadRequest> reqs;
|
||||
reqs.push_back(std::move(r0));
|
||||
reqs.push_back(std::move(r1));
|
||||
AlignedBuf aligned_buf;
|
||||
ASSERT_OK(r->MultiRead(reqs.data(), reqs.size(), &aligned_buf));
|
||||
|
||||
AssertResult(content, reqs);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -21,10 +21,10 @@
|
||||
#include "util/rate_limiter.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
Status WritableFileWriter::Append(const Slice& data) {
|
||||
IOStatus WritableFileWriter::Append(const Slice& data) {
|
||||
const char* src = data.data();
|
||||
size_t left = data.size();
|
||||
Status s;
|
||||
IOStatus s;
|
||||
pending_sync_ = true;
|
||||
|
||||
TEST_KILL_RANDOM("WritableFileWriter::Append:0",
|
||||
@@ -94,7 +94,7 @@ Status WritableFileWriter::Append(const Slice& data) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WritableFileWriter::Pad(const size_t pad_bytes) {
|
||||
IOStatus WritableFileWriter::Pad(const size_t pad_bytes) {
|
||||
assert(pad_bytes < kDefaultPageSize);
|
||||
size_t left = pad_bytes;
|
||||
size_t cap = buf_.Capacity() - buf_.CurrentSize();
|
||||
@@ -107,7 +107,7 @@ Status WritableFileWriter::Pad(const size_t pad_bytes) {
|
||||
buf_.PadWith(append_bytes, 0);
|
||||
left -= append_bytes;
|
||||
if (left > 0) {
|
||||
Status s = Flush();
|
||||
IOStatus s = Flush();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -116,12 +116,12 @@ Status WritableFileWriter::Pad(const size_t pad_bytes) {
|
||||
}
|
||||
pending_sync_ = true;
|
||||
filesize_ += pad_bytes;
|
||||
return Status::OK();
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
Status WritableFileWriter::Close() {
|
||||
IOStatus WritableFileWriter::Close() {
|
||||
// Do not quit immediately on failure the file MUST be closed
|
||||
Status s;
|
||||
IOStatus s;
|
||||
|
||||
// Possible to close it twice now as we MUST close
|
||||
// in __dtor, simply flushing is not enough
|
||||
@@ -133,7 +133,7 @@ Status WritableFileWriter::Close() {
|
||||
|
||||
s = Flush(); // flush cache to OS
|
||||
|
||||
Status interim;
|
||||
IOStatus interim;
|
||||
// In direct I/O mode we write whole pages so
|
||||
// we need to let the file know where data ends.
|
||||
if (use_direct_io()) {
|
||||
@@ -155,13 +155,18 @@ Status WritableFileWriter::Close() {
|
||||
writable_file_.reset();
|
||||
TEST_KILL_RANDOM("WritableFileWriter::Close:1", rocksdb_kill_odds);
|
||||
|
||||
if (s.ok() && checksum_generator_ != nullptr && !checksum_finalized_) {
|
||||
checksum_generator_->Finalize();
|
||||
checksum_finalized_ = true;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// write out the cached data to the OS cache or storage if direct I/O
|
||||
// enabled
|
||||
Status WritableFileWriter::Flush() {
|
||||
Status s;
|
||||
IOStatus WritableFileWriter::Flush() {
|
||||
IOStatus s;
|
||||
TEST_KILL_RANDOM("WritableFileWriter::Flush:0",
|
||||
rocksdb_kill_odds * REDUCE_ODDS2);
|
||||
|
||||
@@ -216,16 +221,24 @@ Status WritableFileWriter::Flush() {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string WritableFileWriter::GetFileChecksum() {
|
||||
if (checksum_generator_ != nullptr) {
|
||||
return checksum_generator_->GetChecksum();
|
||||
} else {
|
||||
return kUnknownFileChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
const char* WritableFileWriter::GetFileChecksumFuncName() const {
|
||||
if (checksum_func_ != nullptr) {
|
||||
return checksum_func_->Name();
|
||||
if (checksum_generator_ != nullptr) {
|
||||
return checksum_generator_->Name();
|
||||
} else {
|
||||
return kUnknownFileChecksumFuncName.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
Status WritableFileWriter::Sync(bool use_fsync) {
|
||||
Status s = Flush();
|
||||
IOStatus WritableFileWriter::Sync(bool use_fsync) {
|
||||
IOStatus s = Flush();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -238,23 +251,23 @@ Status WritableFileWriter::Sync(bool use_fsync) {
|
||||
}
|
||||
TEST_KILL_RANDOM("WritableFileWriter::Sync:1", rocksdb_kill_odds);
|
||||
pending_sync_ = false;
|
||||
return Status::OK();
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
Status WritableFileWriter::SyncWithoutFlush(bool use_fsync) {
|
||||
IOStatus WritableFileWriter::SyncWithoutFlush(bool use_fsync) {
|
||||
if (!writable_file_->IsSyncThreadSafe()) {
|
||||
return Status::NotSupported(
|
||||
return IOStatus::NotSupported(
|
||||
"Can't WritableFileWriter::SyncWithoutFlush() because "
|
||||
"WritableFile::IsSyncThreadSafe() is false");
|
||||
}
|
||||
TEST_SYNC_POINT("WritableFileWriter::SyncWithoutFlush:1");
|
||||
Status s = SyncInternal(use_fsync);
|
||||
IOStatus s = SyncInternal(use_fsync);
|
||||
TEST_SYNC_POINT("WritableFileWriter::SyncWithoutFlush:2");
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WritableFileWriter::SyncInternal(bool use_fsync) {
|
||||
Status s;
|
||||
IOStatus WritableFileWriter::SyncInternal(bool use_fsync) {
|
||||
IOStatus s;
|
||||
IOSTATS_TIMER_GUARD(fsync_nanos);
|
||||
TEST_SYNC_POINT("WritableFileWriter::SyncInternal:0");
|
||||
auto prev_perf_level = GetPerfLevel();
|
||||
@@ -268,7 +281,7 @@ Status WritableFileWriter::SyncInternal(bool use_fsync) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WritableFileWriter::RangeSync(uint64_t offset, uint64_t nbytes) {
|
||||
IOStatus WritableFileWriter::RangeSync(uint64_t offset, uint64_t nbytes) {
|
||||
IOSTATS_TIMER_GUARD(range_sync_nanos);
|
||||
TEST_SYNC_POINT("WritableFileWriter::RangeSync:0");
|
||||
return writable_file_->RangeSync(offset, nbytes, IOOptions(), nullptr);
|
||||
@@ -276,8 +289,8 @@ Status WritableFileWriter::RangeSync(uint64_t offset, uint64_t nbytes) {
|
||||
|
||||
// This method writes to disk the specified data and makes use of the rate
|
||||
// limiter if available
|
||||
Status WritableFileWriter::WriteBuffered(const char* data, size_t size) {
|
||||
Status s;
|
||||
IOStatus WritableFileWriter::WriteBuffered(const char* data, size_t size) {
|
||||
IOStatus s;
|
||||
assert(!use_direct_io());
|
||||
const char* src = data;
|
||||
size_t left = size;
|
||||
@@ -332,14 +345,8 @@ Status WritableFileWriter::WriteBuffered(const char* data, size_t size) {
|
||||
}
|
||||
|
||||
void WritableFileWriter::CalculateFileChecksum(const Slice& data) {
|
||||
if (checksum_func_ != nullptr) {
|
||||
if (is_first_checksum_) {
|
||||
file_checksum_ = checksum_func_->Value(data.data(), data.size());
|
||||
is_first_checksum_ = false;
|
||||
} else {
|
||||
file_checksum_ =
|
||||
checksum_func_->Extend(file_checksum_, data.data(), data.size());
|
||||
}
|
||||
if (checksum_generator_ != nullptr) {
|
||||
checksum_generator_->Update(data.data(), data.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,9 +359,9 @@ void WritableFileWriter::CalculateFileChecksum(const Slice& data) {
|
||||
// only write on aligned
|
||||
// offsets.
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status WritableFileWriter::WriteDirect() {
|
||||
IOStatus WritableFileWriter::WriteDirect() {
|
||||
assert(use_direct_io());
|
||||
Status s;
|
||||
IOStatus s;
|
||||
const size_t alignment = buf_.Alignment();
|
||||
assert((next_write_offset_ % alignment) == 0);
|
||||
|
||||
|
||||
+30
-21
@@ -15,6 +15,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/file_checksum.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/rate_limiter.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -36,11 +37,11 @@ class WritableFileWriter {
|
||||
void NotifyOnFileWriteFinish(uint64_t offset, size_t length,
|
||||
const FileOperationInfo::TimePoint& start_ts,
|
||||
const FileOperationInfo::TimePoint& finish_ts,
|
||||
const Status& status) {
|
||||
const IOStatus& io_status) {
|
||||
FileOperationInfo info(file_name_, start_ts, finish_ts);
|
||||
info.offset = offset;
|
||||
info.length = length;
|
||||
info.status = status;
|
||||
info.status = io_status;
|
||||
|
||||
for (auto& listener : listeners_) {
|
||||
listener->OnFileWriteFinish(info);
|
||||
@@ -71,9 +72,8 @@ class WritableFileWriter {
|
||||
RateLimiter* rate_limiter_;
|
||||
Statistics* stats_;
|
||||
std::vector<std::shared_ptr<EventListener>> listeners_;
|
||||
FileChecksumFunc* checksum_func_;
|
||||
std::string file_checksum_ = kUnknownFileChecksum;
|
||||
bool is_first_checksum_ = true;
|
||||
std::unique_ptr<FileChecksumGenerator> checksum_generator_;
|
||||
bool checksum_finalized_;
|
||||
|
||||
public:
|
||||
WritableFileWriter(
|
||||
@@ -81,7 +81,7 @@ class WritableFileWriter {
|
||||
const FileOptions& options, Env* env = nullptr,
|
||||
Statistics* stats = nullptr,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners = {},
|
||||
FileChecksumFunc* checksum_func = nullptr)
|
||||
FileChecksumGenFactory* file_checksum_gen_factory = nullptr)
|
||||
: writable_file_(std::move(file)),
|
||||
file_name_(_file_name),
|
||||
env_(env),
|
||||
@@ -97,7 +97,8 @@ class WritableFileWriter {
|
||||
rate_limiter_(options.rate_limiter),
|
||||
stats_(stats),
|
||||
listeners_(),
|
||||
checksum_func_(checksum_func) {
|
||||
checksum_generator_(nullptr),
|
||||
checksum_finalized_(false) {
|
||||
TEST_SYNC_POINT_CALLBACK("WritableFileWriter::WritableFileWriter:0",
|
||||
reinterpret_cast<void*>(max_buffer_size_));
|
||||
buf_.Alignment(writable_file_->GetRequiredBufferAlignment());
|
||||
@@ -112,6 +113,13 @@ class WritableFileWriter {
|
||||
#else // !ROCKSDB_LITE
|
||||
(void)listeners;
|
||||
#endif
|
||||
if (file_checksum_gen_factory != nullptr) {
|
||||
FileChecksumGenContext checksum_gen_context;
|
||||
checksum_gen_context.file_name = _file_name;
|
||||
checksum_generator_ =
|
||||
file_checksum_gen_factory->CreateFileChecksumGenerator(
|
||||
checksum_gen_context);
|
||||
}
|
||||
}
|
||||
|
||||
WritableFileWriter(const WritableFileWriter&) = delete;
|
||||
@@ -122,24 +130,24 @@ class WritableFileWriter {
|
||||
|
||||
std::string file_name() const { return file_name_; }
|
||||
|
||||
Status Append(const Slice& data);
|
||||
IOStatus Append(const Slice& data);
|
||||
|
||||
Status Pad(const size_t pad_bytes);
|
||||
IOStatus Pad(const size_t pad_bytes);
|
||||
|
||||
Status Flush();
|
||||
IOStatus Flush();
|
||||
|
||||
Status Close();
|
||||
IOStatus Close();
|
||||
|
||||
Status Sync(bool use_fsync);
|
||||
IOStatus Sync(bool use_fsync);
|
||||
|
||||
// Sync only the data that was already Flush()ed. Safe to call concurrently
|
||||
// with Append() and Flush(). If !writable_file_->IsSyncThreadSafe(),
|
||||
// returns NotSupported status.
|
||||
Status SyncWithoutFlush(bool use_fsync);
|
||||
IOStatus SyncWithoutFlush(bool use_fsync);
|
||||
|
||||
uint64_t GetFileSize() const { return filesize_; }
|
||||
|
||||
Status InvalidateCache(size_t offset, size_t length) {
|
||||
IOStatus InvalidateCache(size_t offset, size_t length) {
|
||||
return writable_file_->InvalidateCache(offset, length);
|
||||
}
|
||||
|
||||
@@ -149,11 +157,12 @@ class WritableFileWriter {
|
||||
|
||||
bool TEST_BufferIsEmpty() { return buf_.CurrentSize() == 0; }
|
||||
|
||||
void TEST_SetFileChecksumFunc(FileChecksumFunc* checksum_func) {
|
||||
checksum_func_ = checksum_func;
|
||||
void TEST_SetFileChecksumGenerator(
|
||||
FileChecksumGenerator* checksum_generator) {
|
||||
checksum_generator_.reset(checksum_generator);
|
||||
}
|
||||
|
||||
const std::string& GetFileChecksum() const { return file_checksum_; }
|
||||
std::string GetFileChecksum();
|
||||
|
||||
const char* GetFileChecksumFuncName() const;
|
||||
|
||||
@@ -161,11 +170,11 @@ class WritableFileWriter {
|
||||
// Used when os buffering is OFF and we are writing
|
||||
// DMA such as in Direct I/O mode
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status WriteDirect();
|
||||
IOStatus WriteDirect();
|
||||
#endif // !ROCKSDB_LITE
|
||||
// Normal write
|
||||
Status WriteBuffered(const char* data, size_t size);
|
||||
Status RangeSync(uint64_t offset, uint64_t nbytes);
|
||||
Status SyncInternal(bool use_fsync);
|
||||
IOStatus WriteBuffered(const char* data, size_t size);
|
||||
IOStatus RangeSync(uint64_t offset, uint64_t nbytes);
|
||||
IOStatus SyncInternal(bool use_fsync);
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -645,6 +645,7 @@ struct AdvancedColumnFamilyOptions {
|
||||
bool report_bg_io_stats = false;
|
||||
|
||||
// Files older than TTL will go through the compaction process.
|
||||
// Pre-req: This needs max_open_files to be set to -1.
|
||||
// In Level: Non-bottom-level files older than TTL will go through the
|
||||
// compation process.
|
||||
// In FIFO: Files older than TTL will be deleted.
|
||||
@@ -672,6 +673,7 @@ struct AdvancedColumnFamilyOptions {
|
||||
// Supported in Level and FIFO compaction.
|
||||
// In FIFO compaction, this option has the same meaning as TTL and whichever
|
||||
// stricter will be used.
|
||||
// Pre-req: max_open_file == -1.
|
||||
// unit: seconds. Ex: 7 days = 7 * 24 * 60 * 60
|
||||
//
|
||||
// Values:
|
||||
|
||||
@@ -480,6 +480,25 @@ class DB {
|
||||
keys, values);
|
||||
}
|
||||
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& /*options*/,
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_family*/,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* /*values*/,
|
||||
std::vector<std::string>* /*timestamps*/) {
|
||||
return std::vector<Status>(
|
||||
keys.size(), Status::NotSupported(
|
||||
"MultiGet() returning timestamps not implemented."));
|
||||
}
|
||||
virtual std::vector<Status> MultiGet(const ReadOptions& options,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) {
|
||||
return MultiGet(
|
||||
options,
|
||||
std::vector<ColumnFamilyHandle*>(keys.size(), DefaultColumnFamily()),
|
||||
keys, values, timestamps);
|
||||
}
|
||||
|
||||
// Overloaded MultiGet API that improves performance by batching operations
|
||||
// in the read path for greater efficiency. Currently, only the block based
|
||||
// table format with full filters are supported. Other table formats such
|
||||
@@ -521,6 +540,30 @@ class DB {
|
||||
}
|
||||
}
|
||||
|
||||
virtual void MultiGet(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses, const bool /*sorted_input*/ = false) {
|
||||
std::vector<ColumnFamilyHandle*> cf;
|
||||
std::vector<Slice> user_keys;
|
||||
std::vector<Status> status;
|
||||
std::vector<std::string> vals;
|
||||
std::vector<std::string> tss;
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
cf.emplace_back(column_family);
|
||||
user_keys.emplace_back(keys[i]);
|
||||
}
|
||||
status = MultiGet(options, cf, user_keys, &vals, &tss);
|
||||
std::copy(status.begin(), status.end(), statuses);
|
||||
std::copy(tss.begin(), tss.end(), timestamps);
|
||||
for (auto& value : vals) {
|
||||
values->PinSelf(value);
|
||||
values++;
|
||||
}
|
||||
}
|
||||
|
||||
// Overloaded MultiGet API that improves performance by batching operations
|
||||
// in the read path for greater efficiency. Currently, only the block based
|
||||
// table format with full filters are supported. Other table formats such
|
||||
@@ -560,6 +603,28 @@ class DB {
|
||||
values++;
|
||||
}
|
||||
}
|
||||
virtual void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses, const bool /*sorted_input*/ = false) {
|
||||
std::vector<ColumnFamilyHandle*> cf;
|
||||
std::vector<Slice> user_keys;
|
||||
std::vector<Status> status;
|
||||
std::vector<std::string> vals;
|
||||
std::vector<std::string> tss;
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
cf.emplace_back(column_families[i]);
|
||||
user_keys.emplace_back(keys[i]);
|
||||
}
|
||||
status = MultiGet(options, cf, user_keys, &vals, &tss);
|
||||
std::copy(status.begin(), status.end(), statuses);
|
||||
std::copy(tss.begin(), tss.end(), timestamps);
|
||||
for (auto& value : vals) {
|
||||
values->PinSelf(value);
|
||||
values++;
|
||||
}
|
||||
}
|
||||
|
||||
// If the key definitely does not exist in the database, then this method
|
||||
// returns false, else true. If the caller wants to obtain value when the key
|
||||
|
||||
+13
-1
@@ -57,6 +57,7 @@ struct MutableDBOptions;
|
||||
class RateLimiter;
|
||||
class ThreadStatusUpdater;
|
||||
struct ThreadStatus;
|
||||
class FileSystem;
|
||||
|
||||
const size_t kDefaultPageSize = 4 * 1024;
|
||||
|
||||
@@ -140,7 +141,9 @@ class Env {
|
||||
uint64_t size_bytes;
|
||||
};
|
||||
|
||||
Env() : thread_status_updater_(nullptr) {}
|
||||
Env();
|
||||
// Construct an Env with a separate FileSystem implementation
|
||||
Env(std::shared_ptr<FileSystem> fs);
|
||||
// No copying allowed
|
||||
Env(const Env&) = delete;
|
||||
void operator=(const Env&) = delete;
|
||||
@@ -539,12 +542,19 @@ class Env {
|
||||
|
||||
virtual void SanitizeEnvOptions(EnvOptions* /*env_opts*/) const {}
|
||||
|
||||
// Get the FileSystem implementation this Env was constructed with. It
|
||||
// could be a fully implemented one, or a wrapper class around the Env
|
||||
const std::shared_ptr<FileSystem>& GetFileSystem() const;
|
||||
|
||||
// If you're adding methods here, remember to add them to EnvWrapper too.
|
||||
|
||||
protected:
|
||||
// The pointer to an internal structure that will update the
|
||||
// status of each thread.
|
||||
ThreadStatusUpdater* thread_status_updater_;
|
||||
|
||||
// Pointer to the underlying FileSystem implementation
|
||||
std::shared_ptr<FileSystem> file_system_;
|
||||
};
|
||||
|
||||
// The factory function to construct a ThreadStatusUpdater. Any Env
|
||||
@@ -1603,4 +1613,6 @@ Env* NewTimedEnv(Env* base_env);
|
||||
Status NewEnvLogger(const std::string& fname, Env* env,
|
||||
std::shared_ptr<Logger>* result);
|
||||
|
||||
std::unique_ptr<Env> NewCompositeEnv(std::shared_ptr<FileSystem> fs);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -18,27 +18,44 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// FileChecksumFunc is the function class to generates the checksum value
|
||||
struct FileChecksumGenContext {
|
||||
std::string file_name;
|
||||
};
|
||||
|
||||
// FileChecksumGenerator is the class to generates the checksum value
|
||||
// for each file when the file is written to the file system.
|
||||
class FileChecksumFunc {
|
||||
class FileChecksumGenerator {
|
||||
public:
|
||||
virtual ~FileChecksumFunc() {}
|
||||
// Return the checksum of concat (A, data[0,n-1]) where init_checksum is the
|
||||
// returned value of some string A. It is used to maintain the checksum of a
|
||||
// stream of data
|
||||
virtual std::string Extend(const std::string& init_checksum, const char* data,
|
||||
size_t n) = 0;
|
||||
virtual ~FileChecksumGenerator() {}
|
||||
|
||||
// Return the checksum value of data[0,n-1]
|
||||
virtual std::string Value(const char* data, size_t n) = 0;
|
||||
// Update the current result after process the data. For different checksum
|
||||
// functions, the temporal results may be stored and used in Update to
|
||||
// include the new data.
|
||||
virtual void Update(const char* data, size_t n) = 0;
|
||||
|
||||
// Return a processed value of the checksum for store in somewhere
|
||||
virtual std::string ProcessChecksum(const std::string& checksum) = 0;
|
||||
// Generate the final results if no further new data will be updated.
|
||||
virtual void Finalize() = 0;
|
||||
|
||||
// Get the checksum
|
||||
virtual std::string GetChecksum() const = 0;
|
||||
|
||||
// Returns a name that identifies the current file checksum function.
|
||||
virtual const char* Name() const = 0;
|
||||
};
|
||||
|
||||
// Create the FileChecksumGenerator object for each SST file.
|
||||
class FileChecksumGenFactory {
|
||||
public:
|
||||
virtual ~FileChecksumGenFactory() {}
|
||||
|
||||
// Create a new FileChecksumGenerator.
|
||||
virtual std::unique_ptr<FileChecksumGenerator> CreateFileChecksumGenerator(
|
||||
const FileChecksumGenContext& context) = 0;
|
||||
|
||||
// Return the name of this FileChecksumGenFactory.
|
||||
virtual const char* Name() const = 0;
|
||||
};
|
||||
|
||||
// FileChecksumList stores the checksum information of a list of files (e.g.,
|
||||
// SST files). The FileChecksumLIst can be used to store the checksum
|
||||
// information of all SST file getting from the MANIFEST, which are
|
||||
@@ -80,7 +97,10 @@ class FileChecksumList {
|
||||
// Create a new file checksum list.
|
||||
extern FileChecksumList* NewFileChecksumList();
|
||||
|
||||
// Create a Crc32c based file checksum function
|
||||
extern FileChecksumFunc* CreateFileChecksumFuncCrc32c();
|
||||
// Return a shared_ptr of the builtin Crc32 based file checksum generatory
|
||||
// factory object, which can be shared to create the Crc32c based checksum
|
||||
// generator object.
|
||||
extern std::shared_ptr<FileChecksumGenFactory>
|
||||
GetFileChecksumGenCrc32cFactory();
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -102,6 +102,9 @@ struct FileOptions : EnvOptions {
|
||||
|
||||
FileOptions(const EnvOptions& opts)
|
||||
: EnvOptions(opts) {}
|
||||
|
||||
FileOptions(const FileOptions& opts)
|
||||
: EnvOptions(opts), io_options(opts.io_options) {}
|
||||
};
|
||||
|
||||
// A structure to pass back some debugging information from the FileSystem
|
||||
@@ -263,7 +266,7 @@ class FileSystem {
|
||||
const std::string& old_fname,
|
||||
const FileOptions& file_opts,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) = 0;
|
||||
IODebugContext* dbg);
|
||||
|
||||
// Open `fname` for random read and write, if file doesn't exist the file
|
||||
// will be created. On success, stores a pointer to the new file in
|
||||
@@ -465,6 +468,10 @@ class FileSystem {
|
||||
std::string* output_path,
|
||||
IODebugContext* dbg) = 0;
|
||||
|
||||
// Sanitize the FileOptions. Typically called by a FileOptions/EnvOptions
|
||||
// copy constructor
|
||||
virtual void SanitizeFileOptions(FileOptions* /*opts*/) const {}
|
||||
|
||||
// OptimizeForLogRead will create a new FileOptions object that is a copy of
|
||||
// the FileOptions in the parameters, but is optimized for reading log files.
|
||||
virtual FileOptions OptimizeForLogRead(const FileOptions& file_options) const;
|
||||
@@ -1001,11 +1008,13 @@ class FSDirectory {
|
||||
class FileSystemWrapper : public FileSystem {
|
||||
public:
|
||||
// Initialize an EnvWrapper that delegates all calls to *t
|
||||
explicit FileSystemWrapper(FileSystem* t) : target_(t) {}
|
||||
explicit FileSystemWrapper(std::shared_ptr<FileSystem> t) : target_(t) {}
|
||||
~FileSystemWrapper() override {}
|
||||
|
||||
const char* Name() const override { return target_->Name(); }
|
||||
|
||||
// Return the target to which this Env forwards all calls
|
||||
FileSystem* target() const { return target_; }
|
||||
FileSystem* target() const { return target_.get(); }
|
||||
|
||||
// The following text is boilerplate that forwards all methods to target()
|
||||
IOStatus NewSequentialFile(const std::string& f,
|
||||
@@ -1149,6 +1158,10 @@ class FileSystemWrapper : public FileSystem {
|
||||
return target_->NewLogger(fname, options, result, dbg);
|
||||
}
|
||||
|
||||
void SanitizeFileOptions(FileOptions* opts) const override {
|
||||
target_->SanitizeFileOptions(opts);
|
||||
}
|
||||
|
||||
FileOptions OptimizeForLogRead(
|
||||
const FileOptions& file_options) const override {
|
||||
return target_->OptimizeForLogRead(file_options);
|
||||
@@ -1182,7 +1195,7 @@ class FileSystemWrapper : public FileSystem {
|
||||
}
|
||||
|
||||
private:
|
||||
FileSystem* target_;
|
||||
std::shared_ptr<FileSystem> target_;
|
||||
};
|
||||
|
||||
class FSSequentialFileWrapper : public FSSequentialFile {
|
||||
@@ -1380,8 +1393,13 @@ class FSDirectoryWrapper : public FSDirectory {
|
||||
FSDirectory* target_;
|
||||
};
|
||||
|
||||
// A utility routine: write "data" to the named file.
|
||||
extern IOStatus WriteStringToFile(FileSystem* fs, const Slice& data,
|
||||
const std::string& fname,
|
||||
bool should_sync = false);
|
||||
|
||||
// A utility routine: read contents of named file into *data
|
||||
extern Status ReadFileToString(FileSystem* fs, const std::string& fname,
|
||||
std::string* data);
|
||||
extern IOStatus ReadFileToString(FileSystem* fs, const std::string& fname,
|
||||
std::string* data);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -227,4 +227,19 @@ inline bool IOStatus::operator!=(const IOStatus& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
inline IOStatus status_to_io_status(Status&& status) {
|
||||
if (status.ok()) {
|
||||
// Fast path
|
||||
return IOStatus::OK();
|
||||
} else {
|
||||
const char* state = status.getState();
|
||||
if (state) {
|
||||
return IOStatus(status.code(), status.subcode(),
|
||||
Slice(state, strlen(status.getState()) + 1), Slice());
|
||||
} else {
|
||||
return IOStatus(status.code(), status.subcode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -51,6 +51,13 @@ class InternalKeyComparator;
|
||||
class WalFilter;
|
||||
class FileSystem;
|
||||
|
||||
enum class CpuPriority {
|
||||
kIdle = 0,
|
||||
kLow = 1,
|
||||
kNormal = 2,
|
||||
kHigh = 3,
|
||||
};
|
||||
|
||||
// DB contents are stored in a set of blocks, each of which holds a
|
||||
// sequence of key,value pairs. Each block may be compressed before
|
||||
// being stored in a file. The following enum describes which
|
||||
@@ -396,11 +403,6 @@ struct DBOptions {
|
||||
// Default: Env::Default()
|
||||
Env* env = Env::Default();
|
||||
|
||||
// Use the specified object to interact with the storage to
|
||||
// read/write files. This is in addition to env. This option should be used
|
||||
// if the desired storage subsystem provides a FileSystem implementation.
|
||||
std::shared_ptr<FileSystem> file_system = nullptr;
|
||||
|
||||
// Use to control write rate of flush and compaction. Flush has higher
|
||||
// priority than compaction. Rate limiting is disabled if nullptr.
|
||||
// If rate limiter is enabled, bytes_per_sync is set to 1MB by default.
|
||||
@@ -1124,12 +1126,24 @@ struct DBOptions {
|
||||
// Default: 0
|
||||
size_t log_readahead_size = 0;
|
||||
|
||||
// If user does NOT provide SST file checksum function, the SST file checksum
|
||||
// will NOT be used. The single checksum instance are shared by options and
|
||||
// file writers. Make sure the algorithm is thread safe.
|
||||
// If user does NOT provide the checksum generator factory, the file checksum
|
||||
// will NOT be used. A new file checksum generator object will be created
|
||||
// when a SST file is created. Therefore, each created FileChecksumGenerator
|
||||
// will only be used from a single thread and so does not need to be
|
||||
// thread-safe.
|
||||
//
|
||||
// Default: nullptr
|
||||
std::shared_ptr<FileChecksumFunc> sst_file_checksum_func = nullptr;
|
||||
std::shared_ptr<FileChecksumGenFactory> file_checksum_gen_factory = nullptr;
|
||||
|
||||
// By default, RocksDB recovery fails if any table file referenced in
|
||||
// MANIFEST are missing after scanning the MANIFEST.
|
||||
// Best-efforts recovery is another recovery mode that
|
||||
// tries to restore the database to the most recent point in time without
|
||||
// missing file.
|
||||
// Currently not compatible with atomic flush. Furthermore, WAL files will
|
||||
// not be used for recovery if best_efforts_recovery is true.
|
||||
// Default: false
|
||||
bool best_efforts_recovery = false;
|
||||
};
|
||||
|
||||
// Options to control the behavior of a database (passed to DB::Open)
|
||||
|
||||
@@ -272,7 +272,7 @@ struct BlockBasedTableOptions {
|
||||
// 5 -- Can be read by RocksDB's versions since 6.6.0. Full and partitioned
|
||||
// filters use a generally faster and more accurate Bloom filter
|
||||
// implementation, with a different schema.
|
||||
uint32_t format_version = 2;
|
||||
uint32_t format_version = 4;
|
||||
|
||||
// Store index blocks on disk in compressed format. Changing this option to
|
||||
// false will avoid the overhead of decompression if index blocks are evicted
|
||||
|
||||
@@ -136,6 +136,11 @@ class TablePropertiesCollectorFactory {
|
||||
|
||||
// The name of the properties collector can be used for debugging purpose.
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
// Can be overridden by sub-classes to return the Name, followed by
|
||||
// configuration info that will // be logged to the info log when the
|
||||
// DB is opened
|
||||
virtual std::string ToString() const { return Name(); }
|
||||
};
|
||||
|
||||
// TableProperties contains a bunch of read-only properties of its associated
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "rocksdb/utilities/stackable_db.h"
|
||||
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -142,6 +143,24 @@ struct BackupableDBOptions {
|
||||
}
|
||||
};
|
||||
|
||||
struct CreateBackupOptions {
|
||||
// Flush will always trigger if 2PC is enabled.
|
||||
// If write-ahead logs are disabled, set flush_before_backup=true to
|
||||
// avoid losing unflushed key/value pairs from the memtable.
|
||||
bool flush_before_backup = false;
|
||||
|
||||
// Callback for reporting progress.
|
||||
std::function<void()> progress_callback = []() {};
|
||||
|
||||
// If false, background_thread_cpu_priority is ignored.
|
||||
// Otherwise, the cpu priority can be decreased,
|
||||
// if you try to increase the priority, the priority will not change.
|
||||
// The initial priority of the threads is CpuPriority::kNormal,
|
||||
// so you can decrease to priorities lower than kNormal.
|
||||
bool decrease_background_thread_cpu_priority = false;
|
||||
CpuPriority background_thread_cpu_priority = CpuPriority::kNormal;
|
||||
};
|
||||
|
||||
struct RestoreOptions {
|
||||
// If true, restore won't overwrite the existing log files in wal_dir. It will
|
||||
// also move all log files from archive directory to wal_dir. Use this option
|
||||
@@ -208,8 +227,13 @@ class BackupEngineReadOnly {
|
||||
public:
|
||||
virtual ~BackupEngineReadOnly() {}
|
||||
|
||||
static Status Open(Env* db_env, const BackupableDBOptions& options,
|
||||
static Status Open(const BackupableDBOptions& options, Env* db_env,
|
||||
BackupEngineReadOnly** backup_engine_ptr);
|
||||
// keep for backward compatibility.
|
||||
static Status Open(Env* db_env, const BackupableDBOptions& options,
|
||||
BackupEngineReadOnly** backup_engine_ptr) {
|
||||
return BackupEngineReadOnly::Open(options, db_env, backup_engine_ptr);
|
||||
}
|
||||
|
||||
// Returns info about backups in backup_info
|
||||
// You can GetBackupInfo safely, even with other BackupEngine performing
|
||||
@@ -225,14 +249,29 @@ class BackupEngineReadOnly {
|
||||
// responsibility to synchronize the operation, i.e. don't delete the backup
|
||||
// when you're restoring from it
|
||||
// See also the corresponding doc in BackupEngine
|
||||
virtual Status RestoreDBFromBackup(const RestoreOptions& options,
|
||||
BackupID backup_id,
|
||||
const std::string& db_dir,
|
||||
const std::string& wal_dir) = 0;
|
||||
|
||||
// keep for backward compatibility.
|
||||
virtual Status RestoreDBFromBackup(
|
||||
BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
const RestoreOptions& options = RestoreOptions()) {
|
||||
return RestoreDBFromBackup(options, backup_id, db_dir, wal_dir);
|
||||
}
|
||||
|
||||
// See the corresponding doc in BackupEngine
|
||||
virtual Status RestoreDBFromLatestBackup(const RestoreOptions& options,
|
||||
const std::string& db_dir,
|
||||
const std::string& wal_dir) = 0;
|
||||
|
||||
// keep for backward compatibility.
|
||||
virtual Status RestoreDBFromLatestBackup(
|
||||
const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
const RestoreOptions& options = RestoreOptions()) {
|
||||
return RestoreDBFromLatestBackup(options, db_dir, wal_dir);
|
||||
}
|
||||
|
||||
// checks that each file exists and that the size of the file matches our
|
||||
// expectations. it does not check file checksum.
|
||||
@@ -253,27 +292,44 @@ class BackupEngine {
|
||||
|
||||
// BackupableDBOptions have to be the same as the ones used in previous
|
||||
// BackupEngines for the same backup directory.
|
||||
static Status Open(Env* db_env, const BackupableDBOptions& options,
|
||||
static Status Open(const BackupableDBOptions& options, Env* db_env,
|
||||
BackupEngine** backup_engine_ptr);
|
||||
|
||||
// same as CreateNewBackup, but stores extra application metadata
|
||||
// Flush will always trigger if 2PC is enabled.
|
||||
// If write-ahead logs are disabled, set flush_before_backup=true to
|
||||
// avoid losing unflushed key/value pairs from the memtable.
|
||||
// keep for backward compatibility.
|
||||
static Status Open(Env* db_env, const BackupableDBOptions& options,
|
||||
BackupEngine** backup_engine_ptr) {
|
||||
return BackupEngine::Open(options, db_env, backup_engine_ptr);
|
||||
}
|
||||
|
||||
// same as CreateNewBackup, but stores extra application metadata.
|
||||
virtual Status CreateNewBackupWithMetadata(
|
||||
const CreateBackupOptions& options, DB* db,
|
||||
const std::string& app_metadata) = 0;
|
||||
|
||||
// keep here for backward compatibility.
|
||||
virtual Status CreateNewBackupWithMetadata(
|
||||
DB* db, const std::string& app_metadata, bool flush_before_backup = false,
|
||||
std::function<void()> progress_callback = []() {}) = 0;
|
||||
std::function<void()> progress_callback = []() {}) {
|
||||
CreateBackupOptions options;
|
||||
options.flush_before_backup = flush_before_backup;
|
||||
options.progress_callback = progress_callback;
|
||||
return CreateNewBackupWithMetadata(options, db, app_metadata);
|
||||
}
|
||||
|
||||
// Captures the state of the database in the latest backup
|
||||
// NOT a thread safe call
|
||||
// Flush will always trigger if 2PC is enabled.
|
||||
// If write-ahead logs are disabled, set flush_before_backup=true to
|
||||
// avoid losing unflushed key/value pairs from the memtable.
|
||||
virtual Status CreateNewBackup(const CreateBackupOptions& options, DB* db) {
|
||||
return CreateNewBackupWithMetadata(options, db, "");
|
||||
}
|
||||
|
||||
// keep here for backward compatibility.
|
||||
virtual Status CreateNewBackup(DB* db, bool flush_before_backup = false,
|
||||
std::function<void()> progress_callback =
|
||||
[]() {}) {
|
||||
return CreateNewBackupWithMetadata(db, "", flush_before_backup,
|
||||
progress_callback);
|
||||
CreateBackupOptions options;
|
||||
options.flush_before_backup = flush_before_backup;
|
||||
options.progress_callback = progress_callback;
|
||||
return CreateNewBackup(options, db);
|
||||
}
|
||||
|
||||
// Deletes old backups, keeping latest num_backups_to_keep alive.
|
||||
@@ -313,14 +369,29 @@ class BackupEngine {
|
||||
// database will diverge from backups 4 and 5 and the new backup will fail.
|
||||
// If you want to create new backup, you will first have to delete backups 4
|
||||
// and 5.
|
||||
virtual Status RestoreDBFromBackup(const RestoreOptions& options,
|
||||
BackupID backup_id,
|
||||
const std::string& db_dir,
|
||||
const std::string& wal_dir) = 0;
|
||||
|
||||
// keep for backward compatibility.
|
||||
virtual Status RestoreDBFromBackup(
|
||||
BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
const RestoreOptions& options = RestoreOptions()) {
|
||||
return RestoreDBFromBackup(options, backup_id, db_dir, wal_dir);
|
||||
}
|
||||
|
||||
// restore from the latest backup
|
||||
virtual Status RestoreDBFromLatestBackup(const RestoreOptions& options,
|
||||
const std::string& db_dir,
|
||||
const std::string& wal_dir) = 0;
|
||||
|
||||
// keep for backward compatibility.
|
||||
virtual Status RestoreDBFromLatestBackup(
|
||||
const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
const RestoreOptions& options = RestoreOptions()) {
|
||||
return RestoreDBFromLatestBackup(options, db_dir, wal_dir);
|
||||
}
|
||||
|
||||
// checks that each file exists and that the size of the file matches our
|
||||
// expectations. it does not check file checksum.
|
||||
|
||||
@@ -18,9 +18,9 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class CompactOnDeletionCollectorFactory
|
||||
: public TablePropertiesCollectorFactory {
|
||||
public:
|
||||
virtual ~CompactOnDeletionCollectorFactory() {}
|
||||
~CompactOnDeletionCollectorFactory() {}
|
||||
|
||||
virtual TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollectorFactory::Context context) override;
|
||||
|
||||
// Change the value of sliding_window_size "N"
|
||||
@@ -34,10 +34,12 @@ class CompactOnDeletionCollectorFactory
|
||||
deletion_trigger_.store(deletion_trigger);
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
const char* Name() const override {
|
||||
return "CompactOnDeletionCollector";
|
||||
}
|
||||
|
||||
std::string ToString() const override;
|
||||
|
||||
private:
|
||||
friend std::shared_ptr<CompactOnDeletionCollectorFactory>
|
||||
NewCompactOnDeletionCollectorFactory(size_t sliding_window_size,
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 6
|
||||
#define ROCKSDB_MINOR 8
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 9
|
||||
#define ROCKSDB_PATCH 4
|
||||
|
||||
// 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
|
||||
|
||||
@@ -69,7 +69,9 @@ WriteBufferManager::~WriteBufferManager() {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (cache_rep_) {
|
||||
for (auto* handle : cache_rep_->dummy_handles_) {
|
||||
cache_rep_->cache_->Release(handle, true);
|
||||
if (handle != nullptr) {
|
||||
cache_rep_->cache_->Release(handle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -88,9 +90,16 @@ void WriteBufferManager::ReserveMemWithCache(size_t mem) {
|
||||
while (new_mem_used > cache_rep_->cache_allocated_size_) {
|
||||
// Expand size by at least 256KB.
|
||||
// Add a dummy record to the cache
|
||||
Cache::Handle* handle;
|
||||
Cache::Handle* handle = nullptr;
|
||||
cache_rep_->cache_->Insert(cache_rep_->GetNextCacheKey(), nullptr,
|
||||
kSizeDummyEntry, nullptr, &handle);
|
||||
// We keep the handle even if insertion fails and a null handle is
|
||||
// returned, so that when memory shrinks, we don't release extra
|
||||
// entries from cache.
|
||||
// Ideallly we should prevent this allocation from happening if
|
||||
// this insertion fails. However, the callers to this code path
|
||||
// are not able to handle failures properly. We'll need to improve
|
||||
// it in the future.
|
||||
cache_rep_->dummy_handles_.push_back(handle);
|
||||
cache_rep_->cache_allocated_size_ += kSizeDummyEntry;
|
||||
}
|
||||
@@ -119,7 +128,11 @@ void WriteBufferManager::FreeMemWithCache(size_t mem) {
|
||||
if (new_mem_used < cache_rep_->cache_allocated_size_ / 4 * 3 &&
|
||||
cache_rep_->cache_allocated_size_ - kSizeDummyEntry > new_mem_used) {
|
||||
assert(!cache_rep_->dummy_handles_.empty());
|
||||
cache_rep_->cache_->Release(cache_rep_->dummy_handles_.back(), true);
|
||||
auto* handle = cache_rep_->dummy_handles_.back();
|
||||
// If insert failed, handle is null so we should not release.
|
||||
if (handle != nullptr) {
|
||||
cache_rep_->cache_->Release(handle, true);
|
||||
}
|
||||
cache_rep_->dummy_handles_.pop_back();
|
||||
cache_rep_->cache_allocated_size_ -= kSizeDummyEntry;
|
||||
}
|
||||
|
||||
@@ -146,6 +146,35 @@ TEST_F(WriteBufferManagerTest, NoCapCacheCost) {
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 1024 * 1024);
|
||||
ASSERT_LT(cache->GetPinnedUsage(), 1024 * 1024 + 10000);
|
||||
}
|
||||
|
||||
TEST_F(WriteBufferManagerTest, CacheFull) {
|
||||
// 15MB cache size with strict capacity
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = 12 * 1024 * 1024;
|
||||
lo.num_shard_bits = 0;
|
||||
lo.strict_capacity_limit = true;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
std::unique_ptr<WriteBufferManager> wbf(new WriteBufferManager(0, cache));
|
||||
wbf->ReserveMem(10 * 1024 * 1024);
|
||||
size_t prev_pinned = cache->GetPinnedUsage();
|
||||
ASSERT_GE(prev_pinned, 10 * 1024 * 1024);
|
||||
// Some insert will fail
|
||||
wbf->ReserveMem(10 * 1024 * 1024);
|
||||
ASSERT_LE(cache->GetPinnedUsage(), 12 * 1024 * 1024);
|
||||
|
||||
// Increase capacity so next insert will succeed
|
||||
cache->SetCapacity(30 * 1024 * 1024);
|
||||
wbf->ReserveMem(10 * 1024 * 1024);
|
||||
ASSERT_GT(cache->GetPinnedUsage(), 20 * 1024 * 1024);
|
||||
|
||||
// Gradually release 20 MB
|
||||
for (int i = 0; i < 40; i++) {
|
||||
wbf->FreeMem(512 * 1024);
|
||||
}
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 10 * 1024 * 1024);
|
||||
ASSERT_LT(cache->GetPinnedUsage(), 20 * 1024 * 1024);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -57,9 +57,6 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options,
|
||||
cf_options.purge_redundant_kvs_while_flush),
|
||||
use_fsync(db_options.use_fsync),
|
||||
compression_per_level(cf_options.compression_per_level),
|
||||
bottommost_compression(cf_options.bottommost_compression),
|
||||
bottommost_compression_opts(cf_options.bottommost_compression_opts),
|
||||
compression_opts(cf_options.compression_opts),
|
||||
level_compaction_dynamic_level_bytes(
|
||||
cf_options.level_compaction_dynamic_level_bytes),
|
||||
access_hint_on_compaction_start(
|
||||
@@ -78,7 +75,7 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options,
|
||||
cf_options.memtable_insert_with_hint_prefix_extractor.get()),
|
||||
cf_paths(cf_options.cf_paths),
|
||||
compaction_thread_limiter(cf_options.compaction_thread_limiter),
|
||||
sst_file_checksum_func(db_options.sst_file_checksum_func.get()) {}
|
||||
file_checksum_gen_factory(db_options.file_checksum_gen_factory.get()) {}
|
||||
|
||||
// Multiple two operands. If they overflow, return op1.
|
||||
uint64_t MultiplyCheckOverflow(uint64_t op1, double op2) {
|
||||
|
||||
@@ -90,12 +90,6 @@ struct ImmutableCFOptions {
|
||||
|
||||
std::vector<CompressionType> compression_per_level;
|
||||
|
||||
CompressionType bottommost_compression;
|
||||
|
||||
CompressionOptions bottommost_compression_opts;
|
||||
|
||||
CompressionOptions compression_opts;
|
||||
|
||||
bool level_compaction_dynamic_level_bytes;
|
||||
|
||||
Options::AccessHint access_hint_on_compaction_start;
|
||||
@@ -126,7 +120,7 @@ struct ImmutableCFOptions {
|
||||
|
||||
std::shared_ptr<ConcurrentTaskLimiter> compaction_thread_limiter;
|
||||
|
||||
FileChecksumFunc* sst_file_checksum_func;
|
||||
FileChecksumGenFactory* file_checksum_gen_factory;
|
||||
};
|
||||
|
||||
struct MutableCFOptions {
|
||||
@@ -166,6 +160,9 @@ struct MutableCFOptions {
|
||||
paranoid_file_checks(options.paranoid_file_checks),
|
||||
report_bg_io_stats(options.report_bg_io_stats),
|
||||
compression(options.compression),
|
||||
bottommost_compression(options.bottommost_compression),
|
||||
compression_opts(options.compression_opts),
|
||||
bottommost_compression_opts(options.bottommost_compression_opts),
|
||||
sample_for_compression(options.sample_for_compression) {
|
||||
RefreshDerivedOptions(options.num_levels, options.compaction_style);
|
||||
}
|
||||
@@ -198,6 +195,7 @@ struct MutableCFOptions {
|
||||
paranoid_file_checks(false),
|
||||
report_bg_io_stats(false),
|
||||
compression(Snappy_Supported() ? kSnappyCompression : kNoCompression),
|
||||
bottommost_compression(kDisableCompressionOption),
|
||||
sample_for_compression(0) {}
|
||||
|
||||
explicit MutableCFOptions(const Options& options);
|
||||
@@ -253,6 +251,10 @@ struct MutableCFOptions {
|
||||
bool paranoid_file_checks;
|
||||
bool report_bg_io_stats;
|
||||
CompressionType compression;
|
||||
CompressionType bottommost_compression;
|
||||
CompressionOptions compression_opts;
|
||||
CompressionOptions bottommost_compression_opts;
|
||||
|
||||
uint64_t sample_for_compression;
|
||||
|
||||
// Derived options
|
||||
|
||||
@@ -26,7 +26,7 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
error_if_exists(options.error_if_exists),
|
||||
paranoid_checks(options.paranoid_checks),
|
||||
env(options.env),
|
||||
fs(options.file_system),
|
||||
fs(options.env->GetFileSystem()),
|
||||
rate_limiter(options.rate_limiter),
|
||||
sst_file_manager(options.sst_file_manager),
|
||||
info_log(options.info_log),
|
||||
@@ -95,7 +95,8 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
persist_stats_to_disk(options.persist_stats_to_disk),
|
||||
write_dbid_to_manifest(options.write_dbid_to_manifest),
|
||||
log_readahead_size(options.log_readahead_size),
|
||||
sst_file_checksum_func(options.sst_file_checksum_func) {
|
||||
file_checksum_gen_factory(options.file_checksum_gen_factory),
|
||||
best_efforts_recovery(options.best_efforts_recovery) {
|
||||
}
|
||||
|
||||
void ImmutableDBOptions::Dump(Logger* log) const {
|
||||
@@ -246,10 +247,12 @@ void ImmutableDBOptions::Dump(Logger* log) const {
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " Options.log_readahead_size: %" ROCKSDB_PRIszt,
|
||||
log_readahead_size);
|
||||
ROCKS_LOG_HEADER(log, " Options.sst_file_checksum_func: %s",
|
||||
sst_file_checksum_func
|
||||
? sst_file_checksum_func->Name()
|
||||
ROCKS_LOG_HEADER(log, " Options.file_checksum_gen_factory: %s",
|
||||
file_checksum_gen_factory
|
||||
? file_checksum_gen_factory->Name()
|
||||
: kUnknownFileChecksumFuncName.c_str());
|
||||
ROCKS_LOG_HEADER(log, " Options.best_efforts_recovery: %d",
|
||||
static_cast<int>(best_efforts_recovery));
|
||||
}
|
||||
|
||||
MutableDBOptions::MutableDBOptions()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user