mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abd1fdddef | ||
|
|
3a6e83b56b | ||
|
|
ec2b996b29 | ||
|
|
9f5cfb8e71 | ||
|
|
8a008d4170 | ||
|
|
22ce462450 | ||
|
|
a3c1832e86 | ||
|
|
74fb7f0ba5 | ||
|
|
0f4d90e6e4 | ||
|
|
699a569c52 | ||
|
|
0acaa1a846 | ||
|
|
3bde41b5a3 | ||
|
|
cd2520361d | ||
|
|
6e8a1354a7 | ||
|
|
b0259e45e0 | ||
|
|
f064d74e45 | ||
|
|
68d43b4d30 | ||
|
|
61876614dc | ||
|
|
3e9c5a3523 | ||
|
|
1a59b6e2a9 | ||
|
|
82d8ca8ade | ||
|
|
60d8b19836 | ||
|
|
f786b4a5b4 | ||
|
|
aa0367aabb | ||
|
|
cb19e7411f | ||
|
|
a6a9213a36 | ||
|
|
872a261ffc | ||
|
|
6ca3feed5c | ||
|
|
7c76a7fba2 | ||
|
|
8d34806972 | ||
|
|
e0d9d57750 | ||
|
|
2de61d9129 | ||
|
|
4f66ec977d | ||
|
|
e4dcf5fd22 | ||
|
|
6edc5d0719 | ||
|
|
84c5c9aab1 | ||
|
|
09ea5d8944 | ||
|
|
0d57d93a06 | ||
|
|
662ce62044 | ||
|
|
cfdf2116d3 | ||
|
|
66464d1fde | ||
|
|
3e6c185381 | ||
|
|
1e87f2b68b | ||
|
|
f872009237 | ||
|
|
9f0bd56889 | ||
|
|
3886dddc3b | ||
|
|
c360675750 | ||
|
|
7259e28d91 | ||
|
|
68b46a2e36 | ||
|
|
10bae8ceb3 | ||
|
|
5c2f13fb14 | ||
|
|
15fd3be07b | ||
|
|
c08c0ae731 | ||
|
|
a8975b6245 | ||
|
|
9dbcda9e3b | ||
|
|
b4d7209428 | ||
|
|
554a6456aa | ||
|
|
acb80534ca | ||
|
|
e731f44022 | ||
|
|
c92c58f84d | ||
|
|
68980df89c | ||
|
|
22028aa9ab | ||
|
|
2730fe693e | ||
|
|
1bfeffab2d | ||
|
|
705b8eecb4 | ||
|
|
0b0cb6f1a2 | ||
|
|
24f73436fb | ||
|
|
68614a9608 | ||
|
|
5830c619d5 | ||
|
|
24b118ad98 | ||
|
|
fe90ed7a70 | ||
|
|
5355e527d9 | ||
|
|
2e8ad03ab3 | ||
|
|
f46a2a0375 | ||
|
|
92f631da33 | ||
|
|
d0c6aea192 | ||
|
|
220870523c |
No files matched your search
@@ -32,6 +32,7 @@ ldb
|
||||
manifest_dump
|
||||
sst_dump
|
||||
blob_dump
|
||||
block_cache_trace_analyzer
|
||||
column_aware_encoding_exp
|
||||
util/build_version.cc
|
||||
build_tools/VALGRIND_LOGS/
|
||||
|
||||
@@ -520,6 +520,7 @@ set(SOURCES
|
||||
db/flush_job.cc
|
||||
db/flush_scheduler.cc
|
||||
db/forward_iterator.cc
|
||||
db/import_column_family_job.cc
|
||||
db/internal_stats.cc
|
||||
db/logs_with_prep_tracker.cc
|
||||
db/log_reader.cc
|
||||
@@ -595,6 +596,7 @@ set(SOURCES
|
||||
table/block_based/block_prefix_index.cc
|
||||
table/block_based/data_block_hash_index.cc
|
||||
table/block_based/data_block_footer.cc
|
||||
table/block_based/filter_block_reader_common.cc
|
||||
table/block_based/flush_block_policy.cc
|
||||
table/block_based/full_filter_block.cc
|
||||
table/block_based/index_builder.cc
|
||||
@@ -685,6 +687,7 @@ set(SOURCES
|
||||
utilities/persistent_cache/block_cache_tier_metadata.cc
|
||||
utilities/persistent_cache/persistent_cache_tier.cc
|
||||
utilities/persistent_cache/volatile_tier_impl.cc
|
||||
utilities/simulator_cache/cache_simulator.cc
|
||||
utilities/simulator_cache/sim_cache.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
utilities/trace/file_trace_reader_writer.cc
|
||||
@@ -948,6 +951,7 @@ if(WITH_TESTS)
|
||||
env/mock_env_test.cc
|
||||
file/delete_scheduler_test.cc
|
||||
logging/auto_roll_logger_test.cc
|
||||
logging/env_logger_test.cc
|
||||
logging/event_logger_test.cc
|
||||
memory/arena_test.cc
|
||||
memtable/inlineskiplist_test.cc
|
||||
@@ -1004,6 +1008,7 @@ if(WITH_TESTS)
|
||||
utilities/options/options_util_test.cc
|
||||
utilities/persistent_cache/hash_table_test.cc
|
||||
utilities/persistent_cache/persistent_cache_test.cc
|
||||
utilities/simulator_cache/cache_simulator_test.cc
|
||||
utilities/simulator_cache/sim_cache_test.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
|
||||
utilities/transactions/optimistic_transaction_test.cc
|
||||
|
||||
+16
-29
@@ -1,52 +1,36 @@
|
||||
# Rocksdb Change Log
|
||||
## 6.3.6 (10/1/2019)
|
||||
* Revert the feature "Merging iterator to avoid child iterator reseek for some cases (#5286)" since it might cause strong results when reseek happens with a different iterator upper bound.
|
||||
## Unreleased
|
||||
### Default Option Change
|
||||
* LRUCacheOptions.high_pri_pool_ratio is set to 0.5 (previously 0.0) by default, which means that by default midpoint insertion is enabled. The same change is made for the default value of high_pri_pool_ratio argument in NewLRUCache(). When block cache is not explictly created, the small block cache created by BlockBasedTable will still has this option to be 0.0.
|
||||
* Change BlockBasedTableOptions.cache_index_and_filter_blocks_with_high_priority's default value from false to true.
|
||||
|
||||
## 6.3.5 (9/17/2019)
|
||||
* Fix a bug introduced 6.3 which could cause wrong results in a corner case when prefix bloom filter is used and the iterator is reseeked.
|
||||
|
||||
## 6.3.4 (9/3/2019)
|
||||
### Bug Fixes
|
||||
* Fix a bug in file ingestion caused by incorrect file number allocation when the number of column families involved in the ingestion exceeds 2.
|
||||
|
||||
## 6.3.3 (8/20/2019)
|
||||
### Bug Fixes
|
||||
* Fix a bug where the compaction snapshot refresh feature is not disabled as advertised when `snap_refresh_nanos` is set to 0..
|
||||
|
||||
## 6.3.2 (8/15/2019)
|
||||
### Public API Change
|
||||
* The semantics of the per-block-type block read counts in the performance context now match those of the generic block_read_count.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a regression where the fill_cache read option also affected index blocks.
|
||||
* Fixed an issue where using cache_index_and_filter_blocks==false affected partitions of partitioned indexes as well.
|
||||
|
||||
## 6.3.1 (7/24/2019)
|
||||
### Bug Fixes
|
||||
* Fix auto rolling bug introduced in 6.3.0, which causes segfault if log file creation fails.
|
||||
|
||||
## 6.3.0 (6/18/2019)
|
||||
### Public API Change
|
||||
* Now DB::Close() will return Aborted() error when there is unreleased snapshot. Users can retry after all snapshots are released.
|
||||
* Index blocks are now handled similarly to data blocks with regards to the block cache: instead of storing objects in the cache, only the blocks themselves are cached. In addition, index blocks no longer get evicted from the cache when a table is closed, can now use the compressed block cache (if any), and can be shared among multiple table readers.
|
||||
* Index and filter blocks are now handled similarly to data blocks with regards to the block cache: instead of storing reader objects in the cache, only the blocks themselves are cached. In addition, index and filter blocks (as well as filter partitions) no longer get evicted from the cache when a table is closed. Moreover, index blocks can now use the compressed block cache (if any).
|
||||
* Partitions of partitioned indexes no longer affect the read amplification statistics.
|
||||
* Due to the above refactoring, block cache eviction statistics for indexes are temporarily broken. We plan to reintroduce them in a later phase.
|
||||
* Due to the above refactoring, block cache eviction statistics for indexes and filters are temporarily broken. We plan to reintroduce them in a later phase.
|
||||
* options.keep_log_file_num will be enforced strictly all the time. File names of all log files will be tracked, which may take significantly amount of memory if options.keep_log_file_num is large and either of options.max_log_file_size or options.log_file_time_to_roll is set.
|
||||
* Add initial support for Get/Put with user timestamps. Users can specify timestamps via ReadOptions and WriteOptions when calling DB::Get and DB::Put.
|
||||
* Accessing a partition of a partitioned filter or index through a pinned reference is no longer considered a cache hit.
|
||||
* The semantics of the per-block-type block read counts in the performance context now match those of the generic block_read_count.
|
||||
* Add C bindings for secondary instance, i.e. DBImplSecondary.
|
||||
* db_bench adds a "benchmark" stats_history, which prints out the whole stats history.
|
||||
* Rate limited deletion of WALs is only enabled if DBOptions::wal_dir is not set, or explicitly set to db_name passed to DB::Open and DBOptions::db_paths is empty, or same as db_paths[0].path
|
||||
* Overload GetAllKeyVersions() to support non-default column family.
|
||||
* Added new APIs ExportColumnFamily() and CreateColumnFamilyWithImport() to support export and import of a Column Family. https://github.com/facebook/rocksdb/issues/3469
|
||||
|
||||
### New Features
|
||||
* Add an option `snap_refresh_nanos` (default to 0) to periodically refresh the snapshot list in compaction jobs. Assign to 0 to disable the feature.
|
||||
* Add an option `snap_refresh_nanos` (default to 0.1s) to periodically refresh the snapshot list in compaction jobs. Assign to 0 to disable the feature.
|
||||
* Add an option `unordered_write` which trades snapshot guarantees with higher write throughput. When used with WRITE_PREPARED transactions with two_write_queues=true, it offers higher throughput with however no compromise on guarantees.
|
||||
* Allow DBImplSecondary to remove memtables with obsolete data after replaying MANIFEST and WAL.
|
||||
* Add an option `failed_move_fall_back_to_copy` (default is true) for external SST ingestion. When `move_files` is true and hard link fails, ingestion falls back to copy if `failed_move_fall_back_to_copy` is true. Otherwise, ingestion reports an error.
|
||||
* Add argument `--secondary_path` to ldb to open the database as the secondary instance. This would keep the original DB intact.
|
||||
|
||||
### Performance Improvements
|
||||
* Reduce binary search when iterator reseek into the same data block.
|
||||
* DBIter::Next() can skip user key checking if previous entry's seqnum is 0.
|
||||
* Merging iterator to avoid child iterator reseek for some cases
|
||||
* Reduce iterator key comparision for upper/lower bound check.
|
||||
* Log Writer will flush after finishing the whole record, rather than a fragment.
|
||||
* Lower MultiGet batching API latency by reading data blocks from disk in parallel
|
||||
|
||||
@@ -58,6 +42,8 @@
|
||||
* Fix a bug in WAL replay of secondary instance by skipping write batches with older sequence numbers than the current last sequence number.
|
||||
* Fix flush's/compaction's merge processing logic which allowed `Put`s covered by range tombstones to reappear. Note `Put`s may exist even if the user only ever called `Merge()` due to an internal conversion during compaction to the bottommost level.
|
||||
* Fix/improve memtable earliest sequence assignment and WAL replay so that WAL entries of unflushed column families will not be skipped after replaying the MANIFEST and increasing db sequence due to another flushed/compacted column family.
|
||||
* Fix ingested file and directory not being fsync.
|
||||
* Return TryAgain status in place of Corruption when new tail is not visible to TransactionLogIterator.
|
||||
* Fix a bug caused by secondary not skipping the beginning of new MANIFEST.
|
||||
* On DB open, delete WAL trash files left behind in wal_dir
|
||||
|
||||
@@ -69,6 +55,7 @@
|
||||
* Block-based table index now contains exact highest key in the file, rather than an upper bound. This may improve Get() and iterator Seek() performance in some situations, especially when direct IO is enabled and block cache is disabled. A setting BlockBasedTableOptions::index_shortening is introduced to control this behavior. Set it to kShortenSeparatorsAndSuccessor to get the old behavior.
|
||||
* When reading from option file/string/map, customized envs can be filled according to object registry.
|
||||
* Improve range scan performance when using explicit user readahead by not creating new table readers for every iterator.
|
||||
* Add index type BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey. It significantly reduces read amplification in some setups, especially for iterator seeks. It's not fully implemented yet: IO errors are not handled right.
|
||||
|
||||
### Public API Change
|
||||
* Change the behavior of OptimizeForPointLookup(): move away from hash-based block-based-table index, and use whole key memtable filtering.
|
||||
|
||||
@@ -144,8 +144,8 @@ HAVE_POWER8=1
|
||||
endif
|
||||
|
||||
ifeq (,$(shell $(CXX) -fsyntax-only -march=armv8-a+crc -xc /dev/null 2>&1))
|
||||
CXXFLAGS += -march=armv8-a+crc
|
||||
CFLAGS += -march=armv8-a+crc
|
||||
CXXFLAGS += -march=armv8-a+crc+crypto
|
||||
CFLAGS += -march=armv8-a+crc+crypto
|
||||
ARMCRC_SOURCE=1
|
||||
endif
|
||||
|
||||
@@ -432,6 +432,7 @@ TESTS = \
|
||||
inlineskiplist_test \
|
||||
env_basic_test \
|
||||
env_test \
|
||||
env_logger_test \
|
||||
hash_test \
|
||||
thread_local_test \
|
||||
rate_limiter_test \
|
||||
@@ -499,6 +500,7 @@ TESTS = \
|
||||
plain_table_db_test \
|
||||
comparator_db_test \
|
||||
external_sst_file_test \
|
||||
import_column_family_test \
|
||||
prefix_test \
|
||||
skiplist_test \
|
||||
write_buffer_manager_test \
|
||||
@@ -509,6 +511,7 @@ TESTS = \
|
||||
cassandra_serialize_test \
|
||||
ttl_test \
|
||||
backupable_db_test \
|
||||
cache_simulator_test \
|
||||
sim_cache_test \
|
||||
version_edit_test \
|
||||
version_set_test \
|
||||
@@ -575,6 +578,7 @@ PARALLEL_TEST = \
|
||||
db_universal_compaction_test \
|
||||
db_wal_test \
|
||||
external_sst_file_test \
|
||||
import_column_family_test \
|
||||
fault_injection_test \
|
||||
inlineskiplist_test \
|
||||
manual_compaction_test \
|
||||
@@ -1272,6 +1276,9 @@ external_sst_file_basic_test: db/external_sst_file_basic_test.o db/db_test_util.
|
||||
external_sst_file_test: db/external_sst_file_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
import_column_family_test: db/import_column_family_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_tailing_iter_test: db/db_tailing_iter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1320,6 +1327,9 @@ backupable_db_test: utilities/backupable/backupable_db_test.o $(LIBOBJECTS) $(TE
|
||||
checkpoint_test: utilities/checkpoint/checkpoint_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
cache_simulator_test: utilities/simulator_cache/cache_simulator_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sim_cache_test: utilities/simulator_cache/sim_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1529,6 +1539,9 @@ filelock_test: util/filelock_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
auto_roll_logger_test: logging/auto_roll_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
env_logger_test: logging/env_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
memtable_list_test: db/memtable_list_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1649,7 +1662,7 @@ JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux
|
||||
ifeq ($(PLATFORM), OS_SOLARIS)
|
||||
ARCH := $(shell isainfo -b)
|
||||
else ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
ifneq (,$(filter $(MACHINE), amd64 arm64 aarch64 sparc64))
|
||||
ifneq (,$(filter $(MACHINE), amd64 arm64 sparc64 aarch64))
|
||||
ARCH := 64
|
||||
else
|
||||
ARCH := 32
|
||||
@@ -1658,11 +1671,14 @@ else
|
||||
ARCH := $(shell getconf LONG_BIT)
|
||||
endif
|
||||
|
||||
ifeq (,$(filter $(MACHINE), ppc arm64 aarch64 sparc64))
|
||||
ifeq (,$(findstring ppc,$(MACHINE)))
|
||||
ROCKSDBJNILIB = librocksdbjni-linux$(ARCH).so
|
||||
else
|
||||
ROCKSDBJNILIB = librocksdbjni-linux-$(MACHINE).so
|
||||
endif
|
||||
ifneq (,$(findstring aarch64,$(MACHINE)))
|
||||
ROCKSDBJNILIB = librocksdbjni-linux-$(MACHINE).so
|
||||
endif
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-linux$(ARCH).jar
|
||||
ROCKSDB_JAR_ALL = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH).jar
|
||||
ROCKSDB_JAVADOCS_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-javadoc.jar
|
||||
@@ -1859,14 +1875,6 @@ rocksdbjavastaticdockerppc64le:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_ppc64le-be --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerarm64v8:
|
||||
mkdir -p java/target
|
||||
DOCKER_LINUX_ARM64V8_CONTAINER=`docker ps -aqf name=rocksdb_linux_arm64v8-be`; \
|
||||
if [ -z "$$DOCKER_LINUX_ARM64V8_CONTAINER" ]; then \
|
||||
docker container create --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --name rocksdb_linux_arm64v8-be evolvedbinary/rocksjava:centos7_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh; \
|
||||
fi
|
||||
docker start -a rocksdb_linux_arm64v8-be
|
||||
|
||||
rocksdbjavastaticpublish: rocksdbjavastaticrelease rocksdbjavastaticpublishcentral
|
||||
|
||||
rocksdbjavastaticpublishdocker: rocksdbjavastaticreleasedocker rocksdbjavastaticpublishcentral
|
||||
|
||||
@@ -113,6 +113,7 @@ cpp_library(
|
||||
"db/flush_job.cc",
|
||||
"db/flush_scheduler.cc",
|
||||
"db/forward_iterator.cc",
|
||||
"db/import_column_family_job.cc",
|
||||
"db/internal_stats.cc",
|
||||
"db/log_reader.cc",
|
||||
"db/log_writer.cc",
|
||||
@@ -192,6 +193,7 @@ cpp_library(
|
||||
"table/block_based/block_prefix_index.cc",
|
||||
"table/block_based/data_block_footer.cc",
|
||||
"table/block_based/data_block_hash_index.cc",
|
||||
"table/block_based/filter_block_reader_common.cc",
|
||||
"table/block_based/flush_block_policy.cc",
|
||||
"table/block_based/full_filter_block.cc",
|
||||
"table/block_based/index_builder.cc",
|
||||
@@ -280,6 +282,7 @@ cpp_library(
|
||||
"utilities/persistent_cache/block_cache_tier_metadata.cc",
|
||||
"utilities/persistent_cache/persistent_cache_tier.cc",
|
||||
"utilities/persistent_cache/volatile_tier_impl.cc",
|
||||
"utilities/simulator_cache/cache_simulator.cc",
|
||||
"utilities/simulator_cache/sim_cache.cc",
|
||||
"utilities/table_properties_collectors/compact_on_deletion_collector.cc",
|
||||
"utilities/trace/file_trace_reader_writer.cc",
|
||||
@@ -367,6 +370,11 @@ ROCKS_TESTS = [
|
||||
"logging/auto_roll_logger_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"env_logger_test",
|
||||
"logging/env_logger_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"autovector_test",
|
||||
"util/autovector_test.cc",
|
||||
@@ -417,6 +425,11 @@ ROCKS_TESTS = [
|
||||
"cache/cache_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"cache_simulator_test",
|
||||
"utilities/simulator_cache/cache_simulator_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"cassandra_format_test",
|
||||
"utilities/cassandra/cassandra_format_test.cc",
|
||||
|
||||
+59
-7
@@ -1,15 +1,67 @@
|
||||
version: 1.0.{build}
|
||||
|
||||
image: Visual Studio 2017
|
||||
|
||||
environment:
|
||||
JAVA_HOME: C:\Program Files\Java\jdk1.8.0
|
||||
THIRDPARTY_HOME: $(APPVEYOR_BUILD_FOLDER)\thirdparty
|
||||
SNAPPY_HOME: $(THIRDPARTY_HOME)\snappy-1.1.7
|
||||
SNAPPY_INCLUDE: $(SNAPPY_HOME);$(SNAPPY_HOME)\build
|
||||
SNAPPY_LIB_DEBUG: $(SNAPPY_HOME)\build\Debug\snappy.lib
|
||||
SNAPPY_LIB_RELEASE: $(SNAPPY_HOME)\build\Release\snappy.lib
|
||||
LZ4_HOME: $(THIRDPARTY_HOME)\lz4-1.8.3
|
||||
LZ4_INCLUDE: $(LZ4_HOME)\lib
|
||||
LZ4_LIB_DEBUG: $(LZ4_HOME)\visual\VS2010\bin\x64_Debug\liblz4_static.lib
|
||||
LZ4_LIB_RELEASE: $(LZ4_HOME)\visual\VS2010\bin\x64_Release\liblz4_static.lib
|
||||
ZSTD_HOME: $(THIRDPARTY_HOME)\zstd-1.4.0
|
||||
ZSTD_INCLUDE: $(ZSTD_HOME)\lib;$(ZSTD_HOME)\lib\dictBuilder
|
||||
ZSTD_LIB_DEBUG: $(ZSTD_HOME)\build\VS2010\bin\x64_Debug\libzstd_static.lib
|
||||
ZSTD_LIB_RELEASE: $(ZSTD_HOME)\build\VS2010\bin\x64_Release\libzstd_static.lib
|
||||
|
||||
install:
|
||||
- md %THIRDPARTY_HOME%
|
||||
- echo "Building Snappy dependency..."
|
||||
- cd %THIRDPARTY_HOME%
|
||||
- curl -fsSL -o snappy-1.1.7.zip https://github.com/google/snappy/archive/1.1.7.zip
|
||||
- unzip snappy-1.1.7.zip
|
||||
- cd snappy-1.1.7
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..
|
||||
- msbuild Snappy.sln /p:Configuration=Debug /p:Platform=x64
|
||||
- msbuild Snappy.sln /p:Configuration=Release /p:Platform=x64
|
||||
- echo "Building LZ4 dependency..."
|
||||
- cd %THIRDPARTY_HOME%
|
||||
- curl -fsSL -o lz4-1.8.3.zip https://github.com/lz4/lz4/archive/v1.8.3.zip
|
||||
- unzip lz4-1.8.3.zip
|
||||
- cd lz4-1.8.3\visual\VS2010
|
||||
- ps: $CMD="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.com"; & $CMD lz4.sln /upgrade
|
||||
- msbuild lz4.sln /p:Configuration=Debug /p:Platform=x64
|
||||
- msbuild lz4.sln /p:Configuration=Release /p:Platform=x64
|
||||
- echo "Building ZStd dependency..."
|
||||
- cd %THIRDPARTY_HOME%
|
||||
- curl -fsSL -o zstd-1.4.0.zip https://github.com/facebook/zstd/archive/v1.4.0.zip
|
||||
- unzip zstd-1.4.0.zip
|
||||
- cd zstd-1.4.0\build\VS2010
|
||||
- ps: $CMD="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.com"; & $CMD zstd.sln /upgrade
|
||||
- msbuild zstd.sln /p:Configuration=Debug /p:Platform=x64
|
||||
- msbuild zstd.sln /p:Configuration=Release /p:Platform=x64
|
||||
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 15 Win64" -DOPTDBG=1 -DWITH_XPRESS=1 -DPORTABLE=1 -DJNI=1 ..
|
||||
- cd ..
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 15 Win64" -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DLZ4=1 -DZSTD=1 -DXPRESS=1 -DJNI=1 ..
|
||||
- cd ..
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
parallel: true
|
||||
verbosity: normal
|
||||
test:
|
||||
test_script:
|
||||
- ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test2,db_test,env_basic_test,env_test -Concurrency 8
|
||||
|
||||
test:
|
||||
|
||||
test_script:
|
||||
- ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test2,db_test,env_basic_test,env_test -Concurrency 8
|
||||
|
||||
on_failure:
|
||||
- cmd: 7z a build-failed.zip %APPVEYOR_BUILD_FOLDER%\build\ && appveyor PushArtifact build-failed.zip
|
||||
|
||||
@@ -56,10 +56,10 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
# we need this to build with MySQL. Don't use for other purposes.
|
||||
source "$PWD/build_tools/fbcode_config4.8.1.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_5xx" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -63,6 +63,21 @@ CLEANUP_ENV="
|
||||
'user':'root'
|
||||
}"
|
||||
|
||||
UPLOAD_DB_DIR="
|
||||
{
|
||||
'name':'Upload database directory',
|
||||
'shell':'tar -cvzf rocksdb_db.tar.gz /dev/shm/rocksdb/',
|
||||
'user':'root',
|
||||
'cleanup':true,
|
||||
'provide_artifacts': [
|
||||
{
|
||||
'name':'rocksdb_db_dir',
|
||||
'paths': ['rocksdb_db.tar.gz'],
|
||||
'bundle': false,
|
||||
},
|
||||
],
|
||||
}"
|
||||
|
||||
# We will eventually set the RATIO to 1, but we want do this
|
||||
# in steps. RATIO=$(nproc) will make it work as J=1
|
||||
if [ -z $RATIO ]; then
|
||||
@@ -428,7 +443,8 @@ STRESS_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -519,6 +535,7 @@ ASAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -586,6 +603,7 @@ UBSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -678,6 +696,7 @@ TSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
|
||||
Vendored
+4
-2
@@ -90,7 +90,7 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
bool strict_capacity_limit) {
|
||||
auto type = GetParam();
|
||||
if (type == kLRU) {
|
||||
return NewLRUCache(capacity, num_shard_bits, strict_capacity_limit);
|
||||
return NewLRUCache(capacity, num_shard_bits, strict_capacity_limit, 0.0);
|
||||
}
|
||||
if (type == kClock) {
|
||||
return NewClockCache(capacity, num_shard_bits, strict_capacity_limit);
|
||||
@@ -562,6 +562,7 @@ TEST_P(CacheTest, SetStrictCapacityLimit) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(nullptr, handles[i]);
|
||||
}
|
||||
ASSERT_EQ(10, cache->GetUsage());
|
||||
|
||||
// test2: set the flag to true. Insert and check if it fails.
|
||||
std::string extra_key = "extra";
|
||||
@@ -571,6 +572,7 @@ TEST_P(CacheTest, SetStrictCapacityLimit) {
|
||||
s = cache->Insert(extra_key, extra_value, 1, &deleter, &handle);
|
||||
ASSERT_TRUE(s.IsIncomplete());
|
||||
ASSERT_EQ(nullptr, handle);
|
||||
ASSERT_EQ(10, cache->GetUsage());
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
cache->Release(handles[i]);
|
||||
@@ -591,7 +593,7 @@ TEST_P(CacheTest, SetStrictCapacityLimit) {
|
||||
s = cache2->Insert(extra_key, extra_value, 1, &deleter);
|
||||
// AS if the key have been inserted into cache but get evicted immediately.
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(5, cache->GetUsage());
|
||||
ASSERT_EQ(5, cache2->GetUsage());
|
||||
ASSERT_EQ(nullptr, cache2->Lookup(extra_key));
|
||||
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
|
||||
Vendored
+62
-76
@@ -24,7 +24,7 @@ LRUHandleTable::LRUHandleTable() : list_(nullptr), length_(0), elems_(0) {
|
||||
|
||||
LRUHandleTable::~LRUHandleTable() {
|
||||
ApplyToAllCacheEntries([](LRUHandle* h) {
|
||||
if (h->refs == 1) {
|
||||
if (!h->HasRefs()) {
|
||||
h->Free();
|
||||
}
|
||||
});
|
||||
@@ -113,29 +113,17 @@ LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
SetCapacity(capacity);
|
||||
}
|
||||
|
||||
LRUCacheShard::~LRUCacheShard() {}
|
||||
|
||||
bool LRUCacheShard::Unref(LRUHandle* e) {
|
||||
assert(e->refs > 0);
|
||||
e->refs--;
|
||||
return e->refs == 0;
|
||||
}
|
||||
|
||||
// Call deleter and free
|
||||
|
||||
void LRUCacheShard::EraseUnRefEntries() {
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
while (lru_.next != &lru_) {
|
||||
LRUHandle* old = lru_.next;
|
||||
assert(old->InCache());
|
||||
assert(old->refs ==
|
||||
1); // LRU list contains elements which may be evicted
|
||||
// LRU list contains only elements which can be evicted
|
||||
assert(old->InCache() && !old->HasRefs());
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old->key(), old->hash);
|
||||
old->SetInCache(false);
|
||||
Unref(old);
|
||||
usage_ -= old->charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
@@ -148,22 +136,27 @@ void LRUCacheShard::EraseUnRefEntries() {
|
||||
|
||||
void LRUCacheShard::ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) {
|
||||
const auto applyCallback = [&]() {
|
||||
table_.ApplyToAllCacheEntries(
|
||||
[callback](LRUHandle* h) { callback(h->value, h->charge); });
|
||||
};
|
||||
|
||||
if (thread_safe) {
|
||||
mutex_.Lock();
|
||||
}
|
||||
table_.ApplyToAllCacheEntries(
|
||||
[callback](LRUHandle* h) { callback(h->value, h->charge); });
|
||||
if (thread_safe) {
|
||||
mutex_.Unlock();
|
||||
MutexLock l(&mutex_);
|
||||
applyCallback();
|
||||
} else {
|
||||
applyCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri) {
|
||||
MutexLock l(&mutex_);
|
||||
*lru = &lru_;
|
||||
*lru_low_pri = lru_low_pri_;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::TEST_GetLRUSize() {
|
||||
MutexLock l(&mutex_);
|
||||
LRUHandle* lru_handle = lru_.next;
|
||||
size_t lru_size = 0;
|
||||
while (lru_handle != &lru_) {
|
||||
@@ -231,14 +224,13 @@ void LRUCacheShard::MaintainPoolSize() {
|
||||
|
||||
void LRUCacheShard::EvictFromLRU(size_t charge,
|
||||
autovector<LRUHandle*>* deleted) {
|
||||
while (usage_ + charge > capacity_ && lru_.next != &lru_) {
|
||||
while ((usage_ + charge) > capacity_ && lru_.next != &lru_) {
|
||||
LRUHandle* old = lru_.next;
|
||||
assert(old->InCache());
|
||||
assert(old->refs == 1); // LRU list contains elements which may be evicted
|
||||
// LRU list contains only elements which can be evicted
|
||||
assert(old->InCache() && !old->HasRefs());
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old->key(), old->hash);
|
||||
old->SetInCache(false);
|
||||
Unref(old);
|
||||
usage_ -= old->charge;
|
||||
deleted->push_back(old);
|
||||
}
|
||||
@@ -252,8 +244,8 @@ void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
high_pri_pool_capacity_ = capacity_ * high_pri_pool_ratio_;
|
||||
EvictFromLRU(0, &last_reference_list);
|
||||
}
|
||||
// we free the entries here outside of mutex for
|
||||
// performance reasons
|
||||
|
||||
// Free the entries outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
entry->Free();
|
||||
}
|
||||
@@ -269,22 +261,22 @@ Cache::Handle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) {
|
||||
LRUHandle* e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (e->refs == 1) {
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
}
|
||||
e->refs++;
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
return reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
|
||||
bool LRUCacheShard::Ref(Cache::Handle* h) {
|
||||
LRUHandle* handle = reinterpret_cast<LRUHandle*>(h);
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(h);
|
||||
MutexLock l(&mutex_);
|
||||
if (handle->InCache() && handle->refs == 1) {
|
||||
LRU_Remove(handle);
|
||||
}
|
||||
handle->refs++;
|
||||
// To create another reference - entry must be already externally referenced
|
||||
assert(e->HasRefs());
|
||||
e->Ref();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -303,30 +295,27 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool force_erase) {
|
||||
bool last_reference = false;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
last_reference = Unref(e);
|
||||
last_reference = e->Unref();
|
||||
if (last_reference && e->InCache()) {
|
||||
// The item is still in cache, and nobody else holds a reference to it
|
||||
if (usage_ > capacity_ || force_erase) {
|
||||
// The LRU list must be empty since the cache is full
|
||||
assert(lru_.next == &lru_ || force_erase);
|
||||
// Take this opportunity and remove the item
|
||||
table_.Remove(e->key(), e->hash);
|
||||
e->SetInCache(false);
|
||||
} else {
|
||||
// Put the item back on the LRU list, and don't free it
|
||||
LRU_Insert(e);
|
||||
last_reference = false;
|
||||
}
|
||||
}
|
||||
if (last_reference) {
|
||||
usage_ -= e->charge;
|
||||
}
|
||||
if (e->refs == 1 && e->InCache()) {
|
||||
// The item is still in cache, and nobody else holds a reference to it
|
||||
if (usage_ > capacity_ || force_erase) {
|
||||
// the cache is full
|
||||
// The LRU list must be empty since the cache is full
|
||||
assert(!(usage_ > capacity_) || lru_.next == &lru_);
|
||||
// take this opportunity and remove the item
|
||||
table_.Remove(e->key(), e->hash);
|
||||
e->SetInCache(false);
|
||||
Unref(e);
|
||||
usage_ -= e->charge;
|
||||
last_reference = true;
|
||||
} else {
|
||||
// put the item on the list to be potentially freed
|
||||
LRU_Insert(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// free outside of mutex
|
||||
// Free the entry here outside of mutex for performance reasons
|
||||
if (last_reference) {
|
||||
e->Free();
|
||||
}
|
||||
@@ -342,7 +331,7 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
// It shouldn't happen very often though.
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
Status s;
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
|
||||
e->value = value;
|
||||
@@ -351,9 +340,7 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
e->key_length = key.size();
|
||||
e->flags = 0;
|
||||
e->hash = hash;
|
||||
e->refs = (handle == nullptr
|
||||
? 1
|
||||
: 2); // One from LRUCache, one for the returned handle
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetInCache(true);
|
||||
e->SetPriority(priority);
|
||||
@@ -366,11 +353,12 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
// is freed or the lru list is empty
|
||||
EvictFromLRU(charge, &last_reference_list);
|
||||
|
||||
if (usage_ - lru_usage_ + charge > capacity_ &&
|
||||
if ((usage_ + charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
if (handle == nullptr) {
|
||||
// Don't insert the entry but still return ok, as if the entry inserted
|
||||
// into cache and get evicted immediately.
|
||||
e->SetInCache(false);
|
||||
last_reference_list.push_back(e);
|
||||
} else {
|
||||
delete[] reinterpret_cast<char*>(e);
|
||||
@@ -378,32 +366,30 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
s = Status::Incomplete("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
// insert into the cache
|
||||
// note that the cache might get larger than its capacity if not enough
|
||||
// space was freed
|
||||
// Insert into the cache. Note that the cache might get larger than its
|
||||
// capacity if not enough space was freed up.
|
||||
LRUHandle* old = table_.Insert(e);
|
||||
usage_ += e->charge;
|
||||
if (old != nullptr) {
|
||||
assert(old->InCache());
|
||||
old->SetInCache(false);
|
||||
if (Unref(old)) {
|
||||
usage_ -= old->charge;
|
||||
// old is on LRU because it's in cache and its reference count
|
||||
// was just 1 (Unref returned 0)
|
||||
if (!old->HasRefs()) {
|
||||
// old is on LRU because it's in cache and its reference count is 0
|
||||
LRU_Remove(old);
|
||||
usage_ -= old->charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
LRU_Insert(e);
|
||||
} else {
|
||||
e->Ref();
|
||||
*handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
s = Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
// we free the entries here outside of mutex for
|
||||
// performance reasons
|
||||
// Free the entries here outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
entry->Free();
|
||||
}
|
||||
@@ -418,18 +404,18 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
MutexLock l(&mutex_);
|
||||
e = table_.Remove(key, hash);
|
||||
if (e != nullptr) {
|
||||
last_reference = Unref(e);
|
||||
if (last_reference) {
|
||||
usage_ -= e->charge;
|
||||
}
|
||||
if (last_reference && e->InCache()) {
|
||||
LRU_Remove(e);
|
||||
}
|
||||
assert(e->InCache());
|
||||
e->SetInCache(false);
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
usage_ -= e->charge;
|
||||
last_reference = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mutex not held here
|
||||
// Free the entry here outside of mutex for performance reasons
|
||||
// last_reference will only be true if e != nullptr
|
||||
if (last_reference) {
|
||||
e->Free();
|
||||
|
||||
Vendored
+40
-36
@@ -17,31 +17,34 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// LRU cache implementation
|
||||
// LRU cache implementation. This class is not thread-safe.
|
||||
|
||||
// An entry is a variable length heap-allocated structure.
|
||||
// Entries are referenced by cache and/or by any external entity.
|
||||
// The cache keeps all its entries in table. Some elements
|
||||
// The cache keeps all its entries in a hash table. Some elements
|
||||
// are also stored on LRU list.
|
||||
//
|
||||
// LRUHandle can be in these states:
|
||||
// 1. Referenced externally AND in hash table.
|
||||
// In that case the entry is *not* in the LRU. (refs > 1 && in_cache == true)
|
||||
// 2. Not referenced externally and in hash table. In that case the entry is
|
||||
// in the LRU and can be freed. (refs == 1 && in_cache == true)
|
||||
// 3. Referenced externally and not in hash table. In that case the entry is
|
||||
// in not on LRU and not in table. (refs >= 1 && in_cache == false)
|
||||
// In that case the entry is *not* in the LRU list
|
||||
// (refs >= 1 && in_cache == true)
|
||||
// 2. Not referenced externally AND in hash table.
|
||||
// In that case the entry is in the LRU list and can be freed.
|
||||
// (refs == 0 && in_cache == true)
|
||||
// 3. Referenced externally AND not in hash table.
|
||||
// In that case the entry is not in the LRU list and not in hash table.
|
||||
// The entry can be freed when refs becomes 0.
|
||||
// (refs >= 1 && in_cache == false)
|
||||
//
|
||||
// All newly created LRUHandles are in state 1. If you call
|
||||
// LRUCacheShard::Release
|
||||
// on entry in state 1, it will go into state 2. To move from state 1 to
|
||||
// state 3, either call LRUCacheShard::Erase or LRUCacheShard::Insert with the
|
||||
// same key.
|
||||
// LRUCacheShard::Release on entry in state 1, it will go into state 2.
|
||||
// To move from state 1 to state 3, either call LRUCacheShard::Erase or
|
||||
// LRUCacheShard::Insert with the same key (but possibly different value).
|
||||
// To move from state 2 to state 1, use LRUCacheShard::Lookup.
|
||||
// Before destruction, make sure that no handles are in state 1. This means
|
||||
// that any successful LRUCacheShard::Lookup/LRUCacheShard::Insert have a
|
||||
// matching
|
||||
// RUCache::Release (to move into state 2) or LRUCacheShard::Erase (for state 3)
|
||||
// matching LRUCache::Release (to move into state 2) or LRUCacheShard::Erase
|
||||
// (to move into state 3).
|
||||
|
||||
struct LRUHandle {
|
||||
void* value;
|
||||
@@ -51,37 +54,42 @@ struct LRUHandle {
|
||||
LRUHandle* prev;
|
||||
size_t charge; // TODO(opt): Only allow uint32_t?
|
||||
size_t key_length;
|
||||
uint32_t refs; // a number of refs to this entry
|
||||
// cache itself is counted as 1
|
||||
// The hash of key(). Used for fast sharding and comparisons.
|
||||
uint32_t hash;
|
||||
// The number of external refs to this entry. The cache itself is not counted.
|
||||
uint32_t refs;
|
||||
|
||||
// Include the following flags:
|
||||
// IN_CACHE: whether this entry is referenced by the hash table.
|
||||
// IS_HIGH_PRI: whether this entry is high priority entry.
|
||||
// IN_HIGH_PRI_POOL: whether this entry is in high-pri pool.
|
||||
// HAS_HIT: whether this entry has had any lookups (hits).
|
||||
enum Flags : uint8_t {
|
||||
// Whether this entry is referenced by the hash table.
|
||||
IN_CACHE = (1 << 0),
|
||||
// Whether this entry is high priority entry.
|
||||
IS_HIGH_PRI = (1 << 1),
|
||||
// Whether this entry is in high-pri pool.
|
||||
IN_HIGH_PRI_POOL = (1 << 2),
|
||||
// Wwhether this entry has had any lookups (hits).
|
||||
HAS_HIT = (1 << 3),
|
||||
};
|
||||
|
||||
uint8_t flags;
|
||||
|
||||
uint32_t hash; // Hash of key(); used for fast sharding and comparisons
|
||||
// Beginning of the key (MUST BE THE LAST FIELD IN THIS STRUCT!)
|
||||
char key_data[1];
|
||||
|
||||
char key_data[1]; // Beginning of key
|
||||
Slice key() const { return Slice(key_data, key_length); }
|
||||
|
||||
Slice key() const {
|
||||
// For cheaper lookups, we allow a temporary Handle object
|
||||
// to store a pointer to a key in "value".
|
||||
if (next == this) {
|
||||
return *(reinterpret_cast<Slice*>(value));
|
||||
} else {
|
||||
return Slice(key_data, key_length);
|
||||
}
|
||||
// Increase the reference count by 1.
|
||||
void Ref() { refs++; }
|
||||
|
||||
// Just reduce the reference count by 1. Return true if it was last reference.
|
||||
bool Unref() {
|
||||
assert(refs > 0);
|
||||
refs--;
|
||||
return refs == 0;
|
||||
}
|
||||
|
||||
// Return true if there are external refs, false otherwise.
|
||||
bool HasRefs() const { return refs > 0; }
|
||||
|
||||
bool InCache() const { return flags & IN_CACHE; }
|
||||
bool IsHighPri() const { return flags & IS_HIGH_PRI; }
|
||||
bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
|
||||
@@ -114,7 +122,7 @@ struct LRUHandle {
|
||||
void SetHit() { flags |= HAS_HIT; }
|
||||
|
||||
void Free() {
|
||||
assert((refs == 1 && InCache()) || (refs == 0 && !InCache()));
|
||||
assert(refs == 0);
|
||||
if (deleter) {
|
||||
(*deleter)(key(), value);
|
||||
}
|
||||
@@ -169,7 +177,7 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, bool use_adaptive_mutex);
|
||||
virtual ~LRUCacheShard();
|
||||
virtual ~LRUCacheShard() override = default;
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
// if current usage is more than new capacity, the function will attempt to
|
||||
@@ -225,10 +233,6 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// high-pri pool is no larger than the size specify by high_pri_pool_pct.
|
||||
void MaintainPoolSize();
|
||||
|
||||
// Just reduce the reference count by 1.
|
||||
// Return true if last reference
|
||||
bool Unref(LRUHandle* e);
|
||||
|
||||
// Free some space following strict LRU policy until enough space
|
||||
// to hold (usage_ + charge) is freed or the lru list is empty
|
||||
// This function is not thread safe - it needs to be executed while
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
import re
|
||||
import sys
|
||||
|
||||
+3
-2
@@ -221,8 +221,9 @@ Status BuildTable(
|
||||
mutable_cf_options.prefix_extractor.get(), nullptr,
|
||||
(internal_stats == nullptr) ? nullptr
|
||||
: internal_stats->GetFileReadHist(0),
|
||||
false /* for_compaction */, nullptr /* arena */,
|
||||
false /* skip_filter */, level));
|
||||
TableReaderCaller::kFlush, /*arena=*/nullptr,
|
||||
/*skip_filter=*/false, level, /*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key*/ nullptr));
|
||||
s = it->status();
|
||||
if (s.ok() && paranoid_file_checks) {
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
|
||||
@@ -1034,7 +1034,7 @@ void rocksdb_wal_iter_destroy (const rocksdb_wal_iterator_t* iter) {
|
||||
rocksdb_writebatch_t* rocksdb_wal_iter_get_batch (const rocksdb_wal_iterator_t* iter, uint64_t* seq) {
|
||||
rocksdb_writebatch_t* result = rocksdb_writebatch_create();
|
||||
BatchResult wal_batch = iter->rep->GetBatch();
|
||||
result->rep = * wal_batch.writeBatchPtr.release();
|
||||
result->rep = std::move(*wal_batch.writeBatchPtr);
|
||||
if (seq != nullptr) {
|
||||
*seq = wal_batch.sequence;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,15 @@ class CompactedDBImpl : public DBImpl {
|
||||
const IngestExternalFileOptions& /*ingestion_options*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
using DB::CreateColumnFamilyWithImport;
|
||||
virtual Status CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& /*options*/,
|
||||
const std::string& /*column_family_name*/,
|
||||
const ImportColumnFamilyOptions& /*import_options*/,
|
||||
const ExportImportFilesMetaData& /*metadata*/,
|
||||
ColumnFamilyHandle** /*handle*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
|
||||
@@ -39,7 +39,6 @@ class SnapshotListFetchCallback {
|
||||
virtual void Refresh(std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber max) = 0;
|
||||
inline bool TimeToRefresh(const size_t key_index) {
|
||||
assert(snap_refresh_nanos_ != 0);
|
||||
// skip the key if key_index % every_nth_key (which is of power 2) is not 0.
|
||||
if ((key_index & every_nth_key_minus_one_) != 0) {
|
||||
return false;
|
||||
|
||||
@@ -521,7 +521,7 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
// mutex to reduce contention
|
||||
db_mutex_->Unlock();
|
||||
uint64_t size = versions_->ApproximateSize(v, a, b, start_lvl, out_lvl + 1,
|
||||
/*for_compaction*/ true);
|
||||
TableReaderCaller::kCompaction);
|
||||
db_mutex_->Lock();
|
||||
ranges.emplace_back(a, b, size);
|
||||
sum += size;
|
||||
@@ -646,12 +646,14 @@ Status CompactionJob::Run() {
|
||||
// to cache it here for further user reads
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(),
|
||||
*files_meta[file_idx], nullptr /* range_del_agg */,
|
||||
prefix_extractor, nullptr,
|
||||
*files_meta[file_idx], /*range_del_agg=*/nullptr, prefix_extractor,
|
||||
/*table_reader_ptr=*/nullptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
false, nullptr /* arena */, false /* skip_filters */,
|
||||
compact_->compaction->output_level());
|
||||
TableReaderCaller::kCompactionRefill, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, compact_->compaction->output_level(),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr);
|
||||
auto s = iter->status();
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
|
||||
@@ -964,7 +964,7 @@ TEST_F(CompactionJobTest, SnapshotRefresh) {
|
||||
public:
|
||||
SnapshotListFetchCallbackTest(Env* env, Random64& rand,
|
||||
std::vector<SequenceNumber>* snapshots)
|
||||
: SnapshotListFetchCallback(env, 1 /*short time delay*/,
|
||||
: SnapshotListFetchCallback(env, 0 /*no time delay*/,
|
||||
1 /*fetch after each key*/),
|
||||
rand_(rand),
|
||||
snapshots_(snapshots) {}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ Status VerifySstFileChecksum(const Options& options,
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
s = table_reader->VerifyChecksum();
|
||||
s = table_reader->VerifyChecksum(TableReaderCaller::kUserVerifyChecksum);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
#include "table/block_based/block_builder.h"
|
||||
#include "test_util/fault_injection_test_env.h"
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
@@ -1286,6 +1287,55 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBasicTest, GetAllKeyVersions) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
const size_t kNumInserts = 4;
|
||||
const size_t kNumDeletes = 4;
|
||||
const size_t kNumUpdates = 4;
|
||||
|
||||
// Check default column family
|
||||
for (size_t i = 0; i != kNumInserts; ++i) {
|
||||
ASSERT_OK(Put(std::to_string(i), "value"));
|
||||
}
|
||||
for (size_t i = 0; i != kNumUpdates; ++i) {
|
||||
ASSERT_OK(Put(std::to_string(i), "value1"));
|
||||
}
|
||||
for (size_t i = 0; i != kNumDeletes; ++i) {
|
||||
ASSERT_OK(Delete(std::to_string(i)));
|
||||
}
|
||||
std::vector<KeyVersion> key_versions;
|
||||
ASSERT_OK(rocksdb::GetAllKeyVersions(db_, Slice(), Slice(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
|
||||
ASSERT_OK(rocksdb::GetAllKeyVersions(db_, handles_[0], Slice(), Slice(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
|
||||
|
||||
// Check non-default column family
|
||||
for (size_t i = 0; i != kNumInserts - 1; ++i) {
|
||||
ASSERT_OK(Put(1, std::to_string(i), "value"));
|
||||
}
|
||||
for (size_t i = 0; i != kNumUpdates - 1; ++i) {
|
||||
ASSERT_OK(Put(1, std::to_string(i), "value1"));
|
||||
}
|
||||
for (size_t i = 0; i != kNumDeletes - 1; ++i) {
|
||||
ASSERT_OK(Delete(1, std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(rocksdb::GetAllKeyVersions(db_, handles_[1], Slice(), Slice(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates - 3, key_versions.size());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class DBBasicTestWithParallelIO
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<bool,bool,bool,bool>> {
|
||||
|
||||
@@ -365,11 +365,11 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
ASSERT_EQ(cache->GetUsage(), index_bytes_insert + filter_bytes_insert);
|
||||
// set the cache capacity to the current usage
|
||||
cache->SetCapacity(index_bytes_insert + filter_bytes_insert);
|
||||
// The index eviction statistics were broken by the refactoring that moved
|
||||
// the index readers out of the block cache. Disabling these until we can
|
||||
// The index and filter eviction statistics were broken by the refactoring
|
||||
// that moved the readers out of the block cache. Disabling these until we can
|
||||
// bring the stats back.
|
||||
// ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT), 0);
|
||||
// ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT), 0);
|
||||
// Note that the second key needs to be no longer than the first one.
|
||||
// Otherwise the second index block may not fit in cache.
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
@@ -380,13 +380,13 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
index_bytes_insert);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_INSERT),
|
||||
filter_bytes_insert);
|
||||
// The index eviction statistics were broken by the refactoring that moved
|
||||
// the index readers out of the block cache. Disabling these until we can
|
||||
// The index and filter eviction statistics were broken by the refactoring
|
||||
// that moved the readers out of the block cache. Disabling these until we can
|
||||
// bring the stats back.
|
||||
// ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT),
|
||||
// index_bytes_insert);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT),
|
||||
filter_bytes_insert);
|
||||
// ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT),
|
||||
// filter_bytes_insert);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -497,14 +497,14 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
|
||||
// Create new iterator for:
|
||||
// (1) 1 for verifying flush results
|
||||
// (2) 3 for compaction input files
|
||||
// (3) 1 for verifying compaction results.
|
||||
ASSERT_EQ(num_new_table_reader, 5);
|
||||
// (2) 1 for verifying compaction results.
|
||||
// (3) New TableReaders will not be created for compaction inputs
|
||||
ASSERT_EQ(num_new_table_reader, 2);
|
||||
|
||||
num_table_cache_lookup = 0;
|
||||
num_new_table_reader = 0;
|
||||
ASSERT_EQ(Key(1), Get(Key(1)));
|
||||
ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 3);
|
||||
ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 5);
|
||||
ASSERT_EQ(num_new_table_reader, 0);
|
||||
|
||||
num_table_cache_lookup = 0;
|
||||
@@ -519,13 +519,14 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
// May preload table cache too.
|
||||
ASSERT_GE(num_table_cache_lookup, 1);
|
||||
old_num_table_cache_lookup2 = num_table_cache_lookup;
|
||||
// One for compaction input, one for verifying compaction results.
|
||||
ASSERT_EQ(num_new_table_reader, 2);
|
||||
// One for verifying compaction results.
|
||||
// No new iterator created for compaction.
|
||||
ASSERT_EQ(num_new_table_reader, 1);
|
||||
|
||||
num_table_cache_lookup = 0;
|
||||
num_new_table_reader = 0;
|
||||
ASSERT_EQ(Key(1), Get(Key(1)));
|
||||
ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 2);
|
||||
ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 3);
|
||||
ASSERT_EQ(num_new_table_reader, 0);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
@@ -4339,12 +4340,6 @@ TEST_P(DBCompactionDirectIOTest, DirectIO) {
|
||||
options.env = new MockEnv(Env::Default());
|
||||
Reopen(options);
|
||||
bool readahead = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"TableCache::NewIterator:for_compaction", [&](void* arg) {
|
||||
bool* use_direct_reads = static_cast<bool*>(arg);
|
||||
ASSERT_EQ(*use_direct_reads,
|
||||
options.use_direct_reads);
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::OpenCompactionOutputFile", [&](void* arg) {
|
||||
bool* use_direct_writes = static_cast<bool*>(arg);
|
||||
|
||||
+152
-19
@@ -14,6 +14,7 @@
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
#include "db/import_column_family_job.h"
|
||||
#include "db/flush_job.h"
|
||||
#include "db/forward_iterator.h"
|
||||
#include "db/job_context.h"
|
||||
@@ -101,7 +103,6 @@ namespace rocksdb {
|
||||
const std::string kDefaultColumnFamilyName("default");
|
||||
const std::string kPersistentStatsColumnFamilyName(
|
||||
"___rocksdb_stats_history___");
|
||||
const int kMicrosInSecond = 1000 * 1000;
|
||||
void DumpRocksDBBuildVersion(Logger* log);
|
||||
|
||||
CompressionType GetCompressionFlush(
|
||||
@@ -698,10 +699,15 @@ void DBImpl::PersistStats() {
|
||||
if (!statistics->getTickerMap(&stats_map)) {
|
||||
return;
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"------- PERSISTING STATS -------");
|
||||
|
||||
if (immutable_db_options_.persist_stats_to_disk) {
|
||||
WriteBatch batch;
|
||||
if (stats_slice_initialized_) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Reading %" ROCKSDB_PRIszt " stats from statistics\n",
|
||||
stats_slice_.size());
|
||||
for (const auto& stat : stats_map) {
|
||||
char key[100];
|
||||
int length =
|
||||
@@ -723,8 +729,13 @@ void DBImpl::PersistStats() {
|
||||
Status s = Write(wo, &batch);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Writing to persistent stats CF failed -- %s\n",
|
||||
"Writing to persistent stats CF failed -- %s",
|
||||
s.ToString().c_str());
|
||||
} else {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Writing %" ROCKSDB_PRIszt " stats with timestamp %" PRIu64
|
||||
" to persistent stats CF succeeded",
|
||||
stats_slice_.size(), now_seconds);
|
||||
}
|
||||
// TODO(Zhongyi): add purging for persisted data
|
||||
} else {
|
||||
@@ -737,6 +748,10 @@ void DBImpl::PersistStats() {
|
||||
stats_delta[stat.first] = stat.second - stats_slice_[stat.first];
|
||||
}
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Storing %" ROCKSDB_PRIszt " stats with timestamp %" PRIu64
|
||||
" to in-memory stats history",
|
||||
stats_slice_.size(), now_seconds);
|
||||
stats_history_[now_seconds] = stats_delta;
|
||||
}
|
||||
stats_slice_initialized_ = true;
|
||||
@@ -744,15 +759,22 @@ void DBImpl::PersistStats() {
|
||||
TEST_SYNC_POINT("DBImpl::PersistStats:StatsCopied");
|
||||
|
||||
// delete older stats snapshots to control memory consumption
|
||||
bool purge_needed =
|
||||
EstimateInMemoryStatsHistorySize() > stats_history_size_limit;
|
||||
size_t stats_history_size = EstimateInMemoryStatsHistorySize();
|
||||
bool purge_needed = stats_history_size > stats_history_size_limit;
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[Pre-GC] In-memory stats history size: %" ROCKSDB_PRIszt
|
||||
" bytes, slice count: %" ROCKSDB_PRIszt,
|
||||
stats_history_size, stats_history_.size());
|
||||
while (purge_needed && !stats_history_.empty()) {
|
||||
stats_history_.erase(stats_history_.begin());
|
||||
purge_needed =
|
||||
EstimateInMemoryStatsHistorySize() > stats_history_size_limit;
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[Post-GC] In-memory stats history size: %" ROCKSDB_PRIszt
|
||||
" bytes, slice count: %" ROCKSDB_PRIszt,
|
||||
stats_history_size, stats_history_.size());
|
||||
}
|
||||
// TODO: persist stats to disk
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
@@ -861,16 +883,6 @@ Directory* DBImpl::GetDataDir(ColumnFamilyData* cfd, size_t path_id) const {
|
||||
return ret_dir;
|
||||
}
|
||||
|
||||
Directory* DBImpl::Directories::GetDataDir(size_t path_id) const {
|
||||
assert(path_id < data_dirs_.size());
|
||||
Directory* ret_dir = data_dirs_[path_id].get();
|
||||
if (ret_dir == nullptr) {
|
||||
// Should use db_dir_
|
||||
return db_dir_.get();
|
||||
}
|
||||
return ret_dir;
|
||||
}
|
||||
|
||||
Status DBImpl::SetOptions(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::unordered_map<std::string, std::string>& options_map) {
|
||||
@@ -2771,7 +2783,7 @@ void DBImpl::GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
if (include_flags & DB::SizeApproximationFlags::INCLUDE_FILES) {
|
||||
sizes[i] += versions_->ApproximateSize(
|
||||
v, k1.Encode(), k2.Encode(), /*start_level=*/0, /*end_level=*/-1,
|
||||
/*for_compaction=*/false);
|
||||
TableReaderCaller::kUserApproximateSize);
|
||||
}
|
||||
if (include_flags & DB::SizeApproximationFlags::INCLUDE_MEMTABLES) {
|
||||
sizes[i] += sv->mem->ApproximateStats(k1.Encode(), k2.Encode()).size;
|
||||
@@ -3650,16 +3662,16 @@ Status DBImpl::IngestExternalFiles(
|
||||
auto* cfd = static_cast<ColumnFamilyHandleImpl*>(arg.column_family)->cfd();
|
||||
ingestion_jobs.emplace_back(env_, versions_.get(), cfd,
|
||||
immutable_db_options_, env_options_,
|
||||
&snapshots_, arg.options);
|
||||
&snapshots_, arg.options, &directories_);
|
||||
}
|
||||
std::vector<std::pair<bool, Status>> exec_results;
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
exec_results.emplace_back(false, Status::OK());
|
||||
}
|
||||
// TODO(yanqin) maybe make jobs run in parallel
|
||||
uint64_t start_file_number = next_file_number;
|
||||
for (size_t i = 1; i != num_cfs; ++i) {
|
||||
start_file_number += args[i - 1].external_files.size();
|
||||
uint64_t start_file_number =
|
||||
next_file_number + args[i - 1].external_files.size();
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
@@ -3883,6 +3895,127 @@ Status DBImpl::IngestExternalFiles(
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& options, const std::string& column_family_name,
|
||||
const ImportColumnFamilyOptions& import_options,
|
||||
const ExportImportFilesMetaData& metadata,
|
||||
ColumnFamilyHandle** handle) {
|
||||
assert(handle != nullptr);
|
||||
assert(*handle == nullptr);
|
||||
std::string cf_comparator_name = options.comparator->Name();
|
||||
if (cf_comparator_name != metadata.db_comparator_name) {
|
||||
return Status::InvalidArgument("Comparator name mismatch");
|
||||
}
|
||||
|
||||
// Create column family.
|
||||
auto status = CreateColumnFamily(options, column_family_name, handle);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Import sst files from metadata.
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(*handle);
|
||||
auto cfd = cfh->cfd();
|
||||
ImportColumnFamilyJob import_job(env_, versions_.get(), cfd,
|
||||
immutable_db_options_, env_options_,
|
||||
import_options, metadata.files);
|
||||
|
||||
SuperVersionContext dummy_sv_ctx(/* create_superversion */ true);
|
||||
VersionEdit dummy_edit;
|
||||
uint64_t next_file_number = 0;
|
||||
std::list<uint64_t>::iterator pending_output_elem;
|
||||
{
|
||||
// Lock db mutex
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (error_handler_.IsDBStopped()) {
|
||||
// Don't import files when there is a bg_error
|
||||
status = error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
// Make sure that bg cleanup wont delete the files that we are importing
|
||||
pending_output_elem = CaptureCurrentFileNumberInPendingOutputs();
|
||||
|
||||
if (status.ok()) {
|
||||
// If crash happen after a hard link established, Recover function may
|
||||
// reuse the file number that has already assigned to the internal file,
|
||||
// and this will overwrite the external file. To protect the external
|
||||
// file, we have to make sure the file number will never being reused.
|
||||
next_file_number =
|
||||
versions_->FetchAddFileNumber(metadata.files.size());
|
||||
auto cf_options = cfd->GetLatestMutableCFOptions();
|
||||
status = versions_->LogAndApply(cfd, *cf_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx, *cf_options);
|
||||
}
|
||||
}
|
||||
}
|
||||
dummy_sv_ctx.Clean();
|
||||
|
||||
if (status.ok()) {
|
||||
SuperVersion* sv = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
status = import_job.Prepare(next_file_number, sv);
|
||||
CleanupSuperVersion(sv);
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
SuperVersionContext sv_context(true /*create_superversion*/);
|
||||
{
|
||||
// Lock db mutex
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
// Stop writes to the DB by entering both write threads
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
WriteThread::Writer nonmem_w;
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
|
||||
}
|
||||
|
||||
num_running_ingest_file_++;
|
||||
assert(!cfd->IsDropped());
|
||||
status = import_job.Run();
|
||||
|
||||
// Install job edit [Mutex will be unlocked here]
|
||||
if (status.ok()) {
|
||||
auto cf_options = cfd->GetLatestMutableCFOptions();
|
||||
status = versions_->LogAndApply(cfd, *cf_options, import_job.edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context, *cf_options);
|
||||
}
|
||||
}
|
||||
|
||||
// Resume writes to the DB
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
|
||||
num_running_ingest_file_--;
|
||||
if (num_running_ingest_file_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
// mutex_ is unlocked here
|
||||
|
||||
sv_context.Clean();
|
||||
}
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ReleaseFileNumberFromPendingOutputs(pending_output_elem);
|
||||
}
|
||||
|
||||
import_job.Cleanup(status);
|
||||
if (!status.ok()) {
|
||||
DropColumnFamily(*handle);
|
||||
DestroyColumnFamilyHandle(*handle);
|
||||
*handle = nullptr;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyChecksum() {
|
||||
Status s;
|
||||
std::vector<ColumnFamilyData*> cfd_list;
|
||||
|
||||
+44
-25
@@ -27,6 +27,7 @@
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
#include "db/flush_job.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/import_column_family_job.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/logs_with_prep_tracker.h"
|
||||
@@ -77,6 +78,38 @@ struct JobContext;
|
||||
struct ExternalSstFileInfo;
|
||||
struct MemTableInfo;
|
||||
|
||||
// Class to maintain directories for all database paths other than main one.
|
||||
class Directories {
|
||||
public:
|
||||
Status SetDirectories(Env* env, const std::string& dbname,
|
||||
const std::string& wal_dir,
|
||||
const std::vector<DbPath>& data_paths);
|
||||
|
||||
Directory* GetDataDir(size_t path_id) const {
|
||||
assert(path_id < data_dirs_.size());
|
||||
Directory* ret_dir = data_dirs_[path_id].get();
|
||||
if (ret_dir == nullptr) {
|
||||
// Should use db_dir_
|
||||
return db_dir_.get();
|
||||
}
|
||||
return ret_dir;
|
||||
}
|
||||
|
||||
Directory* GetWalDir() {
|
||||
if (wal_dir_) {
|
||||
return wal_dir_.get();
|
||||
}
|
||||
return db_dir_.get();
|
||||
}
|
||||
|
||||
Directory* GetDbDir() { return db_dir_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<Directory> db_dir_;
|
||||
std::vector<std::unique_ptr<Directory>> data_dirs_;
|
||||
std::unique_ptr<Directory> wal_dir_;
|
||||
};
|
||||
|
||||
// While DB is the public interface of RocksDB, and DBImpl is the actual
|
||||
// class implementing it. It's the entrance of the core RocksdB engine.
|
||||
// All other DB implementations, e.g. TransactionDB, BlobDB, etc, wrap a
|
||||
@@ -324,6 +357,13 @@ class DBImpl : public DB {
|
||||
virtual Status IngestExternalFiles(
|
||||
const std::vector<IngestExternalFileArg>& args) override;
|
||||
|
||||
using DB::CreateColumnFamilyWithImport;
|
||||
virtual Status CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& options, const std::string& column_family_name,
|
||||
const ImportColumnFamilyOptions& import_options,
|
||||
const ExportImportFilesMetaData& metadata,
|
||||
ColumnFamilyHandle** handle) override;
|
||||
|
||||
virtual Status VerifyChecksum() override;
|
||||
|
||||
using DB::StartTrace;
|
||||
@@ -1057,30 +1097,6 @@ class DBImpl : public DB {
|
||||
}
|
||||
};
|
||||
|
||||
// Class to maintain directories for all database paths other than main one.
|
||||
class Directories {
|
||||
public:
|
||||
Status SetDirectories(Env* env, const std::string& dbname,
|
||||
const std::string& wal_dir,
|
||||
const std::vector<DbPath>& data_paths);
|
||||
|
||||
Directory* GetDataDir(size_t path_id) const;
|
||||
|
||||
Directory* GetWalDir() {
|
||||
if (wal_dir_) {
|
||||
return wal_dir_.get();
|
||||
}
|
||||
return db_dir_.get();
|
||||
}
|
||||
|
||||
Directory* GetDbDir() { return db_dir_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<Directory> db_dir_;
|
||||
std::vector<std::unique_ptr<Directory>> data_dirs_;
|
||||
std::unique_ptr<Directory> wal_dir_;
|
||||
};
|
||||
|
||||
struct LogFileNumberSize {
|
||||
explicit LogFileNumberSize(uint64_t _number) : number(_number) {}
|
||||
void AddSize(uint64_t new_size) { size += new_size; }
|
||||
@@ -1294,6 +1310,8 @@ class DBImpl : public DB {
|
||||
|
||||
Status ScheduleFlushes(WriteContext* context);
|
||||
|
||||
void MaybeFlushStatsCF(autovector<ColumnFamilyData*>* cfds);
|
||||
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context);
|
||||
|
||||
void SelectColumnFamiliesForAtomicFlush(autovector<ColumnFamilyData*>* cfds);
|
||||
@@ -1793,7 +1811,8 @@ class DBImpl : public DB {
|
||||
|
||||
std::string db_absolute_path_;
|
||||
|
||||
// Number of running IngestExternalFile() calls.
|
||||
// Number of running IngestExternalFile() or CreateColumnFamilyWithImport()
|
||||
// calls.
|
||||
// REQUIRES: mutex held
|
||||
int num_running_ingest_file_;
|
||||
|
||||
|
||||
@@ -1008,10 +1008,8 @@ Status DBImpl::CompactFilesImpl(
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, Env::Priority::USER,
|
||||
immutable_db_options_.max_subcompactions <= 1 &&
|
||||
c->mutable_cf_options()->snap_refresh_nanos > 0
|
||||
? &fetch_callback
|
||||
: nullptr);
|
||||
immutable_db_options_.max_subcompactions <= 1 ? &fetch_callback
|
||||
: nullptr);
|
||||
|
||||
// Creating a compaction influences the compaction score because the score
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
@@ -1553,13 +1551,39 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
if (!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load()) {
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() ||
|
||||
!cached_recoverable_state_empty_.load()) {
|
||||
flush_memtable_id = cfd->imm()->GetLatestMemTableID();
|
||||
flush_req.emplace_back(cfd, flush_memtable_id);
|
||||
}
|
||||
if (immutable_db_options_.persist_stats_to_disk) {
|
||||
ColumnFamilyData* cfd_stats =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(
|
||||
kPersistentStatsColumnFamilyName);
|
||||
if (cfd_stats != nullptr && cfd_stats != cfd &&
|
||||
!cfd_stats->mem()->IsEmpty()) {
|
||||
// only force flush stats CF when it will be the only CF lagging
|
||||
// behind after the current flush
|
||||
bool stats_cf_flush_needed = true;
|
||||
for (auto* loop_cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (loop_cfd == cfd_stats || loop_cfd == cfd) {
|
||||
continue;
|
||||
}
|
||||
if (loop_cfd->GetLogNumber() <= cfd_stats->GetLogNumber()) {
|
||||
stats_cf_flush_needed = false;
|
||||
}
|
||||
}
|
||||
if (stats_cf_flush_needed) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Force flushing stats CF with manual flush of %s "
|
||||
"to avoid holding old logs", cfd->GetName().c_str());
|
||||
s = SwitchMemtable(cfd_stats, &context);
|
||||
flush_memtable_id = cfd_stats->imm()->GetLatestMemTableID();
|
||||
flush_req.emplace_back(cfd_stats, flush_memtable_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && !flush_req.empty()) {
|
||||
@@ -2713,10 +2737,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
&event_logger_, c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, thread_pri,
|
||||
immutable_db_options_.max_subcompactions <= 1 &&
|
||||
c->mutable_cf_options()->snap_refresh_nanos > 0
|
||||
? &fetch_callback
|
||||
: nullptr);
|
||||
immutable_db_options_.max_subcompactions <= 1 ? &fetch_callback
|
||||
: nullptr);
|
||||
compaction_job.Prepare();
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
|
||||
@@ -284,9 +284,9 @@ Status DBImpl::CreateAndNewDirectory(Env* env, const std::string& dirname,
|
||||
return env->NewDirectory(dirname, directory);
|
||||
}
|
||||
|
||||
Status DBImpl::Directories::SetDirectories(
|
||||
Env* env, const std::string& dbname, const std::string& wal_dir,
|
||||
const std::vector<DbPath>& data_paths) {
|
||||
Status Directories::SetDirectories(Env* env, const std::string& dbname,
|
||||
const std::string& wal_dir,
|
||||
const std::vector<DbPath>& data_paths) {
|
||||
Status s = DBImpl::CreateAndNewDirectory(env, dbname, &db_dir_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
@@ -115,6 +115,16 @@ class DBImplReadOnly : public DBImpl {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DB::CreateColumnFamilyWithImport;
|
||||
virtual Status CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& /*options*/,
|
||||
const std::string& /*column_family_name*/,
|
||||
const ImportColumnFamilyOptions& /*import_options*/,
|
||||
const ExportImportFilesMetaData& /*metadata*/,
|
||||
ColumnFamilyHandle** /*handle*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
|
||||
|
||||
@@ -587,6 +587,9 @@ Status DB::OpenAsSecondary(
|
||||
&impl->write_controller_));
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->wal_in_db_path_ =
|
||||
IsWalDirSameAsDBPath(&impl->immutable_db_options_);
|
||||
|
||||
impl->mutex_.Lock();
|
||||
s = impl->Recover(column_families, true, false, false);
|
||||
if (s.ok()) {
|
||||
|
||||
@@ -1228,6 +1228,7 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
cfds.push_back(cfd);
|
||||
}
|
||||
}
|
||||
MaybeFlushStatsCF(&cfds);
|
||||
}
|
||||
for (const auto cfd : cfds) {
|
||||
cfd->Ref();
|
||||
@@ -1294,6 +1295,7 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
if (cfd_picked != nullptr) {
|
||||
cfds.push_back(cfd_picked);
|
||||
}
|
||||
MaybeFlushStatsCF(&cfds);
|
||||
}
|
||||
|
||||
for (const auto cfd : cfds) {
|
||||
@@ -1437,6 +1439,40 @@ Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DBImpl::MaybeFlushStatsCF(autovector<ColumnFamilyData*>* cfds) {
|
||||
assert(cfds != nullptr);
|
||||
if (!cfds->empty() && immutable_db_options_.persist_stats_to_disk) {
|
||||
ColumnFamilyData* cfd_stats =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(
|
||||
kPersistentStatsColumnFamilyName);
|
||||
if (cfd_stats != nullptr && !cfd_stats->mem()->IsEmpty()) {
|
||||
for (ColumnFamilyData* cfd : *cfds) {
|
||||
if (cfd == cfd_stats) {
|
||||
// stats CF already included in cfds
|
||||
return;
|
||||
}
|
||||
}
|
||||
// force flush stats CF when its log number is less than all other CF's
|
||||
// log numbers
|
||||
bool force_flush_stats_cf = true;
|
||||
for (auto* loop_cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (loop_cfd == cfd_stats) {
|
||||
continue;
|
||||
}
|
||||
if (loop_cfd->GetLogNumber() <= cfd_stats->GetLogNumber()) {
|
||||
force_flush_stats_cf = false;
|
||||
}
|
||||
}
|
||||
if (force_flush_stats_cf) {
|
||||
cfds->push_back(cfd_stats);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Force flushing stats CF with automated flush "
|
||||
"to avoid holding old logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::ScheduleFlushes(WriteContext* context) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
@@ -1450,6 +1486,7 @@ Status DBImpl::ScheduleFlushes(WriteContext* context) {
|
||||
while ((tmp_cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) {
|
||||
cfds.push_back(tmp_cfd);
|
||||
}
|
||||
MaybeFlushStatsCF(&cfds);
|
||||
}
|
||||
Status status;
|
||||
for (auto& cfd : cfds) {
|
||||
|
||||
+7
-2
@@ -467,7 +467,9 @@ inline bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check)
|
||||
|
||||
is_key_seqnum_zero_ = (ikey_.sequence == 0);
|
||||
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
assert(iterate_upper_bound_ == nullptr || iter_.MayBeOutOfUpperBound() ||
|
||||
user_comparator_.Compare(ikey_.user_key, *iterate_upper_bound_) < 0);
|
||||
if (iterate_upper_bound_ != nullptr && iter_.MayBeOutOfUpperBound() &&
|
||||
user_comparator_.Compare(ikey_.user_key, *iterate_upper_bound_) >= 0) {
|
||||
break;
|
||||
}
|
||||
@@ -859,7 +861,10 @@ void DBIter::PrevInternal() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
assert(iterate_lower_bound_ == nullptr || iter_.MayBeOutOfLowerBound() ||
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) >= 0);
|
||||
if (iterate_lower_bound_ != nullptr && iter_.MayBeOutOfLowerBound() &&
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
// We've iterated earlier than the user-specified lower bound.
|
||||
|
||||
@@ -1049,6 +1049,148 @@ TEST_P(DBIteratorTest, DBIteratorBoundOptimizationTest) {
|
||||
ASSERT_EQ(upper_bound_hits, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable kBinarySearchWithFirstKey, do some iterator operations and check that
|
||||
// they don't do unnecessary block reads.
|
||||
TEST_P(DBIteratorTest, IndexWithFirstKey) {
|
||||
for (int tailing = 0; tailing < 2; ++tailing) {
|
||||
SCOPED_TRACE("tailing = " + std::to_string(tailing));
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor = nullptr;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
Statistics* stats = options.statistics.get();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.index_type =
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey;
|
||||
table_options.index_shortening =
|
||||
BlockBasedTableOptions::IndexShorteningMode::kNoShortening;
|
||||
table_options.flush_block_policy_factory =
|
||||
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
|
||||
table_options.block_cache = NewLRUCache(1000); // fits all blocks
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Merge("a1", "x1"));
|
||||
ASSERT_OK(Merge("b1", "y1"));
|
||||
ASSERT_OK(Merge("c0", "z1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("a2", "x2"));
|
||||
ASSERT_OK(Merge("b2", "y2"));
|
||||
ASSERT_OK(Merge("c0", "z2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("a3", "x3"));
|
||||
ASSERT_OK(Merge("b3", "y3"));
|
||||
ASSERT_OK(Merge("c3", "z3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Block cache is not important for this test.
|
||||
// We use BLOCK_CACHE_DATA_* counters just because they're the most readily
|
||||
// available way of counting block accesses.
|
||||
|
||||
ReadOptions ropt;
|
||||
ropt.tailing = tailing;
|
||||
std::unique_ptr<Iterator> iter(NewIterator(ropt));
|
||||
|
||||
iter->Seek("b10");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("b2", iter->key().ToString());
|
||||
EXPECT_EQ("y2", iter->value().ToString());
|
||||
EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("b3", iter->key().ToString());
|
||||
EXPECT_EQ("y3", iter->value().ToString());
|
||||
EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
iter->Seek("c0");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("c0", iter->key().ToString());
|
||||
EXPECT_EQ("z1,z2", iter->value().ToString());
|
||||
EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
EXPECT_EQ(4, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("c3", iter->key().ToString());
|
||||
EXPECT_EQ("z3", iter->value().ToString());
|
||||
EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
EXPECT_EQ(5, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
|
||||
iter.reset();
|
||||
|
||||
// Enable iterate_upper_bound and check that iterator is not trying to read
|
||||
// blocks that are fully above upper bound.
|
||||
std::string ub = "b3";
|
||||
Slice ub_slice(ub);
|
||||
ropt.iterate_upper_bound = &ub_slice;
|
||||
iter.reset(NewIterator(ropt));
|
||||
|
||||
iter->Seek("b2");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("b2", iter->key().ToString());
|
||||
EXPECT_EQ("y2", iter->value().ToString());
|
||||
EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
EXPECT_EQ(5, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
EXPECT_EQ(5, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, IndexWithFirstKeyGet) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor = nullptr;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
Statistics* stats = options.statistics.get();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.index_type =
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey;
|
||||
table_options.index_shortening =
|
||||
BlockBasedTableOptions::IndexShorteningMode::kNoShortening;
|
||||
table_options.flush_block_policy_factory =
|
||||
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
|
||||
table_options.block_cache = NewLRUCache(1000); // fits all blocks
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Merge("a", "x1"));
|
||||
ASSERT_OK(Merge("c", "y1"));
|
||||
ASSERT_OK(Merge("e", "z1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("c", "y2"));
|
||||
ASSERT_OK(Merge("e", "z2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Get() between blocks shouldn't read any blocks.
|
||||
ASSERT_EQ("NOT_FOUND", Get("b"));
|
||||
EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
// Get() of an existing key shouldn't read any unnecessary blocks when there's
|
||||
// only one key per block.
|
||||
|
||||
ASSERT_EQ("y1,y2", Get("c"));
|
||||
EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
ASSERT_EQ("x1", Get("a"));
|
||||
EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
EXPECT_EQ(std::vector<std::string>({"NOT_FOUND", "z1,z2"}),
|
||||
MultiGet({"b", "e"}));
|
||||
}
|
||||
|
||||
// TODO(3.13): fix the issue of Seek() + Prev() which might not necessary
|
||||
// return the biggest key which is smaller than the seek key.
|
||||
TEST_P(DBIteratorTest, PrevAfterAndNextAfterMerge) {
|
||||
@@ -2548,6 +2690,163 @@ TEST_P(DBIteratorTest, AvoidReseekLevelIterator) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, AvoidReseekChildIterator) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = CompressionType::kNoCompression;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 800;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
std::string random_str = RandomString(&rnd, 180);
|
||||
|
||||
ASSERT_OK(Put("1", random_str));
|
||||
ASSERT_OK(Put("2", random_str));
|
||||
ASSERT_OK(Put("3", random_str));
|
||||
ASSERT_OK(Put("4", random_str));
|
||||
ASSERT_OK(Put("8", random_str));
|
||||
ASSERT_OK(Put("9", random_str));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("5", random_str));
|
||||
ASSERT_OK(Put("6", random_str));
|
||||
ASSERT_OK(Put("7", random_str));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// These two keys will be kept in memtable.
|
||||
ASSERT_OK(Put("0", random_str));
|
||||
ASSERT_OK(Put("8", random_str));
|
||||
|
||||
int num_iter_wrapper_seek = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"IteratorWrapper::Seek:0",
|
||||
[&](void* /*arg*/) { num_iter_wrapper_seek++; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(NewIterator(ReadOptions()));
|
||||
iter->Seek("1");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
// DBIter always wraps internal iterator with IteratorWrapper,
|
||||
// and in merging iterator each child iterator will be wrapped
|
||||
// with IteratorWrapper.
|
||||
ASSERT_EQ(4, num_iter_wrapper_seek);
|
||||
|
||||
// child position: 1 and 5
|
||||
num_iter_wrapper_seek = 0;
|
||||
iter->Seek("2");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(3, num_iter_wrapper_seek);
|
||||
|
||||
// child position: 2 and 5
|
||||
num_iter_wrapper_seek = 0;
|
||||
iter->Seek("6");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(4, num_iter_wrapper_seek);
|
||||
|
||||
// child position: 8 and 6
|
||||
num_iter_wrapper_seek = 0;
|
||||
iter->Seek("7");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(3, num_iter_wrapper_seek);
|
||||
|
||||
// child position: 8 and 7
|
||||
num_iter_wrapper_seek = 0;
|
||||
iter->Seek("5");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(4, num_iter_wrapper_seek);
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// MyRocks may change iterate bounds before seek. Simply test to make sure such
|
||||
// usage doesn't break iterator.
|
||||
TEST_P(DBIteratorTest, IterateBoundChangedBeforeSeek) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = CompressionType::kNoCompression;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 100;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
std::string value(50, 'v');
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("aaa", value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("bbb", "v"));
|
||||
ASSERT_OK(Put("ccc", "v"));
|
||||
ASSERT_OK(Put("ddd", "v"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("eee", "v"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
std::string ub1 = "e";
|
||||
std::string ub2 = "c";
|
||||
Slice ub(ub1);
|
||||
ReadOptions read_opts1;
|
||||
read_opts1.iterate_upper_bound = &ub;
|
||||
Iterator* iter = NewIterator(read_opts1);
|
||||
// Seek and iterate accross block boundary.
|
||||
iter->Seek("b");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("bbb", iter->key());
|
||||
ub = Slice(ub2);
|
||||
iter->Seek("b");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("bbb", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
|
||||
std::string lb1 = "a";
|
||||
std::string lb2 = "c";
|
||||
Slice lb(lb1);
|
||||
ReadOptions read_opts2;
|
||||
read_opts2.iterate_lower_bound = &lb;
|
||||
iter = NewIterator(read_opts2);
|
||||
iter->SeekForPrev("d");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("ccc", iter->key());
|
||||
lb = Slice(lb2);
|
||||
iter->SeekForPrev("d");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("ccc", iter->key());
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, IterateWithLowerBoundAcrossFileBoundary) {
|
||||
ASSERT_OK(Put("aaa", "v"));
|
||||
ASSERT_OK(Put("bbb", "v"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("ccc", "v"));
|
||||
ASSERT_OK(Put("ddd", "v"));
|
||||
ASSERT_OK(Flush());
|
||||
// Move both files to bottom level.
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
Slice lower_bound("b");
|
||||
ReadOptions read_opts;
|
||||
read_opts.iterate_lower_bound = &lower_bound;
|
||||
std::unique_ptr<Iterator> iter(NewIterator(read_opts));
|
||||
iter->SeekForPrev("d");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("ccc", iter->key());
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("bbb", iter->key());
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBIteratorTestInstance, DBIteratorTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
|
||||
@@ -2492,6 +2492,16 @@ class ModelDB : public DB {
|
||||
return Status::NotSupported("Not implemented");
|
||||
}
|
||||
|
||||
using DB::CreateColumnFamilyWithImport;
|
||||
virtual Status CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& /*options*/,
|
||||
const std::string& /*column_family_name*/,
|
||||
const ImportColumnFamilyOptions& /*import_options*/,
|
||||
const ExportImportFilesMetaData& /*metadata*/,
|
||||
ColumnFamilyHandle** /*handle*/) override {
|
||||
return Status::NotSupported("Not implemented.");
|
||||
}
|
||||
|
||||
Status VerifyChecksum() override {
|
||||
return Status::NotSupported("Not implemented.");
|
||||
}
|
||||
@@ -6156,6 +6166,17 @@ TEST_F(DBTest, ThreadLocalPtrDeadlock) {
|
||||
fprintf(stderr, "Done. Flushed %d times, destroyed %d threads\n",
|
||||
flushes_done.load(), threads_destroyed.load());
|
||||
}
|
||||
|
||||
TEST_F(DBTest, LargeBlockSizeTest) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_OK(Put(0, "foo", "bar"));
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 8LL*1024*1024*1024LL;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
ASSERT_NOK(TryReopenWithColumnFamilies({"default", "pikachu"}, options));
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -3771,46 +3771,6 @@ TEST_F(DBTest2, CloseWithUnreleasedSnapshot) {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, PrefixBloomReseek) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(3));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.whole_key_filtering = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Construct two L1 files with keys:
|
||||
// f1:[aaa1 ccc1] f2:[ddd0]
|
||||
ASSERT_OK(Put("aaa1", ""));
|
||||
ASSERT_OK(Put("ccc1", ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("ddd0", ""));
|
||||
ASSERT_OK(Flush());
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kSkip;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(Put("bbb1", ""));
|
||||
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
|
||||
// Seeking into f1, the iterator will check bloom filter which returns the
|
||||
// file iterator ot be invalidate, and the cursor will put into f2, with
|
||||
// the next key to be "ddd0".
|
||||
iter->Seek("bbb1");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bbb1", iter->key().ToString());
|
||||
|
||||
// Reseek ccc1, the L1 iterator needs to go back to f1 and reseek.
|
||||
iter->Seek("ccc1");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("ccc1", iter->key().ToString());
|
||||
|
||||
delete iter;
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+3
-1
@@ -824,7 +824,9 @@ class RecoveryTestHelper {
|
||||
// Create WAL files with values filled in
|
||||
static void FillData(DBWALTest* test, const Options& options,
|
||||
const size_t wal_count, size_t* count) {
|
||||
const ImmutableDBOptions db_options(options);
|
||||
// Calling internal functions requires sanitized options.
|
||||
Options sanitized_options = SanitizeOptions(test->dbname_, options);
|
||||
const ImmutableDBOptions db_options(sanitized_options);
|
||||
|
||||
*count = 0;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "test_util/fault_injection_test_env.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -20,6 +21,7 @@ class ExternalSSTFileBasicTest
|
||||
public:
|
||||
ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_basic_test") {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
fault_injection_test_env_.reset(new FaultInjectionTestEnv(Env::Default()));
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
@@ -140,6 +142,7 @@ class ExternalSSTFileBasicTest
|
||||
|
||||
protected:
|
||||
std::string sst_files_dir_;
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_test_env_;
|
||||
};
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, Basic) {
|
||||
@@ -689,6 +692,59 @@ TEST_F(ExternalSSTFileBasicTest, FadviseTrigger) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, SyncFailure) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_injection_test_env_.get();
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> test_cases = {
|
||||
{"ExternalSstFileIngestionJob::BeforeSyncIngestedFile",
|
||||
"ExternalSstFileIngestionJob::AfterSyncIngestedFile"},
|
||||
{"ExternalSstFileIngestionJob::BeforeSyncDir",
|
||||
"ExternalSstFileIngestionJob::AfterSyncDir"},
|
||||
{"ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno",
|
||||
"ExternalSstFileIngestionJob::AfterSyncGlobalSeqno"}};
|
||||
|
||||
for (size_t i = 0; i < test_cases.size(); i++) {
|
||||
SyncPoint::GetInstance()->SetCallBack(test_cases[i].first, [&](void*) {
|
||||
fault_injection_test_env_->SetFilesystemActive(false);
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(test_cases[i].second, [&](void*) {
|
||||
fault_injection_test_env_->SetFilesystemActive(true);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
if (i == 2) {
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
}
|
||||
|
||||
Options sst_file_writer_options;
|
||||
std::unique_ptr<SstFileWriter> sst_file_writer(
|
||||
new SstFileWriter(EnvOptions(), sst_file_writer_options));
|
||||
std::string file_name =
|
||||
sst_files_dir_ + "sync_failure_test_" + ToString(i) + ".sst";
|
||||
ASSERT_OK(sst_file_writer->Open(file_name));
|
||||
ASSERT_OK(sst_file_writer->Put("bar", "v2"));
|
||||
ASSERT_OK(sst_file_writer->Finish());
|
||||
|
||||
IngestExternalFileOptions ingest_opt;
|
||||
if (i == 0) {
|
||||
ingest_opt.move_files = true;
|
||||
}
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
if (i == 2) {
|
||||
ingest_opt.write_global_seqno = true;
|
||||
}
|
||||
ASSERT_FALSE(db_->IngestExternalFile({file_name}, ingest_opt).ok());
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
Destroy(options);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
int kNumLevels = 7;
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "file/file_util.h"
|
||||
#include "table/merging_iterator.h"
|
||||
@@ -86,6 +88,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
}
|
||||
|
||||
// Copy/Move external files into DB
|
||||
std::unordered_set<size_t> ingestion_path_ids;
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
f.fd = FileDescriptor(next_file_number++, 0, f.file_size);
|
||||
f.copy_file = false;
|
||||
@@ -95,8 +98,26 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
f.fd.GetPathId());
|
||||
if (ingestion_options_.move_files) {
|
||||
status = env_->LinkFile(path_outside_db, path_inside_db);
|
||||
if (status.IsNotSupported() &&
|
||||
ingestion_options_.failed_move_fall_back_to_copy) {
|
||||
if (status.ok()) {
|
||||
// It is unsafe to assume application had sync the file and file
|
||||
// directory before ingest the file. For integrity of RocksDB we need
|
||||
// to sync the file.
|
||||
std::unique_ptr<WritableFile> file_to_sync;
|
||||
status = env_->ReopenWritableFile(path_inside_db, &file_to_sync,
|
||||
env_options_);
|
||||
if (status.ok()) {
|
||||
TEST_SYNC_POINT(
|
||||
"ExternalSstFileIngestionJob::BeforeSyncIngestedFile");
|
||||
status = SyncIngestedFile(file_to_sync.get());
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncIngestedFile");
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to sync ingested file %s: %s",
|
||||
path_inside_db.c_str(), status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
} else if (status.IsNotSupported() &&
|
||||
ingestion_options_.failed_move_fall_back_to_copy) {
|
||||
// Original file is on a different FS, use copy instead of hard linking.
|
||||
f.copy_file = true;
|
||||
}
|
||||
@@ -107,6 +128,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
if (f.copy_file) {
|
||||
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Prepare:CopyFile",
|
||||
nullptr);
|
||||
// CopyFile also sync the new file.
|
||||
status = CopyFile(env_, path_outside_db, path_inside_db, 0,
|
||||
db_options_.use_fsync);
|
||||
}
|
||||
@@ -115,13 +137,30 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
break;
|
||||
}
|
||||
f.internal_file_path = path_inside_db;
|
||||
ingestion_path_ids.insert(f.fd.GetPathId());
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncDir");
|
||||
if (status.ok()) {
|
||||
for (auto path_id : ingestion_path_ids) {
|
||||
status = directories_->GetDataDir(path_id)->Fsync();
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to sync directory %" ROCKSDB_PRIszt
|
||||
" while ingest file: %s",
|
||||
path_id, status.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncDir");
|
||||
|
||||
// TODO: The following is duplicated with Cleanup().
|
||||
if (!status.ok()) {
|
||||
// We failed, remove all files that we copied into the db
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.internal_file_path.empty()) {
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
Status s = env_->DeleteFile(f.internal_file_path);
|
||||
if (!s.ok()) {
|
||||
@@ -252,9 +291,6 @@ void ExternalSstFileIngestionJob::Cleanup(const Status& status) {
|
||||
// We failed to add the files to the database
|
||||
// remove all the files we copied
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.internal_file_path.empty()) {
|
||||
continue;
|
||||
}
|
||||
Status s = env_->DeleteFile(f.internal_file_path);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
@@ -311,7 +347,8 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
}
|
||||
|
||||
if (ingestion_options_.verify_checksums_before_ingest) {
|
||||
status = table_reader->VerifyChecksum();
|
||||
status =
|
||||
table_reader->VerifyChecksum(TableReaderCaller::kExternalSSTIngestion);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -371,7 +408,8 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
// updating the block cache.
|
||||
ro.fill_cache = false;
|
||||
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
|
||||
ro, sv->mutable_cf_options.prefix_extractor.get()));
|
||||
ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
table_reader->NewRangeTombstoneIterator(ro));
|
||||
|
||||
@@ -560,6 +598,18 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
std::string seqno_val;
|
||||
PutFixed64(&seqno_val, seqno);
|
||||
status = rwfile->Write(file_to_ingest->global_seqno_offset, seqno_val);
|
||||
if (status.ok()) {
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno");
|
||||
status = SyncIngestedFile(rwfile.get());
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncGlobalSeqno");
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to sync ingested file %s after writing global "
|
||||
"sequence number: %s",
|
||||
file_to_ingest->internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -600,6 +650,16 @@ bool ExternalSstFileIngestionJob::IngestedFileFitInLevel(
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename TWritableFile>
|
||||
Status ExternalSstFileIngestionJob::SyncIngestedFile(TWritableFile* file) {
|
||||
assert(file != nullptr);
|
||||
if (db_options_.use_fsync) {
|
||||
return file->Fsync();
|
||||
} else {
|
||||
return file->Sync();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Directories;
|
||||
|
||||
struct IngestedFileInfo {
|
||||
// External file path
|
||||
std::string external_file_path;
|
||||
@@ -77,7 +79,8 @@ class ExternalSstFileIngestionJob {
|
||||
Env* env, VersionSet* versions, ColumnFamilyData* cfd,
|
||||
const ImmutableDBOptions& db_options, const EnvOptions& env_options,
|
||||
SnapshotList* db_snapshots,
|
||||
const IngestExternalFileOptions& ingestion_options)
|
||||
const IngestExternalFileOptions& ingestion_options,
|
||||
Directories* directories)
|
||||
: env_(env),
|
||||
versions_(versions),
|
||||
cfd_(cfd),
|
||||
@@ -85,8 +88,11 @@ class ExternalSstFileIngestionJob {
|
||||
env_options_(env_options),
|
||||
db_snapshots_(db_snapshots),
|
||||
ingestion_options_(ingestion_options),
|
||||
directories_(directories),
|
||||
job_start_time_(env_->NowMicros()),
|
||||
consumed_seqno_(false) {}
|
||||
consumed_seqno_(false) {
|
||||
assert(directories != nullptr);
|
||||
}
|
||||
|
||||
// Prepare the job by copying external files into the DB.
|
||||
Status Prepare(const std::vector<std::string>& external_files_paths,
|
||||
@@ -153,6 +159,10 @@ class ExternalSstFileIngestionJob {
|
||||
bool IngestedFileFitInLevel(const IngestedFileInfo* file_to_ingest,
|
||||
int level);
|
||||
|
||||
// Helper method to sync given file.
|
||||
template <typename TWritableFile>
|
||||
Status SyncIngestedFile(TWritableFile* file);
|
||||
|
||||
Env* env_;
|
||||
VersionSet* versions_;
|
||||
ColumnFamilyData* cfd_;
|
||||
@@ -161,6 +171,7 @@ class ExternalSstFileIngestionJob {
|
||||
SnapshotList* db_snapshots_;
|
||||
autovector<IngestedFileInfo> files_to_ingest_;
|
||||
const IngestExternalFileOptions& ingestion_options_;
|
||||
Directories* directories_;
|
||||
VersionEdit edit_;
|
||||
uint64_t job_start_time_;
|
||||
bool consumed_seqno_;
|
||||
|
||||
@@ -2369,11 +2369,10 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
new FaultInjectionTestEnv(env_));
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_injection_env.get();
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2387,9 +2386,6 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2397,9 +2393,8 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
-1, true, true_data);
|
||||
ASSERT_OK(s);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2431,11 +2426,10 @@ TEST_P(ExternalSSTFileTest,
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_injection_env.get();
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
const std::vector<std::map<std::string, std::string>> data_before_ingestion =
|
||||
{{{"foo1", "fv1_0"}, {"foo2", "fv2_0"}, {"foo3", "fv3_0"}},
|
||||
{{"bar1", "bv1_0"}, {"bar2", "bv2_0"}, {"bar3", "bv3_0"}},
|
||||
{{"bar4", "bv4_0"}, {"bar5", "bv5_0"}, {"bar6", "bv6_0"}}};
|
||||
{{"bar1", "bv1_0"}, {"bar2", "bv2_0"}, {"bar3", "bv3_0"}}};
|
||||
for (size_t i = 0; i != handles_.size(); ++i) {
|
||||
int cf = static_cast<int>(i);
|
||||
const auto& orig_data = data_before_ingestion[i];
|
||||
@@ -2448,7 +2442,6 @@ TEST_P(ExternalSSTFileTest,
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2462,8 +2455,6 @@ TEST_P(ExternalSSTFileTest,
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2517,11 +2508,10 @@ TEST_P(ExternalSSTFileTest,
|
||||
dbfull()->ReleaseSnapshot(read_opts.snapshot);
|
||||
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
// Should see consistent state after ingestion for all column families even
|
||||
// without snapshot.
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2551,11 +2541,10 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) {
|
||||
"DBImpl::IngestExternalFiles:BeforeLastJobPrepare:1"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2569,9 +2558,6 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) {
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2591,9 +2577,8 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) {
|
||||
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2622,11 +2607,10 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) {
|
||||
"DBImpl::IngestExternalFiles:BeforeJobsRun:1"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2640,8 +2624,6 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) {
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2661,9 +2643,8 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) {
|
||||
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2683,7 +2664,7 @@ TEST_P(ExternalSSTFileTest,
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_injection_env.get();
|
||||
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
SyncPoint::GetInstance()->ClearTrace();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -2701,7 +2682,6 @@ TEST_P(ExternalSSTFileTest,
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2715,8 +2695,6 @@ TEST_P(ExternalSSTFileTest,
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2737,9 +2715,8 @@ TEST_P(ExternalSSTFileTest,
|
||||
fault_injection_env->DropUnsyncedFileData();
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
|
||||
+24
-5
@@ -79,7 +79,11 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
*files_[file_index_],
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
prefix_extractor_, nullptr /* table_reader_ptr */, nullptr, false);
|
||||
prefix_extractor_, /*table_reader_ptr=*/nullptr,
|
||||
/*file_read_hist=*/nullptr, TableReaderCaller::kUserIterator,
|
||||
/*arena=*/nullptr, /*skip_filters=*/false, /*level=*/-1,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr);
|
||||
file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
valid_ = false;
|
||||
if (!range_del_agg.IsEmpty()) {
|
||||
@@ -642,7 +646,12 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
l0_iters_.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(), *l0,
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
sv_->mutable_cf_options.prefix_extractor.get()));
|
||||
sv_->mutable_cf_options.prefix_extractor.get(),
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr));
|
||||
}
|
||||
BuildLevelIterators(vstorage);
|
||||
current_ = nullptr;
|
||||
@@ -714,7 +723,12 @@ void ForwardIterator::RenewIterators() {
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
*l0_files_new[inew],
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
svnew->mutable_cf_options.prefix_extractor.get()));
|
||||
svnew->mutable_cf_options.prefix_extractor.get(),
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr));
|
||||
}
|
||||
|
||||
for (auto* f : l0_iters_) {
|
||||
@@ -772,8 +786,13 @@ void ForwardIterator::ResetIncompleteIterators() {
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
*l0_files[i], nullptr /* range_del_agg */,
|
||||
sv_->mutable_cf_options.prefix_extractor.get());
|
||||
*l0_files[i], /*range_del_agg=*/nullptr,
|
||||
sv_->mutable_cf_options.prefix_extractor.get(),
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr);
|
||||
l0_iters_[i]->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/import_column_family_job.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/version_edit.h"
|
||||
#include "file/file_util.h"
|
||||
#include "table/merging_iterator.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "table/sst_file_writer_collectors.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
Status ImportColumnFamilyJob::Prepare(uint64_t next_file_number,
|
||||
SuperVersion* sv) {
|
||||
Status status;
|
||||
|
||||
// Read the information of files we are importing
|
||||
for (const auto& file_metadata : metadata_) {
|
||||
const auto file_path = file_metadata.db_path + "/" + file_metadata.name;
|
||||
IngestedFileInfo file_to_import;
|
||||
status = GetIngestedFileInfo(file_path, &file_to_import, sv);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
files_to_import_.push_back(file_to_import);
|
||||
}
|
||||
|
||||
const auto ucmp = cfd_->internal_comparator().user_comparator();
|
||||
auto num_files = files_to_import_.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
} else if (num_files > 1) {
|
||||
// Verify that passed files don't have overlapping ranges in any particular
|
||||
// level.
|
||||
int min_level = 1; // Check for overlaps in Level 1 and above.
|
||||
int max_level = -1;
|
||||
for (const auto& file_metadata : metadata_) {
|
||||
if (file_metadata.level > max_level) {
|
||||
max_level = file_metadata.level;
|
||||
}
|
||||
}
|
||||
for (int level = min_level; level <= max_level; ++level) {
|
||||
autovector<const IngestedFileInfo*> sorted_files;
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
if (metadata_[i].level == level) {
|
||||
sorted_files.push_back(&files_to_import_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(sorted_files.begin(), sorted_files.end(),
|
||||
[&ucmp](const IngestedFileInfo* info1,
|
||||
const IngestedFileInfo* info2) {
|
||||
return ucmp->Compare(info1->smallest_user_key,
|
||||
info2->smallest_user_key) < 0;
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < sorted_files.size() - 1; i++) {
|
||||
if (ucmp->Compare(sorted_files[i]->largest_user_key,
|
||||
sorted_files[i + 1]->smallest_user_key) >= 0) {
|
||||
return Status::InvalidArgument("Files have overlapping ranges");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& f : files_to_import_) {
|
||||
if (f.num_entries == 0) {
|
||||
return Status::InvalidArgument("File contain no entries");
|
||||
}
|
||||
|
||||
if (!f.smallest_internal_key().Valid() ||
|
||||
!f.largest_internal_key().Valid()) {
|
||||
return Status::Corruption("File has corrupted keys");
|
||||
}
|
||||
}
|
||||
|
||||
// Copy/Move external files into DB
|
||||
auto hardlink_files = import_options_.move_files;
|
||||
for (auto& f : files_to_import_) {
|
||||
f.fd = FileDescriptor(next_file_number++, 0, f.file_size);
|
||||
|
||||
const auto path_outside_db = f.external_file_path;
|
||||
const auto path_inside_db = TableFileName(
|
||||
cfd_->ioptions()->cf_paths, f.fd.GetNumber(), f.fd.GetPathId());
|
||||
|
||||
if (hardlink_files) {
|
||||
status = env_->LinkFile(path_outside_db, path_inside_db);
|
||||
if (status.IsNotSupported()) {
|
||||
// Original file is on a different FS, use copy instead of hard linking
|
||||
hardlink_files = false;
|
||||
}
|
||||
}
|
||||
if (!hardlink_files) {
|
||||
status = CopyFile(env_, path_outside_db, path_inside_db, 0,
|
||||
db_options_.use_fsync);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
f.copy_file = !hardlink_files;
|
||||
f.internal_file_path = path_inside_db;
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
// We failed, remove all files that we copied into the db
|
||||
for (const auto& f : files_to_import_) {
|
||||
if (f.internal_file_path.empty()) {
|
||||
break;
|
||||
}
|
||||
const auto s = env_->DeleteFile(f.internal_file_path);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
f.internal_file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// REQUIRES: we have become the only writer by entering both write_thread_ and
|
||||
// nonmem_write_thread_
|
||||
Status ImportColumnFamilyJob::Run() {
|
||||
Status status;
|
||||
edit_.SetColumnFamily(cfd_->GetID());
|
||||
|
||||
for (size_t i = 0; i < files_to_import_.size(); ++i) {
|
||||
const auto& f = files_to_import_[i];
|
||||
const auto& file_metadata = metadata_[i];
|
||||
edit_.AddFile(file_metadata.level, f.fd.GetNumber(), f.fd.GetPathId(),
|
||||
f.fd.GetFileSize(), f.smallest_internal_key(),
|
||||
f.largest_internal_key(), file_metadata.smallest_seqno,
|
||||
file_metadata.largest_seqno, false);
|
||||
|
||||
// If incoming sequence number is higher, update local sequence number.
|
||||
if (file_metadata.largest_seqno > versions_->LastSequence()) {
|
||||
versions_->SetLastAllocatedSequence(file_metadata.largest_seqno);
|
||||
versions_->SetLastPublishedSequence(file_metadata.largest_seqno);
|
||||
versions_->SetLastSequence(file_metadata.largest_seqno);
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void ImportColumnFamilyJob::Cleanup(const Status& status) {
|
||||
if (!status.ok()) {
|
||||
// We failed to add files to the database remove all the files we copied.
|
||||
for (const auto& f : files_to_import_) {
|
||||
const auto s = env_->DeleteFile(f.internal_file_path);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
f.internal_file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
} else if (status.ok() && import_options_.move_files) {
|
||||
// The files were moved and added successfully, remove original file links
|
||||
for (IngestedFileInfo& f : files_to_import_) {
|
||||
const auto s = env_->DeleteFile(f.external_file_path);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"%s was added to DB successfully but failed to remove original "
|
||||
"file link : %s",
|
||||
f.external_file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
const std::string& external_file, IngestedFileInfo* file_to_import,
|
||||
SuperVersion* sv) {
|
||||
file_to_import->external_file_path = external_file;
|
||||
|
||||
// Get external file size
|
||||
auto status = env_->GetFileSize(external_file, &file_to_import->file_size);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Create TableReader for external file
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
std::unique_ptr<RandomAccessFile> sst_file;
|
||||
std::unique_ptr<RandomAccessFileReader> sst_file_reader;
|
||||
|
||||
status = env_->NewRandomAccessFile(external_file, &sst_file, env_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
sst_file_reader.reset(new RandomAccessFileReader(std::move(sst_file),
|
||||
external_file));
|
||||
|
||||
status = cfd_->ioptions()->table_factory->NewTableReader(
|
||||
TableReaderOptions(*cfd_->ioptions(),
|
||||
sv->mutable_cf_options.prefix_extractor.get(),
|
||||
env_options_, cfd_->internal_comparator()),
|
||||
std::move(sst_file_reader), file_to_import->file_size, &table_reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Get the external file properties
|
||||
auto props = table_reader->GetTableProperties();
|
||||
|
||||
// Set original_seqno to 0.
|
||||
file_to_import->original_seqno = 0;
|
||||
|
||||
// Get number of entries in table
|
||||
file_to_import->num_entries = props->num_entries;
|
||||
|
||||
ParsedInternalKey key;
|
||||
ReadOptions ro;
|
||||
// During reading the external file we can cache blocks that we read into
|
||||
// the block cache, if we later change the global seqno of this file, we will
|
||||
// have block in cache that will include keys with wrong seqno.
|
||||
// We need to disable fill_cache so that we read from the file without
|
||||
// updating the block cache.
|
||||
ro.fill_cache = false;
|
||||
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
|
||||
ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
|
||||
|
||||
// Get first (smallest) key from file
|
||||
iter->SeekToFirst();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
}
|
||||
file_to_import->smallest_user_key = key.user_key.ToString();
|
||||
|
||||
// Get last (largest) key from file
|
||||
iter->SeekToLast();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
}
|
||||
file_to_import->largest_user_key = key.user_key.ToString();
|
||||
|
||||
file_to_import->cf_id = static_cast<uint32_t>(props->column_family_id);
|
||||
|
||||
file_to_import->table_properties = *props;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
#include "db/snapshot_impl.h"
|
||||
#include "options/db_options.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/metadata.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Imports a set of sst files as is into a new column family. Logic is similar
|
||||
// to ExternalSstFileIngestionJob.
|
||||
class ImportColumnFamilyJob {
|
||||
public:
|
||||
ImportColumnFamilyJob(
|
||||
Env* env, VersionSet* versions, ColumnFamilyData* cfd,
|
||||
const ImmutableDBOptions& db_options, const EnvOptions& env_options,
|
||||
const ImportColumnFamilyOptions& import_options,
|
||||
const std::vector<LiveFileMetaData>& metadata)
|
||||
: env_(env),
|
||||
versions_(versions),
|
||||
cfd_(cfd),
|
||||
db_options_(db_options),
|
||||
env_options_(env_options),
|
||||
import_options_(import_options),
|
||||
metadata_(metadata) {}
|
||||
|
||||
// Prepare the job by copying external files into the DB.
|
||||
Status Prepare(uint64_t next_file_number, SuperVersion* sv);
|
||||
|
||||
// Will execute the import job and prepare edit() to be applied.
|
||||
// REQUIRES: Mutex held
|
||||
Status Run();
|
||||
|
||||
// Cleanup after successful/failed job
|
||||
void Cleanup(const Status& status);
|
||||
|
||||
VersionEdit* edit() { return &edit_; }
|
||||
|
||||
const autovector<IngestedFileInfo>& files_to_import() const {
|
||||
return files_to_import_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Open the external file and populate `file_to_import` with all the
|
||||
// external information we need to import this file.
|
||||
Status GetIngestedFileInfo(const std::string& external_file,
|
||||
IngestedFileInfo* file_to_import,
|
||||
SuperVersion* sv);
|
||||
|
||||
Env* env_;
|
||||
VersionSet* versions_;
|
||||
ColumnFamilyData* cfd_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const EnvOptions& env_options_;
|
||||
autovector<IngestedFileInfo> files_to_import_;
|
||||
VersionEdit edit_;
|
||||
const ImportColumnFamilyOptions& import_options_;
|
||||
std::vector<LiveFileMetaData> metadata_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,568 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <functional>
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class ImportColumnFamilyTest : public DBTestBase {
|
||||
public:
|
||||
ImportColumnFamilyTest() : DBTestBase("/import_column_family_test") {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
export_files_dir_ = test::TmpDir(env_) + "/export";
|
||||
import_cfh_ = nullptr;
|
||||
import_cfh2_ = nullptr;
|
||||
metadata_ptr_ = nullptr;
|
||||
}
|
||||
|
||||
~ImportColumnFamilyTest() {
|
||||
if (import_cfh_) {
|
||||
db_->DropColumnFamily(import_cfh_);
|
||||
db_->DestroyColumnFamilyHandle(import_cfh_);
|
||||
import_cfh_ = nullptr;
|
||||
}
|
||||
if (import_cfh2_) {
|
||||
db_->DropColumnFamily(import_cfh2_);
|
||||
db_->DestroyColumnFamilyHandle(import_cfh2_);
|
||||
import_cfh2_ = nullptr;
|
||||
}
|
||||
if (metadata_ptr_) {
|
||||
delete metadata_ptr_;
|
||||
metadata_ptr_ = nullptr;
|
||||
}
|
||||
test::DestroyDir(env_, sst_files_dir_);
|
||||
test::DestroyDir(env_, export_files_dir_);
|
||||
}
|
||||
|
||||
void DestroyAndRecreateExternalSSTFilesDir() {
|
||||
test::DestroyDir(env_, sst_files_dir_);
|
||||
env_->CreateDir(sst_files_dir_);
|
||||
test::DestroyDir(env_, export_files_dir_);
|
||||
}
|
||||
|
||||
LiveFileMetaData LiveFileMetaDataInit(std::string name,
|
||||
std::string path,
|
||||
int level,
|
||||
SequenceNumber smallest_seqno,
|
||||
SequenceNumber largest_seqno) {
|
||||
LiveFileMetaData metadata;
|
||||
metadata.name = name;
|
||||
metadata.db_path = path;
|
||||
metadata.smallest_seqno = smallest_seqno;
|
||||
metadata.largest_seqno = largest_seqno;
|
||||
metadata.level = level;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string sst_files_dir_;
|
||||
std::string export_files_dir_;
|
||||
ColumnFamilyHandle* import_cfh_;
|
||||
ColumnFamilyHandle* import_cfh2_;
|
||||
ExportImportFilesMetaData *metadata_ptr_;
|
||||
};
|
||||
|
||||
TEST_F(ImportColumnFamilyTest, ImportSSTFileWriterFiles) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"koko"}, options);
|
||||
|
||||
SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]);
|
||||
SstFileWriter sfw_unknown(EnvOptions(), options);
|
||||
|
||||
// cf1.sst
|
||||
const std::string cf1_sst_name = "cf1.sst";
|
||||
const std::string cf1_sst = sst_files_dir_ + cf1_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(cf1_sst));
|
||||
ASSERT_OK(sfw_cf1.Put("K1", "V1"));
|
||||
ASSERT_OK(sfw_cf1.Put("K2", "V2"));
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
// cf_unknown.sst
|
||||
const std::string unknown_sst_name = "cf_unknown.sst";
|
||||
const std::string unknown_sst = sst_files_dir_ + unknown_sst_name;
|
||||
ASSERT_OK(sfw_unknown.Open(unknown_sst));
|
||||
ASSERT_OK(sfw_unknown.Put("K3", "V1"));
|
||||
ASSERT_OK(sfw_unknown.Put("K4", "V2"));
|
||||
ASSERT_OK(sfw_unknown.Finish());
|
||||
|
||||
{
|
||||
// Import sst file corresponding to cf1 onto a new cf and verify
|
||||
ExportImportFilesMetaData metadata;
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(cf1_sst_name, sst_files_dir_, 0, 10, 19));
|
||||
metadata.db_comparator_name = options.comparator->Name();
|
||||
|
||||
ASSERT_OK(db_->CreateColumnFamilyWithImport(
|
||||
options, "toto", ImportColumnFamilyOptions(), metadata, &import_cfh_));
|
||||
ASSERT_NE(import_cfh_, nullptr);
|
||||
|
||||
std::string value;
|
||||
db_->Get(ReadOptions(), import_cfh_, "K1", &value);
|
||||
ASSERT_EQ(value, "V1");
|
||||
db_->Get(ReadOptions(), import_cfh_, "K2", &value);
|
||||
ASSERT_EQ(value, "V2");
|
||||
ASSERT_OK(db_->DropColumnFamily(import_cfh_));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(import_cfh_));
|
||||
import_cfh_ = nullptr;
|
||||
}
|
||||
|
||||
{
|
||||
// Import sst file corresponding to unknown cf onto a new cf and verify
|
||||
ExportImportFilesMetaData metadata;
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(unknown_sst_name, sst_files_dir_, 0, 20, 29));
|
||||
metadata.db_comparator_name = options.comparator->Name();
|
||||
|
||||
ASSERT_OK(db_->CreateColumnFamilyWithImport(
|
||||
options, "yoyo", ImportColumnFamilyOptions(), metadata, &import_cfh_));
|
||||
ASSERT_NE(import_cfh_, nullptr);
|
||||
|
||||
std::string value;
|
||||
db_->Get(ReadOptions(), import_cfh_, "K3", &value);
|
||||
ASSERT_EQ(value, "V1");
|
||||
db_->Get(ReadOptions(), import_cfh_, "K4", &value);
|
||||
ASSERT_EQ(value, "V2");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImportColumnFamilyTest, ImportSSTFileWriterFilesWithOverlap) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"koko"}, options);
|
||||
|
||||
SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]);
|
||||
|
||||
// file3.sst
|
||||
const std::string file3_sst_name = "file3.sst";
|
||||
const std::string file3_sst = sst_files_dir_ + file3_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file3_sst));
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
sfw_cf1.Put(Key(i), Key(i) + "_val");
|
||||
}
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
// file2.sst
|
||||
const std::string file2_sst_name = "file2.sst";
|
||||
const std::string file2_sst = sst_files_dir_ + file2_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file2_sst));
|
||||
for (int i = 0; i < 100; i += 2) {
|
||||
sfw_cf1.Put(Key(i), Key(i) + "_overwrite1");
|
||||
}
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
// file1a.sst
|
||||
const std::string file1a_sst_name = "file1a.sst";
|
||||
const std::string file1a_sst = sst_files_dir_ + file1a_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file1a_sst));
|
||||
for (int i = 0; i < 52; i += 4) {
|
||||
sfw_cf1.Put(Key(i), Key(i) + "_overwrite2");
|
||||
}
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
// file1b.sst
|
||||
const std::string file1b_sst_name = "file1b.sst";
|
||||
const std::string file1b_sst = sst_files_dir_ + file1b_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file1b_sst));
|
||||
for (int i = 52; i < 100; i += 4) {
|
||||
sfw_cf1.Put(Key(i), Key(i) + "_overwrite2");
|
||||
}
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
// file0a.sst
|
||||
const std::string file0a_sst_name = "file0a.sst";
|
||||
const std::string file0a_sst = sst_files_dir_ + file0a_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file0a_sst));
|
||||
for (int i = 0; i < 100; i += 16) {
|
||||
sfw_cf1.Put(Key(i), Key(i) + "_overwrite3");
|
||||
}
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
// file0b.sst
|
||||
const std::string file0b_sst_name = "file0b.sst";
|
||||
const std::string file0b_sst = sst_files_dir_ + file0b_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file0b_sst));
|
||||
for (int i = 0; i < 100; i += 16) {
|
||||
sfw_cf1.Put(Key(i), Key(i) + "_overwrite4");
|
||||
}
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
// Import sst files and verify
|
||||
ExportImportFilesMetaData metadata;
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file3_sst_name, sst_files_dir_, 3, 10, 19));
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file2_sst_name, sst_files_dir_, 2, 20, 29));
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file1a_sst_name, sst_files_dir_, 1, 30, 34));
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file1b_sst_name, sst_files_dir_, 1, 35, 39));
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file0a_sst_name, sst_files_dir_, 0, 40, 49));
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file0b_sst_name, sst_files_dir_, 0, 50, 59));
|
||||
metadata.db_comparator_name = options.comparator->Name();
|
||||
|
||||
ASSERT_OK(db_->CreateColumnFamilyWithImport(
|
||||
options, "toto", ImportColumnFamilyOptions(), metadata, &import_cfh_));
|
||||
ASSERT_NE(import_cfh_, nullptr);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string value;
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value);
|
||||
if (i % 16 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite4");
|
||||
} else if (i % 4 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite2");
|
||||
} else if (i % 2 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite1");
|
||||
} else {
|
||||
ASSERT_EQ(value, Key(i) + "_val");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 100; i += 5) {
|
||||
ASSERT_OK(
|
||||
db_->Put(WriteOptions(), import_cfh_, Key(i), Key(i) + "_overwrite5"));
|
||||
}
|
||||
|
||||
// Flush and check again
|
||||
ASSERT_OK(db_->Flush(FlushOptions(), import_cfh_));
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string value;
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value);
|
||||
if (i % 5 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite5");
|
||||
} else if (i % 16 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite4");
|
||||
} else if (i % 4 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite2");
|
||||
} else if (i % 2 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite1");
|
||||
} else {
|
||||
ASSERT_EQ(value, Key(i) + "_val");
|
||||
}
|
||||
}
|
||||
|
||||
// Compact and check again.
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(CompactRangeOptions(), import_cfh_, nullptr, nullptr));
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string value;
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value);
|
||||
if (i % 5 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite5");
|
||||
} else if (i % 16 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite4");
|
||||
} else if (i % 4 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite2");
|
||||
} else if (i % 2 == 0) {
|
||||
ASSERT_EQ(value, Key(i) + "_overwrite1");
|
||||
} else {
|
||||
ASSERT_EQ(value, Key(i) + "_val");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImportColumnFamilyTest, ImportExportedSSTFromAnotherCF) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"koko"}, options);
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
Put(1, Key(i), Key(i) + "_val");
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr));
|
||||
|
||||
// Overwrite the value in the same set of keys.
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
Put(1, Key(i), Key(i) + "_overwrite");
|
||||
}
|
||||
|
||||
// Flush to create L0 file.
|
||||
ASSERT_OK(Flush(1));
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
Put(1, Key(i), Key(i) + "_overwrite2");
|
||||
}
|
||||
|
||||
// Flush again to create another L0 file. It should have higher sequencer.
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
Checkpoint* checkpoint;
|
||||
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
|
||||
ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_,
|
||||
&metadata_ptr_));
|
||||
ASSERT_NE(metadata_ptr_, nullptr);
|
||||
delete checkpoint;
|
||||
|
||||
ImportColumnFamilyOptions import_options;
|
||||
import_options.move_files = false;
|
||||
ASSERT_OK(db_->CreateColumnFamilyWithImport(options, "toto", import_options,
|
||||
*metadata_ptr_, &import_cfh_));
|
||||
ASSERT_NE(import_cfh_, nullptr);
|
||||
|
||||
import_options.move_files = true;
|
||||
ASSERT_OK(db_->CreateColumnFamilyWithImport(options, "yoyo", import_options,
|
||||
*metadata_ptr_, &import_cfh2_));
|
||||
ASSERT_NE(import_cfh2_, nullptr);
|
||||
delete metadata_ptr_;
|
||||
metadata_ptr_ = NULL;
|
||||
|
||||
std::string value1, value2;
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
|
||||
ASSERT_EQ(Get(1, Key(i)), value1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
db_->Get(ReadOptions(), import_cfh2_, Key(i), &value2);
|
||||
ASSERT_EQ(Get(1, Key(i)), value2);
|
||||
}
|
||||
|
||||
// Modify keys in cf1 and verify.
|
||||
for (int i = 0; i < 25; i++) {
|
||||
ASSERT_OK(db_->Delete(WriteOptions(), import_cfh_, Key(i)));
|
||||
}
|
||||
for (int i = 25; i < 50; i++) {
|
||||
ASSERT_OK(
|
||||
db_->Put(WriteOptions(), import_cfh_, Key(i), Key(i) + "_overwrite3"));
|
||||
}
|
||||
for (int i = 0; i < 25; ++i) {
|
||||
ASSERT_TRUE(
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value1).IsNotFound());
|
||||
}
|
||||
for (int i = 25; i < 50; ++i) {
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
|
||||
ASSERT_EQ(Key(i) + "_overwrite3", value1);
|
||||
}
|
||||
for (int i = 50; i < 100; ++i) {
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
|
||||
ASSERT_EQ(Key(i) + "_overwrite2", value1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
db_->Get(ReadOptions(), import_cfh2_, Key(i), &value2);
|
||||
ASSERT_EQ(Get(1, Key(i)), value2);
|
||||
}
|
||||
|
||||
// Compact and check again.
|
||||
ASSERT_OK(db_->Flush(FlushOptions(), import_cfh_));
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(CompactRangeOptions(), import_cfh_, nullptr, nullptr));
|
||||
|
||||
for (int i = 0; i < 25; ++i) {
|
||||
ASSERT_TRUE(
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value1).IsNotFound());
|
||||
}
|
||||
for (int i = 25; i < 50; ++i) {
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
|
||||
ASSERT_EQ(Key(i) + "_overwrite3", value1);
|
||||
}
|
||||
for (int i = 50; i < 100; ++i) {
|
||||
db_->Get(ReadOptions(), import_cfh_, Key(i), &value1);
|
||||
ASSERT_EQ(Key(i) + "_overwrite2", value1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
db_->Get(ReadOptions(), import_cfh2_, Key(i), &value2);
|
||||
ASSERT_EQ(Get(1, Key(i)), value2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImportColumnFamilyTest, ImportExportedSSTFromAnotherDB) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"koko"}, options);
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
Put(1, Key(i), Key(i) + "_val");
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// Compact to create a L1 file.
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr));
|
||||
|
||||
// Overwrite the value in the same set of keys.
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
Put(1, Key(i), Key(i) + "_overwrite");
|
||||
}
|
||||
|
||||
// Flush to create L0 file.
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
for (int i = 0; i < 25; ++i) {
|
||||
Put(1, Key(i), Key(i) + "_overwrite2");
|
||||
}
|
||||
|
||||
// Flush again to create another L0 file. It should have higher sequencer.
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
Checkpoint* checkpoint;
|
||||
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
|
||||
ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_,
|
||||
&metadata_ptr_));
|
||||
ASSERT_NE(metadata_ptr_, nullptr);
|
||||
delete checkpoint;
|
||||
|
||||
// Create a new db and import the files.
|
||||
DB* db_copy;
|
||||
test::DestroyDir(env_, dbname_ + "/db_copy");
|
||||
ASSERT_OK(DB::Open(options, dbname_ + "/db_copy", &db_copy));
|
||||
ColumnFamilyHandle* cfh = nullptr;
|
||||
ASSERT_OK(db_copy->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo",
|
||||
ImportColumnFamilyOptions(),
|
||||
*metadata_ptr_, &cfh));
|
||||
ASSERT_NE(cfh, nullptr);
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::string value;
|
||||
db_copy->Get(ReadOptions(), cfh, Key(i), &value);
|
||||
ASSERT_EQ(Get(1, Key(i)), value);
|
||||
}
|
||||
db_copy->DropColumnFamily(cfh);
|
||||
db_copy->DestroyColumnFamilyHandle(cfh);
|
||||
test::DestroyDir(env_, dbname_ + "/db_copy");
|
||||
}
|
||||
|
||||
TEST_F(ImportColumnFamilyTest, ImportColumnFamilyNegativeTest) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"koko"}, options);
|
||||
|
||||
{
|
||||
// Create column family with existing cf name.
|
||||
ExportImportFilesMetaData metadata;
|
||||
|
||||
ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "koko",
|
||||
ImportColumnFamilyOptions(),
|
||||
metadata, &import_cfh_),
|
||||
Status::InvalidArgument("Column family already exists"));
|
||||
ASSERT_EQ(import_cfh_, nullptr);
|
||||
}
|
||||
|
||||
{
|
||||
// Import with no files specified.
|
||||
ExportImportFilesMetaData metadata;
|
||||
|
||||
ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo",
|
||||
ImportColumnFamilyOptions(),
|
||||
metadata, &import_cfh_),
|
||||
Status::InvalidArgument("The list of files is empty"));
|
||||
ASSERT_EQ(import_cfh_, nullptr);
|
||||
}
|
||||
|
||||
{
|
||||
// Import with overlapping keys in sst files.
|
||||
ExportImportFilesMetaData metadata;
|
||||
SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]);
|
||||
const std::string file1_sst_name = "file1.sst";
|
||||
const std::string file1_sst = sst_files_dir_ + file1_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file1_sst));
|
||||
ASSERT_OK(sfw_cf1.Put("K1", "V1"));
|
||||
ASSERT_OK(sfw_cf1.Put("K2", "V2"));
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
const std::string file2_sst_name = "file2.sst";
|
||||
const std::string file2_sst = sst_files_dir_ + file2_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file2_sst));
|
||||
ASSERT_OK(sfw_cf1.Put("K2", "V2"));
|
||||
ASSERT_OK(sfw_cf1.Put("K3", "V3"));
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file1_sst_name, sst_files_dir_, 1, 10, 19));
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file2_sst_name, sst_files_dir_, 1, 10, 19));
|
||||
metadata.db_comparator_name = options.comparator->Name();
|
||||
|
||||
ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo",
|
||||
ImportColumnFamilyOptions(),
|
||||
metadata, &import_cfh_),
|
||||
Status::InvalidArgument("Files have overlapping ranges"));
|
||||
ASSERT_EQ(import_cfh_, nullptr);
|
||||
}
|
||||
|
||||
{
|
||||
// Import with a mismatching comparator, should fail with appropriate error.
|
||||
ExportImportFilesMetaData metadata;
|
||||
Options mismatch_options = CurrentOptions();
|
||||
mismatch_options.comparator = ReverseBytewiseComparator();
|
||||
SstFileWriter sfw_cf1(EnvOptions(), mismatch_options, handles_[1]);
|
||||
const std::string file1_sst_name = "file1.sst";
|
||||
const std::string file1_sst = sst_files_dir_ + file1_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file1_sst));
|
||||
ASSERT_OK(sfw_cf1.Put("K2", "V2"));
|
||||
ASSERT_OK(sfw_cf1.Put("K1", "V1"));
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file1_sst_name, sst_files_dir_, 1, 10, 19));
|
||||
metadata.db_comparator_name = mismatch_options.comparator->Name();
|
||||
|
||||
ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "coco",
|
||||
ImportColumnFamilyOptions(),
|
||||
metadata, &import_cfh_),
|
||||
Status::InvalidArgument("Comparator name mismatch"));
|
||||
ASSERT_EQ(import_cfh_, nullptr);
|
||||
}
|
||||
|
||||
{
|
||||
// Import with non existent sst file should fail with appropriate error
|
||||
ExportImportFilesMetaData metadata;
|
||||
SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]);
|
||||
const std::string file1_sst_name = "file1.sst";
|
||||
const std::string file1_sst = sst_files_dir_ + file1_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file1_sst));
|
||||
ASSERT_OK(sfw_cf1.Put("K1", "V1"));
|
||||
ASSERT_OK(sfw_cf1.Put("K2", "V2"));
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
const std::string file3_sst_name = "file3.sst";
|
||||
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file1_sst_name, sst_files_dir_, 1, 10, 19));
|
||||
metadata.files.push_back(
|
||||
LiveFileMetaDataInit(file3_sst_name, sst_files_dir_, 1, 10, 19));
|
||||
metadata.db_comparator_name = options.comparator->Name();
|
||||
|
||||
ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo",
|
||||
ImportColumnFamilyOptions(),
|
||||
metadata, &import_cfh_),
|
||||
Status::IOError("No such file or directory"));
|
||||
ASSERT_EQ(import_cfh_, nullptr);
|
||||
|
||||
// Test successful import after a failure with the same CF name. Ensures
|
||||
// there is no side effect with CF when there is a failed import
|
||||
metadata.files.pop_back();
|
||||
metadata.db_comparator_name = options.comparator->Name();
|
||||
|
||||
ASSERT_OK(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo",
|
||||
ImportColumnFamilyOptions(),
|
||||
metadata, &import_cfh_));
|
||||
ASSERT_NE(import_cfh_, nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as External SST File Writer and Import are not supported "
|
||||
"in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -237,6 +237,28 @@ class InternalStats {
|
||||
}
|
||||
}
|
||||
|
||||
CompactionStats& operator=(const CompactionStats& c) {
|
||||
micros = c.micros;
|
||||
cpu_micros = c.cpu_micros;
|
||||
bytes_read_non_output_levels = c.bytes_read_non_output_levels;
|
||||
bytes_read_output_level = c.bytes_read_output_level;
|
||||
bytes_written = c.bytes_written;
|
||||
bytes_moved = c.bytes_moved;
|
||||
num_input_files_in_non_output_levels =
|
||||
c.num_input_files_in_non_output_levels;
|
||||
num_input_files_in_output_level = c.num_input_files_in_output_level;
|
||||
num_output_files = c.num_output_files;
|
||||
num_input_records = c.num_input_records;
|
||||
num_dropped_records = c.num_dropped_records;
|
||||
count = c.count;
|
||||
|
||||
int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
|
||||
for (int i = 0; i < num_of_reasons; i++) {
|
||||
counts[i] = c.counts[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
this->micros = 0;
|
||||
this->cpu_micros = 0;
|
||||
|
||||
@@ -20,10 +20,6 @@ namespace rocksdb {
|
||||
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
|
||||
#ifdef JEMALLOC_NO_RENAME
|
||||
#define malloc_stats_print je_malloc_stats_print
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char* cur;
|
||||
char* end;
|
||||
|
||||
+5
-1
@@ -520,7 +520,11 @@ class Repairer {
|
||||
InternalIterator* iter = table_cache_->NewIterator(
|
||||
ropts, env_options_, cfd->internal_comparator(), t->meta,
|
||||
nullptr /* range_del_agg */,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get(),
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kRepair, /*arena=*/nullptr, /*skip_filters=*/false,
|
||||
/*level=*/-1, /*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr);
|
||||
bool empty = true;
|
||||
ParsedInternalKey parsed;
|
||||
t->min_sequence = 0;
|
||||
|
||||
+20
-65
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "table/get_context.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
@@ -43,13 +44,6 @@ static void UnrefEntry(void* arg1, void* arg2) {
|
||||
cache->Release(h);
|
||||
}
|
||||
|
||||
static void DeleteTableReader(void* arg1, void* arg2) {
|
||||
TableReader* table_reader = reinterpret_cast<TableReader*>(arg1);
|
||||
Statistics* stats = reinterpret_cast<Statistics*>(arg2);
|
||||
RecordTick(stats, NO_FILE_CLOSES);
|
||||
delete table_reader;
|
||||
}
|
||||
|
||||
static Slice GetSliceForFileNumber(const uint64_t* file_number) {
|
||||
return Slice(reinterpret_cast<const char*>(file_number),
|
||||
sizeof(*file_number));
|
||||
@@ -96,10 +90,10 @@ void TableCache::ReleaseHandle(Cache::Handle* handle) {
|
||||
Status TableCache::GetTableReader(
|
||||
const EnvOptions& env_options,
|
||||
const InternalKeyComparator& internal_comparator, const FileDescriptor& fd,
|
||||
bool sequential_mode, size_t readahead, bool record_read_stats,
|
||||
HistogramImpl* file_read_hist, std::unique_ptr<TableReader>* table_reader,
|
||||
bool sequential_mode, bool record_read_stats, HistogramImpl* file_read_hist,
|
||||
std::unique_ptr<TableReader>* table_reader,
|
||||
const SliceTransform* prefix_extractor, bool skip_filters, int level,
|
||||
bool prefetch_index_and_filter_in_cache, bool for_compaction) {
|
||||
bool prefetch_index_and_filter_in_cache) {
|
||||
std::string fname =
|
||||
TableFileName(ioptions_.cf_paths, fd.GetNumber(), fd.GetPathId());
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
@@ -107,13 +101,6 @@ Status TableCache::GetTableReader(
|
||||
|
||||
RecordTick(ioptions_.statistics, NO_FILE_OPENS);
|
||||
if (s.ok()) {
|
||||
if (readahead > 0 && !env_options.use_mmap_reads) {
|
||||
// Not compatible with mmap files since ReadaheadRandomAccessFile requires
|
||||
// its wrapped file's Read() to copy data into the provided scratch
|
||||
// buffer, which mmap files don't use.
|
||||
// TODO(ajkr): try madvise for mmap files in place of buffered readahead.
|
||||
file = NewReadaheadRandomAccessFile(std::move(file), readahead);
|
||||
}
|
||||
if (!sequential_mode && ioptions_.advise_random_on_open) {
|
||||
file->Hint(RandomAccessFile::RANDOM);
|
||||
}
|
||||
@@ -122,8 +109,7 @@ Status TableCache::GetTableReader(
|
||||
new RandomAccessFileReader(
|
||||
std::move(file), fname, ioptions_.env,
|
||||
record_read_stats ? ioptions_.statistics : nullptr, SST_READ_MICROS,
|
||||
file_read_hist, ioptions_.rate_limiter, for_compaction,
|
||||
ioptions_.listeners));
|
||||
file_read_hist, ioptions_.rate_limiter, ioptions_.listeners));
|
||||
s = ioptions_.table_factory->NewTableReader(
|
||||
TableReaderOptions(ioptions_, prefix_extractor, env_options,
|
||||
internal_comparator, skip_filters, immortal_tables_,
|
||||
@@ -164,10 +150,9 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
}
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
s = GetTableReader(env_options, internal_comparator, fd,
|
||||
false /* sequential mode */, 0 /* readahead */,
|
||||
record_read_stats, file_read_hist, &table_reader,
|
||||
prefix_extractor, skip_filters, level,
|
||||
prefetch_index_and_filter_in_cache);
|
||||
false /* sequential mode */, record_read_stats,
|
||||
file_read_hist, &table_reader, prefix_extractor,
|
||||
skip_filters, level, prefetch_index_and_filter_in_cache);
|
||||
if (!s.ok()) {
|
||||
assert(table_reader == nullptr);
|
||||
RecordTick(ioptions_.statistics, NO_FILE_ERRORS);
|
||||
@@ -190,54 +175,27 @@ InternalIterator* TableCache::NewIterator(
|
||||
const InternalKeyComparator& icomparator, const FileMetaData& file_meta,
|
||||
RangeDelAggregator* range_del_agg, const SliceTransform* prefix_extractor,
|
||||
TableReader** table_reader_ptr, HistogramImpl* file_read_hist,
|
||||
bool for_compaction, Arena* arena, bool skip_filters, int level,
|
||||
TableReaderCaller caller, Arena* arena, bool skip_filters, int level,
|
||||
const InternalKey* smallest_compaction_key,
|
||||
const InternalKey* largest_compaction_key) {
|
||||
PERF_TIMER_GUARD(new_table_iterator_nanos);
|
||||
|
||||
Status s;
|
||||
bool create_new_table_reader = false;
|
||||
TableReader* table_reader = nullptr;
|
||||
Cache::Handle* handle = nullptr;
|
||||
if (table_reader_ptr != nullptr) {
|
||||
*table_reader_ptr = nullptr;
|
||||
}
|
||||
size_t readahead = 0;
|
||||
if (for_compaction) {
|
||||
#ifndef NDEBUG
|
||||
bool use_direct_reads_for_compaction = env_options.use_direct_reads;
|
||||
TEST_SYNC_POINT_CALLBACK("TableCache::NewIterator:for_compaction",
|
||||
&use_direct_reads_for_compaction);
|
||||
#endif // !NDEBUG
|
||||
if (ioptions_.new_table_reader_for_compaction_inputs) {
|
||||
// get compaction_readahead_size from env_options allows us to set the
|
||||
// value dynamically
|
||||
readahead = env_options.compaction_readahead_size;
|
||||
create_new_table_reader = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool for_compaction = caller == TableReaderCaller::kCompaction;
|
||||
auto& fd = file_meta.fd;
|
||||
if (create_new_table_reader) {
|
||||
std::unique_ptr<TableReader> table_reader_unique_ptr;
|
||||
s = GetTableReader(
|
||||
env_options, icomparator, fd, true /* sequential_mode */, readahead,
|
||||
!for_compaction /* record stats */, nullptr, &table_reader_unique_ptr,
|
||||
prefix_extractor, false /* skip_filters */, level,
|
||||
true /* prefetch_index_and_filter_in_cache */, for_compaction);
|
||||
table_reader = fd.table_reader;
|
||||
if (table_reader == nullptr) {
|
||||
s = FindTable(env_options, icomparator, fd, &handle, prefix_extractor,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
!for_compaction /* record read_stats */, file_read_hist,
|
||||
skip_filters, level);
|
||||
if (s.ok()) {
|
||||
table_reader = table_reader_unique_ptr.release();
|
||||
}
|
||||
} else {
|
||||
table_reader = fd.table_reader;
|
||||
if (table_reader == nullptr) {
|
||||
s = FindTable(env_options, icomparator, fd, &handle, prefix_extractor,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
!for_compaction /* record read_stats */, file_read_hist,
|
||||
skip_filters, level);
|
||||
if (s.ok()) {
|
||||
table_reader = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
table_reader = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
}
|
||||
InternalIterator* result = nullptr;
|
||||
@@ -247,13 +205,10 @@ InternalIterator* TableCache::NewIterator(
|
||||
result = NewEmptyInternalIterator<Slice>(arena);
|
||||
} else {
|
||||
result = table_reader->NewIterator(options, prefix_extractor, arena,
|
||||
skip_filters, for_compaction);
|
||||
skip_filters, caller,
|
||||
env_options.compaction_readahead_size);
|
||||
}
|
||||
if (create_new_table_reader) {
|
||||
assert(handle == nullptr);
|
||||
result->RegisterCleanup(&DeleteTableReader, table_reader,
|
||||
ioptions_.statistics);
|
||||
} else if (handle != nullptr) {
|
||||
if (handle != nullptr) {
|
||||
result->RegisterCleanup(&UnrefEntry, cache_, handle);
|
||||
handle = nullptr; // prevent from releasing below
|
||||
}
|
||||
|
||||
+6
-10
@@ -70,12 +70,10 @@ class TableCache {
|
||||
const ReadOptions& options, const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, RangeDelAggregator* range_del_agg,
|
||||
const SliceTransform* prefix_extractor = nullptr,
|
||||
TableReader** table_reader_ptr = nullptr,
|
||||
HistogramImpl* file_read_hist = nullptr, bool for_compaction = false,
|
||||
Arena* arena = nullptr, bool skip_filters = false, int level = -1,
|
||||
const InternalKey* smallest_compaction_key = nullptr,
|
||||
const InternalKey* largest_compaction_key = nullptr);
|
||||
const SliceTransform* prefix_extractor, TableReader** table_reader_ptr,
|
||||
HistogramImpl* file_read_hist, TableReaderCaller caller, Arena* arena,
|
||||
bool skip_filters, int level, const InternalKey* smallest_compaction_key,
|
||||
const InternalKey* largest_compaction_key);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
// call get_context->SaveValue() repeatedly until
|
||||
@@ -177,13 +175,11 @@ class TableCache {
|
||||
Status GetTableReader(const EnvOptions& env_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& fd, bool sequential_mode,
|
||||
size_t readahead, bool record_read_stats,
|
||||
HistogramImpl* file_read_hist,
|
||||
bool record_read_stats, HistogramImpl* file_read_hist,
|
||||
std::unique_ptr<TableReader>* table_reader,
|
||||
const SliceTransform* prefix_extractor = nullptr,
|
||||
bool skip_filters = false, int level = -1,
|
||||
bool prefetch_index_and_filter_in_cache = true,
|
||||
bool for_compaction = false);
|
||||
bool prefetch_index_and_filter_in_cache = true);
|
||||
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
const EnvOptions& env_options_;
|
||||
|
||||
@@ -199,7 +199,8 @@ void TransactionLogIteratorImpl::NextImpl(bool internal) {
|
||||
if (current_last_seq_ == versions_->LastSequence()) {
|
||||
current_status_ = Status::OK();
|
||||
} else {
|
||||
current_status_ = Status::Corruption("NO MORE DATA LEFT");
|
||||
const char* msg = "Create a new iterator to fetch the new tail.";
|
||||
current_status_ = Status::TryAgain(msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ struct FileDescriptor {
|
||||
smallest_seqno(_smallest_seqno),
|
||||
largest_seqno(_largest_seqno) {}
|
||||
|
||||
FileDescriptor(const FileDescriptor& fd) { *this=fd; }
|
||||
|
||||
FileDescriptor& operator=(const FileDescriptor& fd) {
|
||||
table_reader = fd.table_reader;
|
||||
packed_number_and_path_id = fd.packed_number_and_path_id;
|
||||
|
||||
+117
-54
@@ -850,15 +850,17 @@ namespace {
|
||||
|
||||
class LevelIterator final : public InternalIterator {
|
||||
public:
|
||||
LevelIterator(
|
||||
TableCache* table_cache, const ReadOptions& read_options,
|
||||
const EnvOptions& env_options, const InternalKeyComparator& icomparator,
|
||||
const LevelFilesBrief* flevel, const SliceTransform* prefix_extractor,
|
||||
bool should_sample, HistogramImpl* file_read_hist, bool for_compaction,
|
||||
bool skip_filters, int level, RangeDelAggregator* range_del_agg,
|
||||
const std::vector<AtomicCompactionUnitBoundary>* compaction_boundaries =
|
||||
nullptr)
|
||||
: table_cache_(table_cache),
|
||||
LevelIterator(TableCache* table_cache, const ReadOptions& read_options,
|
||||
const EnvOptions& env_options,
|
||||
const InternalKeyComparator& icomparator,
|
||||
const LevelFilesBrief* flevel,
|
||||
const SliceTransform* prefix_extractor, bool should_sample,
|
||||
HistogramImpl* file_read_hist, TableReaderCaller caller,
|
||||
bool skip_filters, int level, RangeDelAggregator* range_del_agg,
|
||||
const std::vector<AtomicCompactionUnitBoundary>*
|
||||
compaction_boundaries = nullptr)
|
||||
: InternalIterator(false),
|
||||
table_cache_(table_cache),
|
||||
read_options_(read_options),
|
||||
env_options_(env_options),
|
||||
icomparator_(icomparator),
|
||||
@@ -867,7 +869,7 @@ class LevelIterator final : public InternalIterator {
|
||||
prefix_extractor_(prefix_extractor),
|
||||
file_read_hist_(file_read_hist),
|
||||
should_sample_(should_sample),
|
||||
for_compaction_(for_compaction),
|
||||
caller_(caller),
|
||||
skip_filters_(skip_filters),
|
||||
file_index_(flevel_->num_files),
|
||||
level_(level),
|
||||
@@ -885,7 +887,7 @@ class LevelIterator final : public InternalIterator {
|
||||
void SeekToFirst() override;
|
||||
void SeekToLast() override;
|
||||
void Next() final override;
|
||||
bool NextAndGetResult(Slice* ret_key) override;
|
||||
bool NextAndGetResult(IterateResult* result) override;
|
||||
void Prev() override;
|
||||
|
||||
bool Valid() const override { return file_iter_.Valid(); }
|
||||
@@ -893,23 +895,38 @@ class LevelIterator final : public InternalIterator {
|
||||
assert(Valid());
|
||||
return file_iter_.key();
|
||||
}
|
||||
|
||||
Slice value() const override {
|
||||
assert(Valid());
|
||||
return file_iter_.value();
|
||||
}
|
||||
|
||||
Status status() const override {
|
||||
return file_iter_.iter() ? file_iter_.status() : Status::OK();
|
||||
}
|
||||
|
||||
inline bool MayBeOutOfLowerBound() override {
|
||||
assert(Valid());
|
||||
return may_be_out_of_lower_bound_ && file_iter_.MayBeOutOfLowerBound();
|
||||
}
|
||||
|
||||
inline bool MayBeOutOfUpperBound() override {
|
||||
assert(Valid());
|
||||
return file_iter_.MayBeOutOfUpperBound();
|
||||
}
|
||||
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
pinned_iters_mgr_ = pinned_iters_mgr;
|
||||
if (file_iter_.iter()) {
|
||||
file_iter_.SetPinnedItersMgr(pinned_iters_mgr);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsKeyPinned() const override {
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
file_iter_.iter() && file_iter_.IsKeyPinned();
|
||||
}
|
||||
|
||||
bool IsValuePinned() const override {
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
file_iter_.iter() && file_iter_.IsValuePinned();
|
||||
@@ -953,12 +970,27 @@ class LevelIterator final : public InternalIterator {
|
||||
smallest_compaction_key = (*compaction_boundaries_)[file_index_].smallest;
|
||||
largest_compaction_key = (*compaction_boundaries_)[file_index_].largest;
|
||||
}
|
||||
CheckMayBeOutOfLowerBound();
|
||||
return table_cache_->NewIterator(
|
||||
read_options_, env_options_, icomparator_, *file_meta.file_metadata,
|
||||
range_del_agg_, prefix_extractor_,
|
||||
nullptr /* don't need reference to table */,
|
||||
file_read_hist_, for_compaction_, nullptr /* arena */, skip_filters_,
|
||||
level_, smallest_compaction_key, largest_compaction_key);
|
||||
nullptr /* don't need reference to table */, file_read_hist_, caller_,
|
||||
/*arena=*/nullptr, skip_filters_, level_, smallest_compaction_key,
|
||||
largest_compaction_key);
|
||||
}
|
||||
|
||||
// Check if current file being fully within iterate_lower_bound.
|
||||
//
|
||||
// Note MyRocks may update iterate bounds between seek. To workaround it,
|
||||
// we need to check and update may_be_out_of_lower_bound_ accordingly.
|
||||
void CheckMayBeOutOfLowerBound() {
|
||||
if (read_options_.iterate_lower_bound != nullptr &&
|
||||
file_index_ < flevel_->num_files) {
|
||||
may_be_out_of_lower_bound_ =
|
||||
user_comparator_.Compare(
|
||||
ExtractUserKey(file_smallest_key(file_index_)),
|
||||
*read_options_.iterate_lower_bound) < 0;
|
||||
}
|
||||
}
|
||||
|
||||
TableCache* table_cache_;
|
||||
@@ -972,8 +1004,9 @@ class LevelIterator final : public InternalIterator {
|
||||
|
||||
HistogramImpl* file_read_hist_;
|
||||
bool should_sample_;
|
||||
bool for_compaction_;
|
||||
TableReaderCaller caller_;
|
||||
bool skip_filters_;
|
||||
bool may_be_out_of_lower_bound_ = true;
|
||||
size_t file_index_;
|
||||
int level_;
|
||||
RangeDelAggregator* range_del_agg_;
|
||||
@@ -1009,6 +1042,7 @@ void LevelIterator::Seek(const Slice& target) {
|
||||
file_iter_.Seek(target);
|
||||
}
|
||||
SkipEmptyFileForward();
|
||||
CheckMayBeOutOfLowerBound();
|
||||
}
|
||||
|
||||
void LevelIterator::SeekForPrev(const Slice& target) {
|
||||
@@ -1022,6 +1056,7 @@ void LevelIterator::SeekForPrev(const Slice& target) {
|
||||
file_iter_.SeekForPrev(target);
|
||||
SkipEmptyFileBackward();
|
||||
}
|
||||
CheckMayBeOutOfLowerBound();
|
||||
}
|
||||
|
||||
void LevelIterator::SeekToFirst() {
|
||||
@@ -1030,6 +1065,7 @@ void LevelIterator::SeekToFirst() {
|
||||
file_iter_.SeekToFirst();
|
||||
}
|
||||
SkipEmptyFileForward();
|
||||
CheckMayBeOutOfLowerBound();
|
||||
}
|
||||
|
||||
void LevelIterator::SeekToLast() {
|
||||
@@ -1038,15 +1074,17 @@ void LevelIterator::SeekToLast() {
|
||||
file_iter_.SeekToLast();
|
||||
}
|
||||
SkipEmptyFileBackward();
|
||||
CheckMayBeOutOfLowerBound();
|
||||
}
|
||||
|
||||
void LevelIterator::Next() { NextImpl(); }
|
||||
|
||||
bool LevelIterator::NextAndGetResult(Slice* ret_key) {
|
||||
bool LevelIterator::NextAndGetResult(IterateResult* result) {
|
||||
NextImpl();
|
||||
bool is_valid = Valid();
|
||||
if (is_valid) {
|
||||
*ret_key = key();
|
||||
result->key = key();
|
||||
result->may_be_out_of_upper_bound = MayBeOutOfUpperBound();
|
||||
}
|
||||
return is_valid;
|
||||
}
|
||||
@@ -1193,8 +1231,7 @@ Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
|
||||
new RandomAccessFileReader(
|
||||
std::move(file), file_name, nullptr /* env */, nullptr /* stats */,
|
||||
0 /* hist_type */, nullptr /* file_read_hist */,
|
||||
nullptr /* rate_limiter */, false /* for_compaction*/,
|
||||
ioptions->listeners));
|
||||
nullptr /* rate_limiter */, ioptions->listeners));
|
||||
s = ReadTableProperties(
|
||||
file_reader.get(), file_meta->fd.GetFileSize(),
|
||||
Footer::kInvalidTableMagicNumber /* table's magic number */, *ioptions,
|
||||
@@ -1441,10 +1478,14 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
|
||||
const auto& file = storage_info_.LevelFilesBrief(0).files[i];
|
||||
merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(), *file.file_metadata,
|
||||
range_del_agg, mutable_cf_options_.prefix_extractor.get(), nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0), false, arena,
|
||||
false /* skip_filters */, 0 /* level */));
|
||||
read_options, soptions, cfd_->internal_comparator(),
|
||||
*file.file_metadata, range_del_agg,
|
||||
mutable_cf_options_.prefix_extractor.get(), nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0),
|
||||
TableReaderCaller::kUserIterator, arena,
|
||||
/*skip_filters=*/false, /*level=*/0,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr));
|
||||
}
|
||||
if (should_sample) {
|
||||
// Count ones for every L0 files. This is done per iterator creation
|
||||
@@ -1465,8 +1506,8 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
mutable_cf_options_.prefix_extractor.get(), should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
false /* for_compaction */, IsFilterSkipped(level), level,
|
||||
range_del_agg));
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
range_del_agg, /*largest_compaction_key=*/nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1495,10 +1536,14 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
|
||||
continue;
|
||||
}
|
||||
ScopedArenaIterator iter(cfd_->table_cache()->NewIterator(
|
||||
read_options, env_options, cfd_->internal_comparator(), *file->file_metadata,
|
||||
&range_del_agg, mutable_cf_options_.prefix_extractor.get(), nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0), false, &arena,
|
||||
false /* skip_filters */, 0 /* level */));
|
||||
read_options, env_options, cfd_->internal_comparator(),
|
||||
*file->file_metadata, &range_del_agg,
|
||||
mutable_cf_options_.prefix_extractor.get(), nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0),
|
||||
TableReaderCaller::kUserIterator, &arena,
|
||||
/*skip_filters=*/false, /*level=*/0,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr));
|
||||
status = OverlapWithIterator(
|
||||
ucmp, smallest_user_key, largest_user_key, iter.get(), overlap);
|
||||
if (!status.ok() || *overlap) {
|
||||
@@ -1512,7 +1557,7 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
mutable_cf_options_.prefix_extractor.get(), should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
false /* for_compaction */, IsFilterSkipped(level), level,
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
&range_del_agg));
|
||||
status = OverlapWithIterator(
|
||||
ucmp, smallest_user_key, largest_user_key, iter.get(), overlap);
|
||||
@@ -1618,11 +1663,17 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
}
|
||||
|
||||
PinnedIteratorsManager pinned_iters_mgr;
|
||||
uint64_t tracing_get_id = BlockCacheTraceHelper::kReservedGetId;
|
||||
if (vset_ && vset_->block_cache_tracer_ &&
|
||||
vset_->block_cache_tracer_->is_tracing_enabled()) {
|
||||
tracing_get_id = vset_->block_cache_tracer_->NextGetId();
|
||||
}
|
||||
GetContext get_context(
|
||||
user_comparator(), merge_operator_, info_log_, db_statistics_,
|
||||
status->ok() ? GetContext::kNotFound : GetContext::kMerge, user_key,
|
||||
value, value_found, merge_context, max_covering_tombstone_seq, this->env_,
|
||||
seq, merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob);
|
||||
seq, merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob,
|
||||
tracing_get_id);
|
||||
|
||||
// Pin blocks that we read to hold merge operands
|
||||
if (merge_operator_) {
|
||||
@@ -1740,7 +1791,12 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
if (merge_operator_) {
|
||||
pinned_iters_mgr.StartPinning();
|
||||
}
|
||||
uint64_t tracing_mget_id = BlockCacheTraceHelper::kReservedGetId;
|
||||
|
||||
if (vset_ && vset_->block_cache_tracer_ &&
|
||||
vset_->block_cache_tracer_->is_tracing_enabled()) {
|
||||
tracing_mget_id = vset_->block_cache_tracer_->NextGetId();
|
||||
}
|
||||
// Even though we know the batch size won't be > MAX_BATCH_SIZE,
|
||||
// use autovector in order to avoid unnecessary construction of GetContext
|
||||
// objects, which is expensive
|
||||
@@ -1752,7 +1808,8 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
iter->s->ok() ? GetContext::kNotFound : GetContext::kMerge, iter->ukey,
|
||||
iter->value, nullptr, &(iter->merge_context),
|
||||
&iter->max_covering_tombstone_seq, this->env_, &iter->seq,
|
||||
merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob);
|
||||
merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob,
|
||||
tracing_mget_id);
|
||||
}
|
||||
int get_ctx_index = 0;
|
||||
for (auto iter = range->begin(); iter != range->end();
|
||||
@@ -4356,10 +4413,9 @@ Status VersionSet::Recover(
|
||||
", last_sequence is %" PRIu64 ", log_number is %" PRIu64
|
||||
",prev_log_number is %" PRIu64 ",max_column_family is %" PRIu32
|
||||
",min_log_number_to_keep is %" PRIu64 "\n",
|
||||
manifest_path.c_str(), manifest_file_number_,
|
||||
next_file_number_.load(), last_sequence_.load(), log_number,
|
||||
prev_log_number_, column_family_set_->GetMaxColumnFamily(),
|
||||
min_log_number_to_keep_2pc());
|
||||
manifest_path.c_str(), manifest_file_number_, next_file_number_.load(),
|
||||
last_sequence_.load(), log_number, prev_log_number_,
|
||||
column_family_set_->GetMaxColumnFamily(), min_log_number_to_keep_2pc());
|
||||
|
||||
for (auto cfd : *column_family_set_) {
|
||||
if (cfd->IsDropped()) {
|
||||
@@ -4822,7 +4878,7 @@ Status VersionSet::WriteSnapshot(log::Writer* log) {
|
||||
// maintain state of where they first appear in the files.
|
||||
uint64_t VersionSet::ApproximateSize(Version* v, const Slice& start,
|
||||
const Slice& end, int start_level,
|
||||
int end_level, bool for_compaction) {
|
||||
int end_level, TableReaderCaller caller) {
|
||||
// pre-condition
|
||||
assert(v->cfd_->internal_comparator().Compare(start, end) <= 0);
|
||||
|
||||
@@ -4843,7 +4899,7 @@ uint64_t VersionSet::ApproximateSize(Version* v, const Slice& start,
|
||||
|
||||
if (!level) {
|
||||
// level 0 data is sorted order, handle the use case explicitly
|
||||
size += ApproximateSizeLevel0(v, files_brief, start, end, for_compaction);
|
||||
size += ApproximateSizeLevel0(v, files_brief, start, end, caller);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4860,7 +4916,7 @@ uint64_t VersionSet::ApproximateSize(Version* v, const Slice& start,
|
||||
// inferred from the sorted order
|
||||
for (uint64_t i = idx_start; i < files_brief.num_files; i++) {
|
||||
uint64_t val;
|
||||
val = ApproximateSize(v, files_brief.files[i], end, for_compaction);
|
||||
val = ApproximateSize(v, files_brief.files[i], end, caller);
|
||||
if (!val) {
|
||||
// the files after this will not have the range
|
||||
break;
|
||||
@@ -4871,7 +4927,7 @@ uint64_t VersionSet::ApproximateSize(Version* v, const Slice& start,
|
||||
if (i == idx_start) {
|
||||
// subtract the bytes needed to be scanned to get to the starting
|
||||
// key
|
||||
val = ApproximateSize(v, files_brief.files[i], start, for_compaction);
|
||||
val = ApproximateSize(v, files_brief.files[i], start, caller);
|
||||
assert(size >= val);
|
||||
size -= val;
|
||||
}
|
||||
@@ -4885,15 +4941,15 @@ uint64_t VersionSet::ApproximateSizeLevel0(Version* v,
|
||||
const LevelFilesBrief& files_brief,
|
||||
const Slice& key_start,
|
||||
const Slice& key_end,
|
||||
bool for_compaction) {
|
||||
TableReaderCaller caller) {
|
||||
// level 0 files are not in sorted order, we need to iterate through
|
||||
// the list to compute the total bytes that require scanning
|
||||
uint64_t size = 0;
|
||||
for (size_t i = 0; i < files_brief.num_files; i++) {
|
||||
const uint64_t start =
|
||||
ApproximateSize(v, files_brief.files[i], key_start, for_compaction);
|
||||
ApproximateSize(v, files_brief.files[i], key_start, caller);
|
||||
const uint64_t end =
|
||||
ApproximateSize(v, files_brief.files[i], key_end, for_compaction);
|
||||
ApproximateSize(v, files_brief.files[i], key_end, caller);
|
||||
assert(end >= start);
|
||||
size += end - start;
|
||||
}
|
||||
@@ -4901,7 +4957,8 @@ uint64_t VersionSet::ApproximateSizeLevel0(Version* v,
|
||||
}
|
||||
|
||||
uint64_t VersionSet::ApproximateSize(Version* v, const FdWithKeyRange& f,
|
||||
const Slice& key, bool for_compaction) {
|
||||
const Slice& key,
|
||||
TableReaderCaller caller) {
|
||||
// pre-condition
|
||||
assert(v);
|
||||
|
||||
@@ -4919,9 +4976,13 @@ uint64_t VersionSet::ApproximateSize(Version* v, const FdWithKeyRange& f,
|
||||
InternalIterator* iter = v->cfd_->table_cache()->NewIterator(
|
||||
ReadOptions(), v->env_options_, v->cfd_->internal_comparator(),
|
||||
*f.file_metadata, nullptr /* range_del_agg */,
|
||||
v->GetMutableCFOptions().prefix_extractor.get(), &table_reader_ptr);
|
||||
v->GetMutableCFOptions().prefix_extractor.get(), &table_reader_ptr,
|
||||
/*file_read_hist=*/nullptr, caller,
|
||||
/*arena=*/nullptr, /*skip_filters=*/false, /*level=*/-1,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr);
|
||||
if (table_reader_ptr != nullptr) {
|
||||
result = table_reader_ptr->ApproximateOffsetOf(key, for_compaction);
|
||||
result = table_reader_ptr->ApproximateOffsetOf(key, caller);
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
@@ -5000,10 +5061,12 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
read_options, env_options_compactions, cfd->internal_comparator(),
|
||||
*flevel->files[i].file_metadata, range_del_agg,
|
||||
c->mutable_cf_options()->prefix_extractor.get(),
|
||||
nullptr /* table_reader_ptr */,
|
||||
nullptr /* no per level latency histogram */,
|
||||
true /* for_compaction */, nullptr /* arena */,
|
||||
false /* skip_filters */, static_cast<int>(which) /* level */);
|
||||
/*table_reader_ptr=*/nullptr,
|
||||
/*file_read_hist=*/nullptr, TableReaderCaller::kCompaction,
|
||||
/*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/static_cast<int>(which),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr);
|
||||
}
|
||||
} else {
|
||||
// Create concatenating iterator for the files from this level
|
||||
@@ -5011,10 +5074,10 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
cfd->table_cache(), read_options, env_options_compactions,
|
||||
cfd->internal_comparator(), c->input_levels(which),
|
||||
c->mutable_cf_options()->prefix_extractor.get(),
|
||||
false /* should_sample */,
|
||||
nullptr /* no per level latency histogram */,
|
||||
true /* for_compaction */, false /* skip_filters */,
|
||||
static_cast<int>(which) /* level */, range_del_agg,
|
||||
/*should_sample=*/false,
|
||||
/*no per level latency histogram=*/nullptr,
|
||||
TableReaderCaller::kCompaction, /*skip_filters=*/false,
|
||||
/*level=*/static_cast<int>(which), range_del_agg,
|
||||
c->boundaries(which));
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -984,7 +984,8 @@ class VersionSet {
|
||||
// in levels [start_level, end_level). If end_level == 0 it will search
|
||||
// through all non-empty levels
|
||||
uint64_t ApproximateSize(Version* v, const Slice& start, const Slice& end,
|
||||
int start_level, int end_level, bool for_compaction);
|
||||
int start_level, int end_level,
|
||||
TableReaderCaller caller);
|
||||
|
||||
// Return the size of the current manifest file
|
||||
uint64_t manifest_file_size() const { return manifest_file_size_; }
|
||||
@@ -1035,10 +1036,10 @@ class VersionSet {
|
||||
// ApproximateSize helper
|
||||
uint64_t ApproximateSizeLevel0(Version* v, const LevelFilesBrief& files_brief,
|
||||
const Slice& start, const Slice& end,
|
||||
bool for_compaction);
|
||||
TableReaderCaller caller);
|
||||
|
||||
uint64_t ApproximateSize(Version* v, const FdWithKeyRange& f,
|
||||
const Slice& key, bool for_compaction);
|
||||
const Slice& key, TableReaderCaller caller);
|
||||
|
||||
// Save current contents to *log
|
||||
Status WriteSnapshot(log::Writer* log);
|
||||
|
||||
@@ -293,6 +293,29 @@ TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) {
|
||||
// Check that an empty iterator is returned
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
TEST_F(WalManagerTest, TransactionLogIteratorNewFileWhileScanning) {
|
||||
Init();
|
||||
CreateArchiveLogs(2, 100);
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
CreateArchiveLogs(1, 100);
|
||||
int i = 0;
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
i++;
|
||||
}
|
||||
ASSERT_EQ(i, 200);
|
||||
// A new log file was added after the iterator was created.
|
||||
// TryAgain indicates a new iterator is needed to fetch the new data
|
||||
ASSERT_TRUE(iter->status().IsTryAgain());
|
||||
|
||||
iter = OpenTransactionLogIter(0);
|
||||
i = 0;
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
i++;
|
||||
}
|
||||
ASSERT_EQ(i, 300);
|
||||
ASSERT_TRUE(iter->status().ok());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
Vendored
+21
@@ -10,6 +10,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
|
||||
#include <thread>
|
||||
#include "logging/env_logger.h"
|
||||
#include "memory/arena.h"
|
||||
#include "options/db_options.h"
|
||||
#include "port/port.h"
|
||||
@@ -22,6 +23,11 @@ namespace rocksdb {
|
||||
Env::~Env() {
|
||||
}
|
||||
|
||||
Status Env::NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) {
|
||||
return NewEnvLogger(fname, this, result);
|
||||
}
|
||||
|
||||
std::string Env::PriorityToString(Env::Priority priority) {
|
||||
switch (priority) {
|
||||
case Env::Priority::BOTTOM:
|
||||
@@ -422,5 +428,20 @@ EnvOptions::EnvOptions() {
|
||||
AssignEnvOptions(this, options);
|
||||
}
|
||||
|
||||
Status NewEnvLogger(const std::string& fname, Env* env,
|
||||
std::shared_ptr<Logger>* result) {
|
||||
EnvOptions options;
|
||||
// TODO: Tune the buffer size.
|
||||
options.writable_file_max_buffer_size = 1024 * 1024;
|
||||
std::unique_ptr<WritableFile> writable_file;
|
||||
const auto status = env->NewWritableFile(fname, &writable_file, options);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
*result = std::make_shared<EnvLogger>(std::move(writable_file), fname,
|
||||
options, env);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
Vendored
+49
-38
@@ -195,23 +195,26 @@ class EncryptedWritableFile : public WritableFileWrapper {
|
||||
EncryptedWritableFile(WritableFile* f, BlockAccessCipherStream* s, size_t prefixLength)
|
||||
: WritableFileWrapper(f), file_(f), stream_(s), prefixLength_(prefixLength) { }
|
||||
|
||||
Status Append(const Slice& data) override {
|
||||
Status Append(const Slice& data) override {
|
||||
AlignedBuffer buf;
|
||||
Status status;
|
||||
Slice dataToAppend(data);
|
||||
Slice dataToAppend(data);
|
||||
if (data.size() > 0) {
|
||||
auto offset = file_->GetFileSize(); // size including prefix
|
||||
// Encrypt in cloned buffer
|
||||
buf.Alignment(GetRequiredBufferAlignment());
|
||||
buf.AllocateNewBuffer(data.size());
|
||||
// TODO (sagar0): Modify AlignedBuffer.Append to allow doing a memmove
|
||||
// so that the next two lines can be replaced with buf.Append().
|
||||
memmove(buf.BufferStart(), data.data(), data.size());
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), data.size());
|
||||
buf.Size(data.size());
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
dataToAppend = Slice(buf.BufferStart(), data.size());
|
||||
dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
status = file_->Append(dataToAppend);
|
||||
status = file_->Append(dataToAppend);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -221,18 +224,19 @@ class EncryptedWritableFile : public WritableFileWrapper {
|
||||
Status PositionedAppend(const Slice& data, uint64_t offset) override {
|
||||
AlignedBuffer buf;
|
||||
Status status;
|
||||
Slice dataToAppend(data);
|
||||
Slice dataToAppend(data);
|
||||
offset += prefixLength_;
|
||||
if (data.size() > 0) {
|
||||
// Encrypt in cloned buffer
|
||||
buf.Alignment(GetRequiredBufferAlignment());
|
||||
buf.AllocateNewBuffer(data.size());
|
||||
memmove(buf.BufferStart(), data.data(), data.size());
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), data.size());
|
||||
buf.Size(data.size());
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
dataToAppend = Slice(buf.BufferStart(), data.size());
|
||||
dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
status = file_->PositionedAppend(dataToAppend, offset);
|
||||
if (!status.ok()) {
|
||||
@@ -325,18 +329,19 @@ class EncryptedRandomRWFile : public RandomRWFile {
|
||||
Status Write(uint64_t offset, const Slice& data) override {
|
||||
AlignedBuffer buf;
|
||||
Status status;
|
||||
Slice dataToWrite(data);
|
||||
Slice dataToWrite(data);
|
||||
offset += prefixLength_;
|
||||
if (data.size() > 0) {
|
||||
// Encrypt in cloned buffer
|
||||
buf.Alignment(GetRequiredBufferAlignment());
|
||||
buf.AllocateNewBuffer(data.size());
|
||||
memmove(buf.BufferStart(), data.data(), data.size());
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), data.size());
|
||||
buf.Size(data.size());
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
dataToWrite = Slice(buf.BufferStart(), data.size());
|
||||
dataToWrite = Slice(buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
status = file_->Write(offset, dataToWrite);
|
||||
return status;
|
||||
@@ -393,13 +398,14 @@ class EncryptedEnv : public EnvWrapper {
|
||||
Slice prefixSlice;
|
||||
size_t prefixLength = provider_->GetPrefixLength();
|
||||
if (prefixLength > 0) {
|
||||
// Read prefix
|
||||
// Read prefix
|
||||
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
|
||||
prefixBuf.AllocateNewBuffer(prefixLength);
|
||||
status = underlying->Read(prefixLength, &prefixSlice, prefixBuf.BufferStart());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
prefixBuf.Size(prefixLength);
|
||||
}
|
||||
// Create cipher stream
|
||||
std::unique_ptr<BlockAccessCipherStream> stream;
|
||||
@@ -430,13 +436,14 @@ class EncryptedEnv : public EnvWrapper {
|
||||
Slice prefixSlice;
|
||||
size_t prefixLength = provider_->GetPrefixLength();
|
||||
if (prefixLength > 0) {
|
||||
// Read prefix
|
||||
// Read prefix
|
||||
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
|
||||
prefixBuf.AllocateNewBuffer(prefixLength);
|
||||
status = underlying->Read(0, prefixLength, &prefixSlice, prefixBuf.BufferStart());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
prefixBuf.Size(prefixLength);
|
||||
}
|
||||
// Create cipher stream
|
||||
std::unique_ptr<BlockAccessCipherStream> stream;
|
||||
@@ -467,12 +474,13 @@ class EncryptedEnv : public EnvWrapper {
|
||||
Slice prefixSlice;
|
||||
size_t prefixLength = provider_->GetPrefixLength();
|
||||
if (prefixLength > 0) {
|
||||
// Initialize prefix
|
||||
// Initialize prefix
|
||||
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
|
||||
prefixBuf.AllocateNewBuffer(prefixLength);
|
||||
provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
|
||||
prefixSlice = Slice(prefixBuf.BufferStart(), prefixLength);
|
||||
// Write prefix
|
||||
prefixBuf.Size(prefixLength);
|
||||
prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
|
||||
// Write prefix
|
||||
status = underlying->Append(prefixSlice);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -513,12 +521,13 @@ class EncryptedEnv : public EnvWrapper {
|
||||
Slice prefixSlice;
|
||||
size_t prefixLength = provider_->GetPrefixLength();
|
||||
if (prefixLength > 0) {
|
||||
// Initialize prefix
|
||||
// Initialize prefix
|
||||
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
|
||||
prefixBuf.AllocateNewBuffer(prefixLength);
|
||||
provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
|
||||
prefixSlice = Slice(prefixBuf.BufferStart(), prefixLength);
|
||||
// Write prefix
|
||||
prefixBuf.Size(prefixLength);
|
||||
prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
|
||||
// Write prefix
|
||||
status = underlying->Append(prefixSlice);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -554,12 +563,13 @@ class EncryptedEnv : public EnvWrapper {
|
||||
Slice prefixSlice;
|
||||
size_t prefixLength = provider_->GetPrefixLength();
|
||||
if (prefixLength > 0) {
|
||||
// Initialize prefix
|
||||
// Initialize prefix
|
||||
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
|
||||
prefixBuf.AllocateNewBuffer(prefixLength);
|
||||
provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
|
||||
prefixSlice = Slice(prefixBuf.BufferStart(), prefixLength);
|
||||
// Write prefix
|
||||
prefixBuf.Size(prefixLength);
|
||||
prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
|
||||
// Write prefix
|
||||
status = underlying->Append(prefixSlice);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -609,11 +619,13 @@ class EncryptedEnv : public EnvWrapper {
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
prefixBuf.Size(prefixLength);
|
||||
} else {
|
||||
// File is new, initialize & write prefix
|
||||
// File is new, initialize & write prefix
|
||||
provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
|
||||
prefixSlice = Slice(prefixBuf.BufferStart(), prefixLength);
|
||||
// Write prefix
|
||||
prefixBuf.Size(prefixLength);
|
||||
prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
|
||||
// Write prefix
|
||||
status = underlying->Write(0, prefixSlice);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -630,7 +642,7 @@ class EncryptedEnv : public EnvWrapper {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Store in *result the attributes of the children of the specified directory.
|
||||
// Store in *result the attributes of the children of the specified directory.
|
||||
// In case the implementation lists the directory prior to iterating the files
|
||||
// and files are concurrently deleted, the deleted files will be omitted from
|
||||
// result.
|
||||
@@ -670,8 +682,7 @@ class EncryptedEnv : public EnvWrapper {
|
||||
EncryptionProvider *provider_;
|
||||
};
|
||||
|
||||
|
||||
// Returns an Env that encrypts data when stored on disk and decrypts data when
|
||||
// Returns an Env that encrypts data when stored on disk and decrypts data when
|
||||
// read from disk.
|
||||
Env* NewEncryptedEnv(Env* base_env, EncryptionProvider* provider) {
|
||||
return new EncryptedEnv(base_env, provider);
|
||||
@@ -694,14 +705,14 @@ Status BlockAccessCipherStream::Encrypt(uint64_t fileOffset, char *data, size_t
|
||||
char *block = data;
|
||||
size_t n = std::min(dataSize, blockSize - blockOffset);
|
||||
if (n != blockSize) {
|
||||
// We're not encrypting a full block.
|
||||
// We're not encrypting a full block.
|
||||
// Copy data to blockBuffer
|
||||
if (!blockBuffer.get()) {
|
||||
// Allocate buffer
|
||||
blockBuffer = std::unique_ptr<char[]>(new char[blockSize]);
|
||||
}
|
||||
block = blockBuffer.get();
|
||||
// Copy plain data to block buffer
|
||||
// Copy plain data to block buffer
|
||||
memmove(block + blockOffset, data, n);
|
||||
}
|
||||
auto status = EncryptBlock(blockIndex, block, (char*)scratch.data());
|
||||
@@ -741,14 +752,14 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char *data, size_t
|
||||
char *block = data;
|
||||
size_t n = std::min(dataSize, blockSize - blockOffset);
|
||||
if (n != blockSize) {
|
||||
// We're not decrypting a full block.
|
||||
// We're not decrypting a full block.
|
||||
// Copy data to blockBuffer
|
||||
if (!blockBuffer.get()) {
|
||||
// Allocate buffer
|
||||
blockBuffer = std::unique_ptr<char[]>(new char[blockSize]);
|
||||
}
|
||||
block = blockBuffer.get();
|
||||
// Copy encrypted data to block buffer
|
||||
// Copy encrypted data to block buffer
|
||||
memmove(block + blockOffset, data, n);
|
||||
}
|
||||
auto status = DecryptBlock(blockIndex, block, (char*)scratch.data());
|
||||
@@ -807,7 +818,7 @@ Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char *data, char* scra
|
||||
memmove(scratch, iv_.data(), blockSize);
|
||||
EncodeFixed64(scratch, blockIndex + initialCounter_);
|
||||
|
||||
// Encrypt nonce+counter
|
||||
// Encrypt nonce+counter
|
||||
auto status = cipher_.Encrypt(scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -823,13 +834,13 @@ Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char *data, char* scra
|
||||
// Decrypt a block of data at the given block index.
|
||||
// Length of data is equal to BlockSize();
|
||||
Status CTRCipherStream::DecryptBlock(uint64_t blockIndex, char *data, char* scratch) {
|
||||
// For CTR decryption & encryption are the same
|
||||
// For CTR decryption & encryption are the same
|
||||
return EncryptBlock(blockIndex, data, scratch);
|
||||
}
|
||||
|
||||
// GetPrefixLength returns the length of the prefix that is added to every file
|
||||
// and used for storing encryption options.
|
||||
// For optimal performance, the prefix length should be a multiple of
|
||||
// For optimal performance, the prefix length should be a multiple of
|
||||
// the page size.
|
||||
size_t CTREncryptionProvider::GetPrefixLength() {
|
||||
return defaultPrefixLength;
|
||||
@@ -844,7 +855,7 @@ static void decodeCTRParameters(const char *prefix, size_t blockSize, uint64_t &
|
||||
iv = Slice(prefix + blockSize, blockSize);
|
||||
}
|
||||
|
||||
// CreateNewPrefix initialized an allocated block of prefix memory
|
||||
// CreateNewPrefix initialized an allocated block of prefix memory
|
||||
// for a new file.
|
||||
Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/,
|
||||
char* prefix,
|
||||
@@ -873,7 +884,7 @@ Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// PopulateSecretPrefixPart initializes the data into a new prefix block
|
||||
// PopulateSecretPrefixPart initializes the data into a new prefix block
|
||||
// in plain text.
|
||||
// Returns the amount of space (starting from the start of the prefix)
|
||||
// that has been initialized.
|
||||
@@ -908,7 +919,7 @@ Status CTREncryptionProvider::CreateCipherStream(
|
||||
return status;
|
||||
}
|
||||
|
||||
// Create cipher stream
|
||||
// Create cipher stream
|
||||
return CreateCipherStreamFromPrefix(fname, options, initialCounter, iv, prefix, result);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+6
-1
@@ -420,7 +420,7 @@ Status HdfsEnv::NewRandomAccessFile(const std::string& fname,
|
||||
// create a new file for writing
|
||||
Status HdfsEnv::NewWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& /*options*/) {
|
||||
const EnvOptions& options) {
|
||||
result->reset();
|
||||
Status s;
|
||||
HdfsWritableFile* f = new HdfsWritableFile(fileSys_, fname, options);
|
||||
@@ -590,6 +590,11 @@ Status HdfsEnv::UnlockFile(FileLock* /*lock*/) { return Status::OK(); }
|
||||
|
||||
Status HdfsEnv::NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) {
|
||||
// EnvOptions is used exclusively for its `strict_bytes_per_sync` value. That
|
||||
// option is only intended for WAL/flush/compaction writes, so turn it off in
|
||||
// the logger.
|
||||
EnvOptions options;
|
||||
options.strict_bytes_per_sync = false;
|
||||
HdfsWritableFile* f = new HdfsWritableFile(fileSys_, fname, options);
|
||||
if (f == nullptr || !f->isValid()) {
|
||||
delete f;
|
||||
|
||||
Vendored
+4
-3
@@ -887,13 +887,14 @@ class PosixEnv : public Env {
|
||||
FILE* f;
|
||||
{
|
||||
IOSTATS_TIMER_GUARD(open_nanos);
|
||||
f = fopen(fname.c_str(), "w"
|
||||
f = fopen(fname.c_str(),
|
||||
"w"
|
||||
#ifdef __GLIBC_PREREQ
|
||||
#if __GLIBC_PREREQ(2, 7)
|
||||
"e" // glibc extension to enable O_CLOEXEC
|
||||
"e" // glibc extension to enable O_CLOEXEC
|
||||
#endif
|
||||
#endif
|
||||
);
|
||||
);
|
||||
}
|
||||
if (f == nullptr) {
|
||||
result->reset();
|
||||
|
||||
Vendored
-1
@@ -27,7 +27,6 @@
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#endif
|
||||
#include "logging/posix_logger.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
@@ -110,6 +110,7 @@ Status DeleteDBFile(const ImmutableDBOptions* db_options,
|
||||
|
||||
bool IsWalDirSameAsDBPath(const ImmutableDBOptions* db_options) {
|
||||
bool same = false;
|
||||
assert(!db_options->db_paths.empty());
|
||||
Status s = db_options->env->AreFilesSame(db_options->wal_dir,
|
||||
db_options->db_paths[0].path, &same);
|
||||
if (s.IsNotSupported()) {
|
||||
|
||||
@@ -59,7 +59,7 @@ struct LRUCacheOptions {
|
||||
//
|
||||
// See also
|
||||
// BlockBasedTableOptions::cache_index_and_filter_blocks_with_high_priority.
|
||||
double high_pri_pool_ratio = 0.0;
|
||||
double high_pri_pool_ratio = 0.5;
|
||||
|
||||
// If non-nullptr will use this allocator instead of system allocator when
|
||||
// allocating memory for cache blocks. Call this method before you start using
|
||||
@@ -99,7 +99,7 @@ struct LRUCacheOptions {
|
||||
// will be at least 512KB and number of shard bits will not exceed 6.
|
||||
extern std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits = -1,
|
||||
bool strict_capacity_limit = false, double high_pri_pool_ratio = 0.0,
|
||||
bool strict_capacity_limit = false, double high_pri_pool_ratio = 0.5,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex);
|
||||
|
||||
|
||||
@@ -1174,6 +1174,27 @@ class DB {
|
||||
virtual Status IngestExternalFiles(
|
||||
const std::vector<IngestExternalFileArg>& args) = 0;
|
||||
|
||||
// CreateColumnFamilyWithImport() will create a new column family with
|
||||
// column_family_name and import external SST files specified in metadata into
|
||||
// this column family.
|
||||
// (1) External SST files can be created using SstFileWriter.
|
||||
// (2) External SST files can be exported from a particular column family in
|
||||
// an existing DB.
|
||||
// Option in import_options specifies whether the external files are copied or
|
||||
// moved (default is copy). When option specifies copy, managing files at
|
||||
// external_file_path is caller's responsibility. When option specifies a
|
||||
// move, the call ensures that the specified files at external_file_path are
|
||||
// deleted on successful return and files are not modified on any error
|
||||
// return.
|
||||
// On error return, column family handle returned will be nullptr.
|
||||
// ColumnFamily will be present on successful return and will not be present
|
||||
// on error return. ColumnFamily may be present on any crash during this call.
|
||||
virtual Status CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& options, const std::string& column_family_name,
|
||||
const ImportColumnFamilyOptions& import_options,
|
||||
const ExportImportFilesMetaData& metadata,
|
||||
ColumnFamilyHandle** handle) = 0;
|
||||
|
||||
virtual Status VerifyChecksum() = 0;
|
||||
|
||||
// AddFile() is deprecated, please use IngestExternalFile()
|
||||
|
||||
+10
-2
@@ -395,9 +395,11 @@ class Env {
|
||||
// same directory.
|
||||
virtual Status GetTestDirectory(std::string* path) = 0;
|
||||
|
||||
// Create and return a log file for storing informational messages.
|
||||
// Create and returns a default logger (an instance of EnvLogger) for storing
|
||||
// informational messages. Derived classes can overide to provide custom
|
||||
// logger.
|
||||
virtual Status NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) = 0;
|
||||
std::shared_ptr<Logger>* result);
|
||||
|
||||
// Returns the number of micro-seconds since some fixed point in time.
|
||||
// It is often used as system time such as in GenericRateLimiter
|
||||
@@ -1563,4 +1565,10 @@ Status NewHdfsEnv(Env** hdfs_env, const std::string& fsname);
|
||||
// This is a factory method for TimedEnv defined in utilities/env_timed.cc.
|
||||
Env* NewTimedEnv(Env* base_env);
|
||||
|
||||
// Returns an instance of logger that can be used for storing informational
|
||||
// messages.
|
||||
// This is a factory method for EnvLogger declared in logging/env_logging.h
|
||||
Status NewEnvLogger(const std::string& fname, Env* env,
|
||||
std::shared_ptr<Logger>* result);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -108,4 +108,11 @@ struct LiveFileMetaData : SstFileMetaData {
|
||||
int level; // Level at which this file resides.
|
||||
LiveFileMetaData() : column_family_name(), level(0) {}
|
||||
};
|
||||
|
||||
// Metadata returned as output from ExportColumnFamily() and used as input to
|
||||
// CreateColumnFamiliesWithImport().
|
||||
struct ExportImportFilesMetaData {
|
||||
std::string db_comparator_name; // Used to safety check at import.
|
||||
std::vector<LiveFileMetaData> files; // Vector of file metadata.
|
||||
};
|
||||
} // namespace rocksdb
|
||||
@@ -275,10 +275,10 @@ struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions {
|
||||
// this option helps reducing the cpu usage of long-running compactions. The
|
||||
// feature is disabled when max_subcompactions is greater than one.
|
||||
//
|
||||
// Default: 0
|
||||
// Default: 0.1s
|
||||
//
|
||||
// Dynamically changeable through SetOptions() API
|
||||
uint64_t snap_refresh_nanos = 0;
|
||||
uint64_t snap_refresh_nanos = 100 * 1000 * 1000; // 0.1s
|
||||
|
||||
// Disable automatic compactions. Manual compactions can still
|
||||
// be issued on this column family
|
||||
@@ -760,6 +760,8 @@ struct DBOptions {
|
||||
// for this mode if using block-based table.
|
||||
//
|
||||
// Default: false
|
||||
// This flag has no affect on the behavior of compaction and plan to delete
|
||||
// in the future.
|
||||
bool new_table_reader_for_compaction_inputs = false;
|
||||
|
||||
// If non-zero, we perform bigger reads when doing compaction. If you're
|
||||
@@ -1489,4 +1491,10 @@ struct TraceOptions {
|
||||
uint64_t filter = kTraceFilterNone;
|
||||
};
|
||||
|
||||
// ImportColumnFamilyOptions is used by ImportColumnFamily()
|
||||
struct ImportColumnFamilyOptions {
|
||||
// Can be set to true to move the files instead of copying them.
|
||||
bool move_files = false;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+22
-4
@@ -74,7 +74,7 @@ struct BlockBasedTableOptions {
|
||||
// blocks with high priority. If set to true, depending on implementation of
|
||||
// block cache, index and filter blocks may be less likely to be evicted
|
||||
// than data blocks.
|
||||
bool cache_index_and_filter_blocks_with_high_priority = false;
|
||||
bool cache_index_and_filter_blocks_with_high_priority = true;
|
||||
|
||||
// if cache_index_and_filter_blocks is true and the below is true, then
|
||||
// filter and index blocks are stored in the cache, but a reference is
|
||||
@@ -93,14 +93,32 @@ struct BlockBasedTableOptions {
|
||||
enum IndexType : char {
|
||||
// A space efficient index block that is optimized for
|
||||
// binary-search-based index.
|
||||
kBinarySearch,
|
||||
kBinarySearch = 0x00,
|
||||
|
||||
// The hash index, if enabled, will do the hash lookup when
|
||||
// `Options.prefix_extractor` is provided.
|
||||
kHashSearch,
|
||||
kHashSearch = 0x01,
|
||||
|
||||
// A two-level index implementation. Both levels are binary search indexes.
|
||||
kTwoLevelIndexSearch,
|
||||
kTwoLevelIndexSearch = 0x02,
|
||||
|
||||
// Like kBinarySearch, but index also contains first key of each block.
|
||||
// This allows iterators to defer reading the block until it's actually
|
||||
// needed. May significantly reduce read amplification of short range scans.
|
||||
// Without it, iterator seek usually reads one block from each level-0 file
|
||||
// and from each level, which may be expensive.
|
||||
// Works best in combination with:
|
||||
// - IndexShorteningMode::kNoShortening,
|
||||
// - custom FlushBlockPolicy to cut blocks at some meaningful boundaries,
|
||||
// e.g. when prefix changes.
|
||||
// Makes the index significantly bigger (2x or more), especially when keys
|
||||
// are long.
|
||||
//
|
||||
// IO errors are not handled correctly in this mode right now: if an error
|
||||
// happens when lazily reading a block in value(), value() returns empty
|
||||
// slice, and you need to call Valid()/status() afterwards.
|
||||
// TODO(kolmike): Fix it.
|
||||
kBinarySearchWithFirstKey = 0x03,
|
||||
};
|
||||
|
||||
IndexType index_type = kBinarySearch;
|
||||
|
||||
@@ -9,11 +9,15 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DB;
|
||||
class ColumnFamilyHandle;
|
||||
struct LiveFileMetaData;
|
||||
struct ExportImportFilesMetaData;
|
||||
|
||||
class Checkpoint {
|
||||
public:
|
||||
@@ -36,6 +40,16 @@ class Checkpoint {
|
||||
virtual Status CreateCheckpoint(const std::string& checkpoint_dir,
|
||||
uint64_t log_size_for_flush = 0);
|
||||
|
||||
// Exports all live SST files of a specified Column Family onto export_dir,
|
||||
// returning SST files information in metadata.
|
||||
// - SST files will be created as hard links when the directory specified
|
||||
// is in the same partition as the db directory, copied otherwise.
|
||||
// - export_dir should not already exist and will be created by this API.
|
||||
// - Always triggers a flush.
|
||||
virtual Status ExportColumnFamily(ColumnFamilyHandle* handle,
|
||||
const std::string& export_dir,
|
||||
ExportImportFilesMetaData** metadata);
|
||||
|
||||
virtual ~Checkpoint() {}
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@ Status GetAllKeyVersions(DB* db, Slice begin_key, Slice end_key,
|
||||
size_t max_num_ikeys,
|
||||
std::vector<KeyVersion>* key_versions);
|
||||
|
||||
Status GetAllKeyVersions(DB* db, ColumnFamilyHandle* cfh, Slice begin_key,
|
||||
Slice end_key, size_t max_num_ikeys,
|
||||
std::vector<KeyVersion>* key_versions);
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -31,6 +31,7 @@ class LDBCommand {
|
||||
// Command-line arguments
|
||||
static const std::string ARG_DB;
|
||||
static const std::string ARG_PATH;
|
||||
static const std::string ARG_SECONDARY_PATH;
|
||||
static const std::string ARG_HEX;
|
||||
static const std::string ARG_KEY_HEX;
|
||||
static const std::string ARG_VALUE_HEX;
|
||||
@@ -128,6 +129,10 @@ class LDBCommand {
|
||||
protected:
|
||||
LDBCommandExecuteResult exec_state_;
|
||||
std::string db_path_;
|
||||
// If empty, open DB as primary. If non-empty, open the DB as secondary
|
||||
// with this secondary path. When running against a database opened by
|
||||
// another process, ldb wll leave the source directory completely intact.
|
||||
std::string secondary_path_;
|
||||
std::string column_family_name_;
|
||||
DB* db_;
|
||||
DBWithTTL* db_ttl_;
|
||||
|
||||
@@ -120,6 +120,16 @@ class StackableDB : public DB {
|
||||
return db_->IngestExternalFiles(args);
|
||||
}
|
||||
|
||||
using DB::CreateColumnFamilyWithImport;
|
||||
virtual Status CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& options, const std::string& column_family_name,
|
||||
const ImportColumnFamilyOptions& import_options,
|
||||
const ExportImportFilesMetaData& metadata,
|
||||
ColumnFamilyHandle** handle) override {
|
||||
return db_->CreateColumnFamilyWithImport(options, column_family_name,
|
||||
import_options, metadata, handle);
|
||||
}
|
||||
|
||||
virtual Status VerifyChecksum() override { return db_->VerifyChecksum(); }
|
||||
|
||||
using DB::KeyMayExist;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 6
|
||||
#define ROCKSDB_MINOR 3
|
||||
#define ROCKSDB_PATCH 6
|
||||
#define ROCKSDB_MINOR 2
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
@@ -12,17 +12,17 @@ cd /rocksdb-local
|
||||
if hash scl 2>/dev/null; then
|
||||
if scl --list | grep -q 'devtoolset-7'; then
|
||||
scl enable devtoolset-7 'make jclean clean'
|
||||
scl enable devtoolset-7 'PORTABLE=1 make -j2 rocksdbjavastatic'
|
||||
scl enable devtoolset-7 'PORTABLE=1 make -j6 rocksdbjavastatic'
|
||||
elif scl --list | grep -q 'devtoolset-2'; then
|
||||
scl enable devtoolset-2 'make jclean clean'
|
||||
scl enable devtoolset-2 'PORTABLE=1 make -j2 rocksdbjavastatic'
|
||||
scl enable devtoolset-2 'PORTABLE=1 make -j6 rocksdbjavastatic'
|
||||
else
|
||||
echo "Could not find devtoolset"
|
||||
exit 1;
|
||||
fi
|
||||
else
|
||||
make jclean clean
|
||||
PORTABLE=1 make -j2 rocksdbjavastatic
|
||||
PORTABLE=1 make -j6 rocksdbjavastatic
|
||||
fi
|
||||
|
||||
cp java/target/librocksdbjni-linux*.so java/target/rocksdbjni-*-linux*.jar /rocksdb-host/java/target
|
||||
|
||||
@@ -5902,8 +5902,10 @@ class IndexTypeJni {
|
||||
return 0x0;
|
||||
case rocksdb::BlockBasedTableOptions::IndexType::kHashSearch:
|
||||
return 0x1;
|
||||
case rocksdb::BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch:
|
||||
case rocksdb::BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch:
|
||||
return 0x2;
|
||||
case rocksdb::BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey:
|
||||
return 0x3;
|
||||
default:
|
||||
return 0x7F; // undefined
|
||||
}
|
||||
@@ -5920,6 +5922,9 @@ class IndexTypeJni {
|
||||
return rocksdb::BlockBasedTableOptions::IndexType::kHashSearch;
|
||||
case 0x2:
|
||||
return rocksdb::BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
case 0x3:
|
||||
return rocksdb::BlockBasedTableOptions::IndexType::
|
||||
kBinarySearchWithFirstKey;
|
||||
default:
|
||||
// undefined/default
|
||||
return rocksdb::BlockBasedTableOptions::IndexType::kBinarySearch;
|
||||
|
||||
@@ -85,7 +85,7 @@ jlong Java_org_rocksdb_BlockBasedTableConfig_newTableFactoryHandle(
|
||||
std::shared_ptr<rocksdb::Cache> *pCache =
|
||||
reinterpret_cast<std::shared_ptr<rocksdb::Cache> *>(jblock_cache_handle);
|
||||
options.block_cache = *pCache;
|
||||
} else if (jblock_cache_size > 0) {
|
||||
} else if (jblock_cache_size >= 0) {
|
||||
if (jblock_cache_num_shard_bits > 0) {
|
||||
options.block_cache = rocksdb::NewLRUCache(
|
||||
static_cast<size_t>(jblock_cache_size),
|
||||
@@ -94,6 +94,9 @@ jlong Java_org_rocksdb_BlockBasedTableConfig_newTableFactoryHandle(
|
||||
options.block_cache = rocksdb::NewLRUCache(
|
||||
static_cast<size_t>(jblock_cache_size));
|
||||
}
|
||||
} else {
|
||||
options.no_block_cache = true;
|
||||
options.block_cache = nullptr;
|
||||
}
|
||||
}
|
||||
if (jpersistent_cache_handle > 0) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.List;
|
||||
* Taken from include/rocksdb/advanced_options.h
|
||||
*/
|
||||
public interface AdvancedColumnFamilyOptionsInterface
|
||||
<T extends AdvancedColumnFamilyOptionsInterface> {
|
||||
<T extends AdvancedColumnFamilyOptionsInterface<T>> {
|
||||
|
||||
/**
|
||||
* The minimum number of write buffers that will be merged together
|
||||
|
||||
@@ -12,7 +12,7 @@ package org.rocksdb;
|
||||
* and MutableCFOptions in util/cf_options.h
|
||||
*/
|
||||
public interface AdvancedMutableColumnFamilyOptionsInterface
|
||||
<T extends AdvancedMutableColumnFamilyOptionsInterface> {
|
||||
<T extends AdvancedMutableColumnFamilyOptionsInterface<T>> {
|
||||
|
||||
/**
|
||||
* The maximum number of write buffers that are built up in memory.
|
||||
|
||||
@@ -725,7 +725,7 @@ public class BlockBasedTableConfig extends TableFormatConfig {
|
||||
|
||||
/**
|
||||
* Set the size of the cache in bytes that will be used by RocksDB.
|
||||
* If cacheSize is non-positive, then cache will not be used.
|
||||
* If cacheSize is negative, then cache will not be used.
|
||||
* DEFAULT: 8M
|
||||
*
|
||||
* @param blockCacheSize block cache size in bytes
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
package org.rocksdb;
|
||||
|
||||
public interface ColumnFamilyOptionsInterface
|
||||
<T extends ColumnFamilyOptionsInterface>
|
||||
<T extends ColumnFamilyOptionsInterface<T>>
|
||||
extends AdvancedColumnFamilyOptionsInterface<T> {
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ package org.rocksdb;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface DBOptionsInterface<T extends DBOptionsInterface> {
|
||||
public interface DBOptionsInterface<T extends DBOptionsInterface<T>> {
|
||||
|
||||
/**
|
||||
* Use this if your DB is very small (like under 1GB) and you don't want to
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
package org.rocksdb;
|
||||
|
||||
public interface MutableColumnFamilyOptionsInterface
|
||||
<T extends MutableColumnFamilyOptionsInterface>
|
||||
<T extends MutableColumnFamilyOptionsInterface<T>>
|
||||
extends AdvancedMutableColumnFamilyOptionsInterface<T> {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
package org.rocksdb;
|
||||
|
||||
public interface MutableDBOptionsInterface<T extends MutableDBOptionsInterface> {
|
||||
public interface MutableDBOptionsInterface<T extends MutableDBOptionsInterface<T>> {
|
||||
|
||||
/**
|
||||
* Specifies the maximum number of concurrent background jobs (both flushes
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
* during the creation of a {@link org.rocksdb.RocksDB} (i.e., RocksDB.open()).
|
||||
*
|
||||
* If {@link #dispose()} function is not called, then it will be GC'd
|
||||
* automaticallyand native resources will be released as part of the process.
|
||||
* automatically and native resources will be released as part of the process.
|
||||
*/
|
||||
public class Options extends RocksObject
|
||||
implements DBOptionsInterface<Options>,
|
||||
|
||||
@@ -46,8 +46,9 @@ AutoRollLogger::AutoRollLogger(Env* env, const std::string& dbname,
|
||||
}
|
||||
GetExistingFiles();
|
||||
ResetLogger();
|
||||
if (status_.ok()) {
|
||||
status_ = TrimOldLogFiles();
|
||||
s = TrimOldLogFiles();
|
||||
if (!status_.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace {
|
||||
@@ -444,7 +445,7 @@ TEST_F(AutoRollLoggerTest, LogFlushWhileRolling) {
|
||||
{"AutoRollLogger::Flush:PinnedLogger", "PosixLogger::Flush:Begin2"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
flush_thread = port::Thread ([&]() { auto_roll_logger->Flush(); });
|
||||
flush_thread = port::Thread([&]() { auto_roll_logger->Flush(); });
|
||||
TEST_SYNC_POINT(
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit");
|
||||
RollLogFileBySizeTest(auto_roll_logger, options.max_log_file_size,
|
||||
@@ -557,25 +558,6 @@ static std::vector<std::string> GetOldFileNames(const std::string& path) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Return the number of lines where a given pattern was found in the file
|
||||
static size_t GetLinesCount(const std::string& fname,
|
||||
const std::string& pattern) {
|
||||
std::stringstream ssbuf;
|
||||
std::string line;
|
||||
size_t count = 0;
|
||||
|
||||
std::ifstream inFile(fname.c_str());
|
||||
ssbuf << inFile.rdbuf();
|
||||
|
||||
while (getline(ssbuf, line)) {
|
||||
if (line.find(pattern) != std::string::npos) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
TEST_F(AutoRollLoggerTest, LogHeaderTest) {
|
||||
static const size_t MAX_HEADERS = 10;
|
||||
static const size_t LOG_MAX_SIZE = 1024 * 5;
|
||||
@@ -627,7 +609,7 @@ TEST_F(AutoRollLoggerTest, LogHeaderTest) {
|
||||
// verify that the files rolled over
|
||||
ASSERT_NE(oldfname, newfname);
|
||||
// verify that the old log contains all the header logs
|
||||
ASSERT_EQ(GetLinesCount(oldfname, HEADER_STR), MAX_HEADERS);
|
||||
ASSERT_EQ(test::GetLinesCount(oldfname, HEADER_STR), MAX_HEADERS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -653,15 +635,6 @@ TEST_F(AutoRollLoggerTest, LogFileExistence) {
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(AutoRollLoggerTest, FileCreateFailure) {
|
||||
Options options;
|
||||
options.max_log_file_size = 100 * 1024 * 1024;
|
||||
options.db_log_dir = "/a/dir/does/not/exist/at/all";
|
||||
|
||||
std::shared_ptr<Logger> logger;
|
||||
ASSERT_NOK(CreateLoggerFromOptions("", options, &logger));
|
||||
ASSERT_TRUE(!logger);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// 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.
|
||||
//
|
||||
// Logger implementation that uses custom Env object for logging.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include "port/sys_time.h"
|
||||
#include <time.h>
|
||||
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class EnvLogger : public Logger {
|
||||
public:
|
||||
EnvLogger(std::unique_ptr<WritableFile>&& writable_file,
|
||||
const std::string& fname, const EnvOptions& options, Env* env,
|
||||
InfoLogLevel log_level = InfoLogLevel::ERROR_LEVEL)
|
||||
: Logger(log_level),
|
||||
file_(std::move(writable_file), fname, options, env),
|
||||
last_flush_micros_(0),
|
||||
env_(env),
|
||||
flush_pending_(false) {}
|
||||
|
||||
~EnvLogger() {
|
||||
if (!closed_) {
|
||||
closed_ = true;
|
||||
CloseHelper();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void FlushLocked() {
|
||||
mutex_.AssertHeld();
|
||||
if (flush_pending_) {
|
||||
flush_pending_ = false;
|
||||
file_.Flush();
|
||||
}
|
||||
last_flush_micros_ = env_->NowMicros();
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
TEST_SYNC_POINT("EnvLogger::Flush:Begin1");
|
||||
TEST_SYNC_POINT("EnvLogger::Flush:Begin2");
|
||||
|
||||
MutexLock l(&mutex_);
|
||||
FlushLocked();
|
||||
}
|
||||
|
||||
Status CloseImpl() override { return CloseHelper(); }
|
||||
|
||||
Status CloseHelper() {
|
||||
mutex_.Lock();
|
||||
const auto close_status = file_.Close();
|
||||
mutex_.Unlock();
|
||||
|
||||
if (close_status.ok()) {
|
||||
return close_status;
|
||||
}
|
||||
return Status::IOError("Close of log file failed with error:" +
|
||||
(close_status.getState()
|
||||
? std::string(close_status.getState())
|
||||
: std::string()));
|
||||
}
|
||||
|
||||
using Logger::Logv;
|
||||
void Logv(const char* format, va_list ap) override {
|
||||
IOSTATS_TIMER_GUARD(logger_nanos);
|
||||
|
||||
const uint64_t thread_id = env_->GetThreadID();
|
||||
|
||||
// We try twice: the first time with a fixed-size stack allocated buffer,
|
||||
// and the second time with a much larger dynamically allocated buffer.
|
||||
char buffer[500];
|
||||
for (int iter = 0; iter < 2; iter++) {
|
||||
char* base;
|
||||
int bufsize;
|
||||
if (iter == 0) {
|
||||
bufsize = sizeof(buffer);
|
||||
base = buffer;
|
||||
} else {
|
||||
bufsize = 65536;
|
||||
base = new char[bufsize];
|
||||
}
|
||||
char* p = base;
|
||||
char* limit = base + bufsize;
|
||||
|
||||
struct timeval now_tv;
|
||||
gettimeofday(&now_tv, nullptr);
|
||||
const time_t seconds = now_tv.tv_sec;
|
||||
struct tm t;
|
||||
localtime_r(&seconds, &t);
|
||||
p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %llx ",
|
||||
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
|
||||
t.tm_min, t.tm_sec, static_cast<int>(now_tv.tv_usec),
|
||||
static_cast<long long unsigned int>(thread_id));
|
||||
|
||||
// Print the message
|
||||
if (p < limit) {
|
||||
va_list backup_ap;
|
||||
va_copy(backup_ap, ap);
|
||||
p += vsnprintf(p, limit - p, format, backup_ap);
|
||||
va_end(backup_ap);
|
||||
}
|
||||
|
||||
// Truncate to available space if necessary
|
||||
if (p >= limit) {
|
||||
if (iter == 0) {
|
||||
continue; // Try again with larger buffer
|
||||
} else {
|
||||
p = limit - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Add newline if necessary
|
||||
if (p == base || p[-1] != '\n') {
|
||||
*p++ = '\n';
|
||||
}
|
||||
|
||||
assert(p <= limit);
|
||||
mutex_.Lock();
|
||||
// We will ignore any error returned by Append().
|
||||
file_.Append(Slice(base, p - base));
|
||||
flush_pending_ = true;
|
||||
const uint64_t now_micros = env_->NowMicros();
|
||||
if (now_micros - last_flush_micros_ >= flush_every_seconds_ * 1000000) {
|
||||
FlushLocked();
|
||||
}
|
||||
mutex_.Unlock();
|
||||
if (base != buffer) {
|
||||
delete[] base;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t GetLogFileSize() const override {
|
||||
MutexLock l(&mutex_);
|
||||
return file_.GetFileSize();
|
||||
}
|
||||
|
||||
private:
|
||||
WritableFileWriter file_;
|
||||
mutable port::Mutex mutex_; // Mutex to protect the shared variables below.
|
||||
const static uint64_t flush_every_seconds_ = 5;
|
||||
std::atomic_uint_fast64_t last_flush_micros_;
|
||||
Env* env_;
|
||||
std::atomic<bool> flush_pending_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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 "env/mock_env.h"
|
||||
#include "logging/env_logger.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
// In this test we only want to Log some simple log message with
|
||||
// no format.
|
||||
void LogMessage(std::shared_ptr<Logger> logger, const std::string& message) {
|
||||
Log(logger, "%s", message.c_str());
|
||||
}
|
||||
|
||||
// Helper method to write the message num_times in the given logger.
|
||||
void WriteLogs(std::shared_ptr<Logger> logger, const std::string& message,
|
||||
int num_times) {
|
||||
for (int ii = 0; ii < num_times; ++ii) {
|
||||
LogMessage(logger, message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class EnvLoggerTest : public testing::Test {
|
||||
public:
|
||||
Env* env_;
|
||||
|
||||
EnvLoggerTest() : env_(Env::Default()) {}
|
||||
|
||||
~EnvLoggerTest() = default;
|
||||
|
||||
std::shared_ptr<Logger> CreateLogger() {
|
||||
std::shared_ptr<Logger> result;
|
||||
assert(NewEnvLogger(kLogFile, env_, &result).ok());
|
||||
assert(result);
|
||||
result->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL);
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteLogFile() {
|
||||
ASSERT_OK(env_->DeleteFile(kLogFile));
|
||||
}
|
||||
|
||||
static const std::string kSampleMessage;
|
||||
static const std::string kTestDir;
|
||||
static const std::string kLogFile;
|
||||
};
|
||||
|
||||
const std::string EnvLoggerTest::kSampleMessage =
|
||||
"this is the message to be written to the log file!!";
|
||||
const std::string EnvLoggerTest::kLogFile = test::PerThreadDBPath("log_file");
|
||||
|
||||
TEST_F(EnvLoggerTest, EmptyLogFile) {
|
||||
auto logger = CreateLogger();
|
||||
ASSERT_EQ(logger->Close(), Status::OK());
|
||||
|
||||
// Check the size of the log file.
|
||||
uint64_t file_size;
|
||||
ASSERT_EQ(env_->GetFileSize(kLogFile, &file_size), Status::OK());
|
||||
ASSERT_EQ(file_size, 0);
|
||||
DeleteLogFile();
|
||||
}
|
||||
|
||||
TEST_F(EnvLoggerTest, LogMultipleLines) {
|
||||
auto logger = CreateLogger();
|
||||
|
||||
// Write multiple lines.
|
||||
const int kNumIter = 10;
|
||||
WriteLogs(logger, kSampleMessage, kNumIter);
|
||||
|
||||
// Flush the logs.
|
||||
logger->Flush();
|
||||
ASSERT_EQ(logger->Close(), Status::OK());
|
||||
|
||||
// Validate whether the log file has 'kNumIter' number of lines.
|
||||
ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), kNumIter);
|
||||
DeleteLogFile();
|
||||
}
|
||||
|
||||
TEST_F(EnvLoggerTest, Overwrite) {
|
||||
{
|
||||
auto logger = CreateLogger();
|
||||
|
||||
// Write multiple lines.
|
||||
const int kNumIter = 10;
|
||||
WriteLogs(logger, kSampleMessage, kNumIter);
|
||||
|
||||
ASSERT_EQ(logger->Close(), Status::OK());
|
||||
|
||||
// Validate whether the log file has 'kNumIter' number of lines.
|
||||
ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), kNumIter);
|
||||
}
|
||||
|
||||
// Now reopen the file again.
|
||||
{
|
||||
auto logger = CreateLogger();
|
||||
|
||||
// File should be empty.
|
||||
uint64_t file_size;
|
||||
ASSERT_EQ(env_->GetFileSize(kLogFile, &file_size), Status::OK());
|
||||
ASSERT_EQ(file_size, 0);
|
||||
ASSERT_EQ(logger->GetLogFileSize(), 0);
|
||||
ASSERT_EQ(logger->Close(), Status::OK());
|
||||
}
|
||||
DeleteLogFile();
|
||||
}
|
||||
|
||||
TEST_F(EnvLoggerTest, Close) {
|
||||
auto logger = CreateLogger();
|
||||
|
||||
// Write multiple lines.
|
||||
const int kNumIter = 10;
|
||||
WriteLogs(logger, kSampleMessage, kNumIter);
|
||||
|
||||
ASSERT_EQ(logger->Close(), Status::OK());
|
||||
|
||||
// Validate whether the log file has 'kNumIter' number of lines.
|
||||
ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), kNumIter);
|
||||
DeleteLogFile();
|
||||
}
|
||||
|
||||
TEST_F(EnvLoggerTest, ConcurrentLogging) {
|
||||
auto logger = CreateLogger();
|
||||
|
||||
const int kNumIter = 20;
|
||||
std::function<void()> cb = [&]() {
|
||||
WriteLogs(logger, kSampleMessage, kNumIter);
|
||||
logger->Flush();
|
||||
};
|
||||
|
||||
// Write to the logs from multiple threads.
|
||||
std::vector<port::Thread> threads;
|
||||
const int kNumThreads = 5;
|
||||
// Create threads.
|
||||
for (int ii = 0; ii < kNumThreads; ++ii) {
|
||||
threads.push_back(port::Thread(cb));
|
||||
}
|
||||
|
||||
// Wait for them to complete.
|
||||
for (auto& th : threads) {
|
||||
th.join();
|
||||
}
|
||||
|
||||
ASSERT_EQ(logger->Close(), Status::OK());
|
||||
|
||||
// Verfiy the log file.
|
||||
ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage),
|
||||
kNumIter * kNumThreads);
|
||||
DeleteLogFile();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -561,14 +561,89 @@ TEST_F(StatsHistoryTest, PersistentStatsReadOnly) {
|
||||
Close();
|
||||
|
||||
// Reopen and flush memtable.
|
||||
Reopen(options);
|
||||
ASSERT_OK(TryReopen(options));
|
||||
Flush();
|
||||
Close();
|
||||
// Now check keys in read only mode.
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(StatsHistoryTest, ForceManualFlushStatsCF) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.write_buffer_size = 1024 * 1024 * 10; // 10 Mb
|
||||
options.stats_persist_period_sec = 5;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.persist_stats_to_disk = true;
|
||||
std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
|
||||
mock_env.reset(new rocksdb::MockTimeEnv(env_));
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
CreateColumnFamilies({"pikachu"}, options);
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ColumnFamilyData* cfd_default =
|
||||
static_cast<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily())
|
||||
->cfd();
|
||||
ColumnFamilyData* cfd_stats = static_cast<ColumnFamilyHandleImpl*>(
|
||||
dbfull()->PersistentStatsColumnFamily())
|
||||
->cfd();
|
||||
ColumnFamilyData* cfd_test =
|
||||
static_cast<ColumnFamilyHandleImpl*>(handles_[1])->cfd();
|
||||
|
||||
ASSERT_OK(Put("foo", "v0"));
|
||||
ASSERT_OK(Put("bar", "v0"));
|
||||
ASSERT_EQ("v0", Get("bar"));
|
||||
ASSERT_EQ("v0", Get("foo"));
|
||||
ASSERT_OK(Put(1, "Eevee", "v0"));
|
||||
ASSERT_EQ("v0", Get(1, "Eevee"));
|
||||
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
|
||||
// writing to all three cf, flush default cf
|
||||
// LogNumbers: default: 14, stats: 4, pikachu: 4
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_test->GetLogNumber());
|
||||
ASSERT_LT(cfd_stats->GetLogNumber(), cfd_default->GetLogNumber());
|
||||
|
||||
ASSERT_OK(Put("foo1", "v1"));
|
||||
ASSERT_OK(Put("bar1", "v1"));
|
||||
ASSERT_EQ("v1", Get("bar1"));
|
||||
ASSERT_EQ("v1", Get("foo1"));
|
||||
ASSERT_OK(Put(1, "Vaporeon", "v1"));
|
||||
ASSERT_EQ("v1", Get(1, "Vaporeon"));
|
||||
// writing to default and test cf, flush test cf
|
||||
// LogNumbers: default: 14, stats: 16, pikachu: 16
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_test->GetLogNumber());
|
||||
ASSERT_GT(cfd_stats->GetLogNumber(), cfd_default->GetLogNumber());
|
||||
|
||||
ASSERT_OK(Put("foo2", "v2"));
|
||||
ASSERT_OK(Put("bar2", "v2"));
|
||||
ASSERT_EQ("v2", Get("bar2"));
|
||||
ASSERT_EQ("v2", Get("foo2"));
|
||||
dbfull()->TEST_WaitForPersistStatsRun(
|
||||
[&] { mock_env->set_current_time(10); });
|
||||
// writing to default and stats cf, flushing default cf
|
||||
// LogNumbers: default: 19, stats: 19, pikachu: 19
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_test->GetLogNumber());
|
||||
ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_default->GetLogNumber());
|
||||
|
||||
ASSERT_OK(Put("foo3", "v3"));
|
||||
ASSERT_OK(Put("bar3", "v3"));
|
||||
ASSERT_EQ("v3", Get("bar3"));
|
||||
ASSERT_EQ("v3", Get("foo3"));
|
||||
ASSERT_OK(Put(1, "Jolteon", "v3"));
|
||||
ASSERT_EQ("v3", Get(1, "Jolteon"));
|
||||
dbfull()->TEST_WaitForPersistStatsRun(
|
||||
[&] { mock_env->set_current_time(15); });
|
||||
// writing to all three cf, flushing test cf
|
||||
// LogNumbers: default: 19, stats: 19, pikachu: 22
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_LT(cfd_stats->GetLogNumber(), cfd_test->GetLogNumber());
|
||||
ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_default->GetLogNumber());
|
||||
Close();
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -1671,7 +1671,9 @@ std::unordered_map<std::string, BlockBasedTableOptions::IndexType>
|
||||
{"kBinarySearch", BlockBasedTableOptions::IndexType::kBinarySearch},
|
||||
{"kHashSearch", BlockBasedTableOptions::IndexType::kHashSearch},
|
||||
{"kTwoLevelIndexSearch",
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch}};
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch},
|
||||
{"kBinarySearchWithFirstKey",
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey}};
|
||||
|
||||
std::unordered_map<std::string, BlockBasedTableOptions::DataBlockIndexType>
|
||||
OptionsHelper::block_base_table_data_block_index_type_string_map = {
|
||||
|
||||
+21
-18
@@ -617,8 +617,9 @@ TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) {
|
||||
new_opt.block_cache)->GetNumShardBits(),
|
||||
GetDefaultCacheShardBits(new_opt.block_cache->GetCapacity()));
|
||||
ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), false);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(
|
||||
new_opt.block_cache)->GetHighPriPoolRatio(), 0.0);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(new_opt.block_cache)
|
||||
->GetHighPriPoolRatio(),
|
||||
0.5);
|
||||
ASSERT_TRUE(new_opt.block_cache_compressed != nullptr);
|
||||
ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 2*1024UL*1024UL);
|
||||
// Default values
|
||||
@@ -627,16 +628,17 @@ TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) {
|
||||
GetDefaultCacheShardBits(
|
||||
new_opt.block_cache_compressed->GetCapacity()));
|
||||
ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), false);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(
|
||||
new_opt.block_cache_compressed)->GetHighPriPoolRatio(),
|
||||
0.0);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(new_opt.block_cache_compressed)
|
||||
->GetHighPriPoolRatio(),
|
||||
0.5);
|
||||
|
||||
// Set couple of block cache options.
|
||||
ASSERT_OK(GetBlockBasedTableOptionsFromString(table_opt,
|
||||
"block_cache={num_shard_bits=5;high_pri_pool_ratio=0.5;};"
|
||||
"block_cache_compressed={num_shard_bits=5;"
|
||||
"high_pri_pool_ratio=0.5;}",
|
||||
&new_opt));
|
||||
ASSERT_OK(GetBlockBasedTableOptionsFromString(
|
||||
table_opt,
|
||||
"block_cache={num_shard_bits=5;high_pri_pool_ratio=0.5;};"
|
||||
"block_cache_compressed={num_shard_bits=5;"
|
||||
"high_pri_pool_ratio=0.0;}",
|
||||
&new_opt));
|
||||
ASSERT_EQ(new_opt.block_cache->GetCapacity(), 0);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<ShardedCache>(
|
||||
new_opt.block_cache)->GetNumShardBits(), 5);
|
||||
@@ -648,9 +650,9 @@ TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) {
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<ShardedCache>(
|
||||
new_opt.block_cache_compressed)->GetNumShardBits(), 5);
|
||||
ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), false);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(
|
||||
new_opt.block_cache_compressed)->GetHighPriPoolRatio(),
|
||||
0.5);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(new_opt.block_cache_compressed)
|
||||
->GetHighPriPoolRatio(),
|
||||
0.0);
|
||||
|
||||
// Set couple of block cache options.
|
||||
ASSERT_OK(GetBlockBasedTableOptionsFromString(table_opt,
|
||||
@@ -664,16 +666,17 @@ TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) {
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<ShardedCache>(
|
||||
new_opt.block_cache)->GetNumShardBits(), 4);
|
||||
ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), true);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(
|
||||
new_opt.block_cache)->GetHighPriPoolRatio(), 0.0);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(new_opt.block_cache)
|
||||
->GetHighPriPoolRatio(),
|
||||
0.5);
|
||||
ASSERT_TRUE(new_opt.block_cache_compressed != nullptr);
|
||||
ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 1024UL*1024UL);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<ShardedCache>(
|
||||
new_opt.block_cache_compressed)->GetNumShardBits(), 4);
|
||||
ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), true);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(
|
||||
new_opt.block_cache_compressed)->GetHighPriPoolRatio(),
|
||||
0.0);
|
||||
ASSERT_EQ(std::dynamic_pointer_cast<LRUCache>(new_opt.block_cache_compressed)
|
||||
->GetHighPriPoolRatio(),
|
||||
0.5);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
|
||||
@@ -5,10 +5,24 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(__clang__)
|
||||
// glibc's `posix_memalign()` declaration specifies `throw()` while clang's
|
||||
// declaration does not. There is a hack in clang to make its re-declaration
|
||||
// compatible with glibc's if they are declared consecutively. That hack breaks
|
||||
// if yet another `posix_memalign()` declaration comes between glibc's and
|
||||
// clang's declarations. Include "mm_malloc.h" here ensures glibc's and clang's
|
||||
// declarations both come before "jemalloc.h"'s `posix_memalign()` declaration.
|
||||
//
|
||||
// This problem could also be avoided if "jemalloc.h"'s `posix_memalign()`
|
||||
// declaration did not specify `throw()` when built with clang.
|
||||
#include <mm_malloc.h>
|
||||
#endif
|
||||
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
#ifdef __FreeBSD__
|
||||
#include <malloc_np.h>
|
||||
#else
|
||||
#define JEMALLOC_MANGLE
|
||||
#include <jemalloc/jemalloc.h>
|
||||
#endif
|
||||
|
||||
|
||||
+1
-2
@@ -979,8 +979,7 @@ uint64_t WinEnvIO::NowMicros() {
|
||||
return li.QuadPart;
|
||||
}
|
||||
using namespace std::chrono;
|
||||
return duration_cast<microseconds>(
|
||||
high_resolution_clock::now().time_since_epoch()).count();
|
||||
return duration_cast<microseconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
uint64_t WinEnvIO::NowNanos() {
|
||||
|
||||
@@ -36,6 +36,7 @@ LIB_SOURCES = \
|
||||
db/flush_job.cc \
|
||||
db/flush_scheduler.cc \
|
||||
db/forward_iterator.cc \
|
||||
db/import_column_family_job.cc \
|
||||
db/internal_stats.cc \
|
||||
db/logs_with_prep_tracker.cc \
|
||||
db/log_reader.cc \
|
||||
@@ -115,6 +116,7 @@ LIB_SOURCES = \
|
||||
table/block_based/block_prefix_index.cc \
|
||||
table/block_based/data_block_hash_index.cc \
|
||||
table/block_based/data_block_footer.cc \
|
||||
table/block_based/filter_block_reader_common.cc \
|
||||
table/block_based/flush_block_policy.cc \
|
||||
table/block_based/full_filter_block.cc \
|
||||
table/block_based/index_builder.cc \
|
||||
@@ -199,6 +201,7 @@ LIB_SOURCES = \
|
||||
utilities/persistent_cache/block_cache_tier_metadata.cc \
|
||||
utilities/persistent_cache/persistent_cache_tier.cc \
|
||||
utilities/persistent_cache/volatile_tier_impl.cc \
|
||||
utilities/simulator_cache/cache_simulator.cc \
|
||||
utilities/simulator_cache/sim_cache.cc \
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc \
|
||||
utilities/trace/file_trace_reader_writer.cc \
|
||||
@@ -344,6 +347,7 @@ MAIN_SOURCES = \
|
||||
env/env_test.cc \
|
||||
env/mock_env_test.cc \
|
||||
logging/auto_roll_logger_test.cc \
|
||||
logging/env_logger_test.cc \
|
||||
logging/event_logger_test.cc \
|
||||
memory/arena_test.cc \
|
||||
memtable/inlineskiplist_test.cc \
|
||||
@@ -403,6 +407,7 @@ MAIN_SOURCES = \
|
||||
utilities/object_registry_test.cc \
|
||||
utilities/option_change_migration/option_change_migration_test.cc \
|
||||
utilities/options/options_util_test.cc \
|
||||
utilities/simulator_cache/cache_simulator_test.cc \
|
||||
utilities/simulator_cache/sim_cache_test.cc \
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc \
|
||||
utilities/transactions/optimistic_transaction_test.cc \
|
||||
|
||||
+39
-38
@@ -608,8 +608,7 @@ bool IndexBlockIter::ParseNextIndexKey() {
|
||||
}
|
||||
// else we are in the middle of a restart interval and the restart_index_
|
||||
// thus has not changed
|
||||
if (value_delta_encoded_) {
|
||||
assert(value_length == 0);
|
||||
if (value_delta_encoded_ || global_seqno_state_ != nullptr) {
|
||||
DecodeCurrentValue(shared);
|
||||
}
|
||||
return true;
|
||||
@@ -627,24 +626,32 @@ bool IndexBlockIter::ParseNextIndexKey() {
|
||||
// Otherwise the format is delta-size = block handle size - size of last block
|
||||
// handle.
|
||||
void IndexBlockIter::DecodeCurrentValue(uint32_t shared) {
|
||||
assert(value_delta_encoded_);
|
||||
const char* limit = data_ + restarts_;
|
||||
if (shared == 0) {
|
||||
uint64_t o, s;
|
||||
const char* newp = GetVarint64Ptr(value_.data(), limit, &o);
|
||||
assert(newp);
|
||||
newp = GetVarint64Ptr(newp, limit, &s);
|
||||
assert(newp);
|
||||
decoded_value_ = BlockHandle(o, s);
|
||||
value_ = Slice(value_.data(), newp - value_.data());
|
||||
} else {
|
||||
uint64_t next_value_base =
|
||||
decoded_value_.offset() + decoded_value_.size() + kBlockTrailerSize;
|
||||
int64_t delta;
|
||||
const char* newp = GetVarsignedint64Ptr(value_.data(), limit, &delta);
|
||||
decoded_value_ =
|
||||
BlockHandle(next_value_base, decoded_value_.size() + delta);
|
||||
value_ = Slice(value_.data(), newp - value_.data());
|
||||
Slice v(value_.data(), data_ + restarts_ - value_.data());
|
||||
// Delta encoding is used if `shared` != 0.
|
||||
Status decode_s __attribute__((__unused__)) = decoded_value_.DecodeFrom(
|
||||
&v, have_first_key_,
|
||||
(value_delta_encoded_ && shared) ? &decoded_value_.handle : nullptr);
|
||||
assert(decode_s.ok());
|
||||
value_ = Slice(value_.data(), v.data() - value_.data());
|
||||
|
||||
if (global_seqno_state_ != nullptr) {
|
||||
// Overwrite sequence number the same way as in DataBlockIter.
|
||||
|
||||
IterKey& first_internal_key = global_seqno_state_->first_internal_key;
|
||||
first_internal_key.SetInternalKey(decoded_value_.first_internal_key,
|
||||
/* copy */ true);
|
||||
|
||||
assert(GetInternalKeySeqno(first_internal_key.GetInternalKey()) == 0);
|
||||
|
||||
ValueType value_type = ExtractValueType(first_internal_key.GetKey());
|
||||
assert(value_type == ValueType::kTypeValue ||
|
||||
value_type == ValueType::kTypeMerge ||
|
||||
value_type == ValueType::kTypeDeletion ||
|
||||
value_type == ValueType::kTypeRangeDeletion);
|
||||
|
||||
first_internal_key.UpdateInternalKey(global_seqno_state_->global_seqno,
|
||||
value_type);
|
||||
decoded_value_.first_internal_key = first_internal_key.GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,14 +882,10 @@ Block::Block(BlockContents&& contents, SequenceNumber _global_seqno,
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
DataBlockIter* Block::NewIterator(const Comparator* cmp, const Comparator* ucmp,
|
||||
DataBlockIter* iter, Statistics* stats,
|
||||
bool /*total_order_seek*/,
|
||||
bool /*key_includes_seq*/,
|
||||
bool /*value_is_full*/,
|
||||
bool block_contents_pinned,
|
||||
BlockPrefixIndex* /*prefix_index*/) {
|
||||
DataBlockIter* Block::NewDataIterator(const Comparator* cmp,
|
||||
const Comparator* ucmp,
|
||||
DataBlockIter* iter, Statistics* stats,
|
||||
bool block_contents_pinned) {
|
||||
DataBlockIter* ret_iter;
|
||||
if (iter != nullptr) {
|
||||
ret_iter = iter;
|
||||
@@ -913,13 +916,11 @@ DataBlockIter* Block::NewIterator(const Comparator* cmp, const Comparator* ucmp,
|
||||
return ret_iter;
|
||||
}
|
||||
|
||||
template <>
|
||||
IndexBlockIter* Block::NewIterator(const Comparator* cmp,
|
||||
const Comparator* ucmp, IndexBlockIter* iter,
|
||||
Statistics* /*stats*/, bool total_order_seek,
|
||||
bool key_includes_seq, bool value_is_full,
|
||||
bool block_contents_pinned,
|
||||
BlockPrefixIndex* prefix_index) {
|
||||
IndexBlockIter* Block::NewIndexIterator(
|
||||
const Comparator* cmp, const Comparator* ucmp, IndexBlockIter* iter,
|
||||
Statistics* /*stats*/, bool total_order_seek, bool have_first_key,
|
||||
bool key_includes_seq, bool value_is_full, bool block_contents_pinned,
|
||||
BlockPrefixIndex* prefix_index) {
|
||||
IndexBlockIter* ret_iter;
|
||||
if (iter != nullptr) {
|
||||
ret_iter = iter;
|
||||
@@ -938,9 +939,9 @@ IndexBlockIter* Block::NewIterator(const Comparator* cmp,
|
||||
BlockPrefixIndex* prefix_index_ptr =
|
||||
total_order_seek ? nullptr : prefix_index;
|
||||
ret_iter->Initialize(cmp, ucmp, data_, restart_offset_, num_restarts_,
|
||||
prefix_index_ptr, key_includes_seq, value_is_full,
|
||||
block_contents_pinned,
|
||||
nullptr /* data_block_hash_index */);
|
||||
global_seqno_, prefix_index_ptr, have_first_key,
|
||||
key_includes_seq, value_is_full,
|
||||
block_contents_pinned);
|
||||
}
|
||||
|
||||
return ret_iter;
|
||||
|
||||
+64
-39
@@ -165,17 +165,7 @@ class Block {
|
||||
// If iter is null, return new Iterator
|
||||
// If iter is not null, update this one and return it as Iterator*
|
||||
//
|
||||
// key_includes_seq, default true, means that the keys are in internal key
|
||||
// format.
|
||||
// value_is_full, default true, means that no delta encoding is
|
||||
// applied to values.
|
||||
//
|
||||
// NewIterator<DataBlockIter>
|
||||
// Same as above but also updates read_amp_bitmap_ if it is not nullptr.
|
||||
//
|
||||
// NewIterator<IndexBlockIter>
|
||||
// If `prefix_index` is not nullptr this block will do hash lookup for the key
|
||||
// prefix. If total_order_seek is true, prefix_index_ is ignored.
|
||||
// Updates read_amp_bitmap_ if it is not nullptr.
|
||||
//
|
||||
// If `block_contents_pinned` is true, the caller will guarantee that when
|
||||
// the cleanup functions are transferred from the iterator to other
|
||||
@@ -188,13 +178,32 @@ class Block {
|
||||
// NOTE: for the hash based lookup, if a key prefix doesn't match any key,
|
||||
// the iterator will simply be set as "invalid", rather than returning
|
||||
// the key that is just pass the target key.
|
||||
template <typename TBlockIter>
|
||||
TBlockIter* NewIterator(
|
||||
const Comparator* comparator, const Comparator* user_comparator,
|
||||
TBlockIter* iter = nullptr, Statistics* stats = nullptr,
|
||||
bool total_order_seek = true, bool key_includes_seq = true,
|
||||
bool value_is_full = true, bool block_contents_pinned = false,
|
||||
BlockPrefixIndex* prefix_index = nullptr);
|
||||
|
||||
DataBlockIter* NewDataIterator(const Comparator* comparator,
|
||||
const Comparator* user_comparator,
|
||||
DataBlockIter* iter = nullptr,
|
||||
Statistics* stats = nullptr,
|
||||
bool block_contents_pinned = false);
|
||||
|
||||
// key_includes_seq, default true, means that the keys are in internal key
|
||||
// format.
|
||||
// value_is_full, default true, means that no delta encoding is
|
||||
// applied to values.
|
||||
//
|
||||
// If `prefix_index` is not nullptr this block will do hash lookup for the key
|
||||
// prefix. If total_order_seek is true, prefix_index_ is ignored.
|
||||
//
|
||||
// `have_first_key` controls whether IndexValue will contain
|
||||
// first_internal_key. It affects data serialization format, so the same value
|
||||
// have_first_key must be used when writing and reading index.
|
||||
// It is determined by IndexType property of the table.
|
||||
IndexBlockIter* NewIndexIterator(const Comparator* comparator,
|
||||
const Comparator* user_comparator,
|
||||
IndexBlockIter* iter, Statistics* stats,
|
||||
bool total_order_seek, bool have_first_key,
|
||||
bool key_includes_seq, bool value_is_full,
|
||||
bool block_contents_pinned = false,
|
||||
BlockPrefixIndex* prefix_index = nullptr);
|
||||
|
||||
// Report an approximation of how much memory has been used.
|
||||
size_t ApproximateMemoryUsage() const;
|
||||
@@ -471,7 +480,7 @@ class DataBlockIter final : public BlockIter<Slice> {
|
||||
bool SeekForGetImpl(const Slice& target);
|
||||
};
|
||||
|
||||
class IndexBlockIter final : public BlockIter<BlockHandle> {
|
||||
class IndexBlockIter final : public BlockIter<IndexValue> {
|
||||
public:
|
||||
IndexBlockIter() : BlockIter(), prefix_index_(nullptr) {}
|
||||
|
||||
@@ -483,23 +492,12 @@ class IndexBlockIter final : public BlockIter<BlockHandle> {
|
||||
// format.
|
||||
// value_is_full, default true, means that no delta encoding is
|
||||
// applied to values.
|
||||
IndexBlockIter(const Comparator* comparator,
|
||||
const Comparator* user_comparator, const char* data,
|
||||
uint32_t restarts, uint32_t num_restarts,
|
||||
BlockPrefixIndex* prefix_index, bool key_includes_seq,
|
||||
bool value_is_full, bool block_contents_pinned)
|
||||
: IndexBlockIter() {
|
||||
Initialize(comparator, user_comparator, data, restarts, num_restarts,
|
||||
prefix_index, key_includes_seq, block_contents_pinned,
|
||||
value_is_full, nullptr /* data_block_hash_index */);
|
||||
}
|
||||
|
||||
void Initialize(const Comparator* comparator,
|
||||
const Comparator* user_comparator, const char* data,
|
||||
uint32_t restarts, uint32_t num_restarts,
|
||||
BlockPrefixIndex* prefix_index, bool key_includes_seq,
|
||||
bool value_is_full, bool block_contents_pinned,
|
||||
DataBlockHashIndex* /*data_block_hash_index*/) {
|
||||
SequenceNumber global_seqno, BlockPrefixIndex* prefix_index,
|
||||
bool have_first_key, bool key_includes_seq,
|
||||
bool value_is_full, bool block_contents_pinned) {
|
||||
InitializeBase(key_includes_seq ? comparator : user_comparator, data,
|
||||
restarts, num_restarts, kDisableGlobalSequenceNumber,
|
||||
block_contents_pinned);
|
||||
@@ -507,6 +505,12 @@ class IndexBlockIter final : public BlockIter<BlockHandle> {
|
||||
key_.SetIsUserKey(!key_includes_seq_);
|
||||
prefix_index_ = prefix_index;
|
||||
value_delta_encoded_ = !value_is_full;
|
||||
have_first_key_ = have_first_key;
|
||||
if (have_first_key_ && global_seqno != kDisableGlobalSequenceNumber) {
|
||||
global_seqno_state_.reset(new GlobalSeqnoState(global_seqno));
|
||||
} else {
|
||||
global_seqno_state_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Slice user_key() const override {
|
||||
@@ -516,16 +520,17 @@ class IndexBlockIter final : public BlockIter<BlockHandle> {
|
||||
return key();
|
||||
}
|
||||
|
||||
virtual BlockHandle value() const override {
|
||||
virtual IndexValue value() const override {
|
||||
assert(Valid());
|
||||
if (value_delta_encoded_) {
|
||||
if (value_delta_encoded_ || global_seqno_state_ != nullptr) {
|
||||
return decoded_value_;
|
||||
} else {
|
||||
BlockHandle handle;
|
||||
IndexValue entry;
|
||||
Slice v = value_;
|
||||
Status decode_s __attribute__((__unused__)) = handle.DecodeFrom(&v);
|
||||
Status decode_s __attribute__((__unused__)) =
|
||||
entry.DecodeFrom(&v, have_first_key_, nullptr);
|
||||
assert(decode_s.ok());
|
||||
return handle;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,10 +557,15 @@ class IndexBlockIter final : public BlockIter<BlockHandle> {
|
||||
|
||||
void Invalidate(Status s) { InvalidateBase(s); }
|
||||
|
||||
bool IsValuePinned() const override {
|
||||
return global_seqno_state_ != nullptr ? false : BlockIter::IsValuePinned();
|
||||
}
|
||||
|
||||
private:
|
||||
// Key is in InternalKey format
|
||||
bool key_includes_seq_;
|
||||
bool value_delta_encoded_;
|
||||
bool have_first_key_; // value includes first_internal_key
|
||||
BlockPrefixIndex* prefix_index_;
|
||||
// Whether the value is delta encoded. In that case the value is assumed to be
|
||||
// BlockHandle. The first value in each restart interval is the full encoded
|
||||
@@ -563,7 +573,22 @@ class IndexBlockIter final : public BlockIter<BlockHandle> {
|
||||
// offset of delta encoded BlockHandles is computed by adding the size of
|
||||
// previous delta encoded values in the same restart interval to the offset of
|
||||
// the first value in that restart interval.
|
||||
BlockHandle decoded_value_;
|
||||
IndexValue decoded_value_;
|
||||
|
||||
// When sequence number overwriting is enabled, this struct contains the seqno
|
||||
// to overwrite with, and current first_internal_key with overwritten seqno.
|
||||
// This is rarely used, so we put it behind a pointer and only allocate when
|
||||
// needed.
|
||||
struct GlobalSeqnoState {
|
||||
// First internal key according to current index entry, but with sequence
|
||||
// number overwritten to global_seqno.
|
||||
IterKey first_internal_key;
|
||||
SequenceNumber global_seqno;
|
||||
|
||||
explicit GlobalSeqnoState(SequenceNumber seqno) : global_seqno(seqno) {}
|
||||
};
|
||||
|
||||
std::unique_ptr<GlobalSeqnoState> global_seqno_state_;
|
||||
|
||||
bool PrefixSeek(const Slice& target, uint32_t* index);
|
||||
bool BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -162,58 +163,120 @@ void BlockBasedFilterBlockBuilder::GenerateFilter() {
|
||||
}
|
||||
|
||||
BlockBasedFilterBlockReader::BlockBasedFilterBlockReader(
|
||||
const SliceTransform* prefix_extractor,
|
||||
const BlockBasedTableOptions& table_opt, bool _whole_key_filtering,
|
||||
BlockContents&& contents, Statistics* stats)
|
||||
: FilterBlockReader(contents.data.size(), stats, _whole_key_filtering),
|
||||
policy_(table_opt.filter_policy.get()),
|
||||
prefix_extractor_(prefix_extractor),
|
||||
data_(nullptr),
|
||||
offset_(nullptr),
|
||||
num_(0),
|
||||
base_lg_(0),
|
||||
contents_(std::move(contents)) {
|
||||
assert(policy_);
|
||||
size_t n = contents_.data.size();
|
||||
if (n < 5) return; // 1 byte for base_lg_ and 4 for start of offset array
|
||||
base_lg_ = contents_.data[n - 1];
|
||||
uint32_t last_word = DecodeFixed32(contents_.data.data() + n - 5);
|
||||
if (last_word > n - 5) return;
|
||||
data_ = contents_.data.data();
|
||||
offset_ = data_ + last_word;
|
||||
num_ = (n - 5 - last_word) / 4;
|
||||
const BlockBasedTable* t, CachableEntry<BlockContents>&& filter_block)
|
||||
: FilterBlockReaderCommon(t, std::move(filter_block)) {
|
||||
assert(table());
|
||||
assert(table()->get_rep());
|
||||
assert(table()->get_rep()->filter_policy);
|
||||
}
|
||||
|
||||
std::unique_ptr<FilterBlockReader> BlockBasedFilterBlockReader::Create(
|
||||
const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
|
||||
bool use_cache, bool prefetch, bool pin,
|
||||
BlockCacheLookupContext* lookup_context) {
|
||||
assert(table);
|
||||
assert(table->get_rep());
|
||||
assert(!pin || prefetch);
|
||||
|
||||
CachableEntry<BlockContents> filter_block;
|
||||
if (prefetch || !use_cache) {
|
||||
const Status s = ReadFilterBlock(table, prefetch_buffer, ReadOptions(),
|
||||
nullptr /* get_context */, lookup_context,
|
||||
&filter_block);
|
||||
if (!s.ok()) {
|
||||
return std::unique_ptr<FilterBlockReader>();
|
||||
}
|
||||
|
||||
if (use_cache && !pin) {
|
||||
filter_block.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
return std::unique_ptr<FilterBlockReader>(
|
||||
new BlockBasedFilterBlockReader(table, std::move(filter_block)));
|
||||
}
|
||||
|
||||
bool BlockBasedFilterBlockReader::KeyMayMatch(
|
||||
const Slice& key, const SliceTransform* /* prefix_extractor */,
|
||||
uint64_t block_offset, const bool /*no_io*/,
|
||||
const Slice* const /*const_ikey_ptr*/,
|
||||
BlockCacheLookupContext* /*context*/) {
|
||||
uint64_t block_offset, const bool no_io,
|
||||
const Slice* const /*const_ikey_ptr*/, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) {
|
||||
assert(block_offset != kNotValid);
|
||||
if (!whole_key_filtering_) {
|
||||
if (!whole_key_filtering()) {
|
||||
return true;
|
||||
}
|
||||
return MayMatch(key, block_offset);
|
||||
return MayMatch(key, block_offset, no_io, get_context, lookup_context);
|
||||
}
|
||||
|
||||
bool BlockBasedFilterBlockReader::PrefixMayMatch(
|
||||
const Slice& prefix, const SliceTransform* /* prefix_extractor */,
|
||||
uint64_t block_offset, const bool /*no_io*/,
|
||||
const Slice* const /*const_ikey_ptr*/,
|
||||
BlockCacheLookupContext* /*context*/) {
|
||||
uint64_t block_offset, const bool no_io,
|
||||
const Slice* const /*const_ikey_ptr*/, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) {
|
||||
assert(block_offset != kNotValid);
|
||||
return MayMatch(prefix, block_offset);
|
||||
return MayMatch(prefix, block_offset, no_io, get_context, lookup_context);
|
||||
}
|
||||
|
||||
bool BlockBasedFilterBlockReader::MayMatch(const Slice& entry,
|
||||
uint64_t block_offset) {
|
||||
uint64_t index = block_offset >> base_lg_;
|
||||
if (index < num_) {
|
||||
uint32_t start = DecodeFixed32(offset_ + index * 4);
|
||||
uint32_t limit = DecodeFixed32(offset_ + index * 4 + 4);
|
||||
if (start <= limit && limit <= (uint32_t)(offset_ - data_)) {
|
||||
Slice filter = Slice(data_ + start, limit - start);
|
||||
bool const may_match = policy_->KeyMayMatch(entry, filter);
|
||||
bool BlockBasedFilterBlockReader::ParseFieldsFromBlock(
|
||||
const BlockContents& contents, const char** data, const char** offset,
|
||||
size_t* num, size_t* base_lg) {
|
||||
assert(data);
|
||||
assert(offset);
|
||||
assert(num);
|
||||
assert(base_lg);
|
||||
|
||||
const size_t n = contents.data.size();
|
||||
if (n < 5) { // 1 byte for base_lg and 4 for start of offset array
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t last_word = DecodeFixed32(contents.data.data() + n - 5);
|
||||
if (last_word > n - 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*data = contents.data.data();
|
||||
*offset = (*data) + last_word;
|
||||
*num = (n - 5 - last_word) / 4;
|
||||
*base_lg = contents.data[n - 1];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlockBasedFilterBlockReader::MayMatch(
|
||||
const Slice& entry, uint64_t block_offset, bool no_io,
|
||||
GetContext* get_context, BlockCacheLookupContext* lookup_context) const {
|
||||
CachableEntry<BlockContents> filter_block;
|
||||
|
||||
const Status s =
|
||||
GetOrReadFilterBlock(no_io, get_context, lookup_context, &filter_block);
|
||||
if (!s.ok()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(filter_block.GetValue());
|
||||
|
||||
const char* data = nullptr;
|
||||
const char* offset = nullptr;
|
||||
size_t num = 0;
|
||||
size_t base_lg = 0;
|
||||
if (!ParseFieldsFromBlock(*filter_block.GetValue(), &data, &offset, &num,
|
||||
&base_lg)) {
|
||||
return true; // Errors are treated as potential matches
|
||||
}
|
||||
|
||||
const uint64_t index = block_offset >> base_lg;
|
||||
if (index < num) {
|
||||
const uint32_t start = DecodeFixed32(offset + index * 4);
|
||||
const uint32_t limit = DecodeFixed32(offset + index * 4 + 4);
|
||||
if (start <= limit && limit <= (uint32_t)(offset - data)) {
|
||||
const Slice filter = Slice(data + start, limit - start);
|
||||
|
||||
assert(table());
|
||||
assert(table()->get_rep());
|
||||
const FilterPolicy* const policy = table()->get_rep()->filter_policy;
|
||||
|
||||
const bool may_match = policy->KeyMayMatch(entry, filter);
|
||||
if (may_match) {
|
||||
PERF_COUNTER_ADD(bloom_sst_hit_count, 1);
|
||||
return true;
|
||||
@@ -230,27 +293,54 @@ bool BlockBasedFilterBlockReader::MayMatch(const Slice& entry,
|
||||
}
|
||||
|
||||
size_t BlockBasedFilterBlockReader::ApproximateMemoryUsage() const {
|
||||
return num_ * 4 + 5 + (offset_ - data_);
|
||||
size_t usage = ApproximateFilterBlockMemoryUsage();
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
usage += malloc_usable_size(const_cast<BlockBasedFilterBlockReader*>(this));
|
||||
#else
|
||||
usage += sizeof(*this);
|
||||
#endif // ROCKSDB_MALLOC_USABLE_SIZE
|
||||
return usage;
|
||||
}
|
||||
|
||||
std::string BlockBasedFilterBlockReader::ToString() const {
|
||||
CachableEntry<BlockContents> filter_block;
|
||||
|
||||
const Status s =
|
||||
GetOrReadFilterBlock(false /* no_io */, nullptr /* get_context */,
|
||||
nullptr /* lookup_context */, &filter_block);
|
||||
if (!s.ok()) {
|
||||
return std::string("Unable to retrieve filter block");
|
||||
}
|
||||
|
||||
assert(filter_block.GetValue());
|
||||
|
||||
const char* data = nullptr;
|
||||
const char* offset = nullptr;
|
||||
size_t num = 0;
|
||||
size_t base_lg = 0;
|
||||
if (!ParseFieldsFromBlock(*filter_block.GetValue(), &data, &offset, &num,
|
||||
&base_lg)) {
|
||||
return std::string("Error parsing filter block");
|
||||
}
|
||||
|
||||
std::string result;
|
||||
result.reserve(1024);
|
||||
|
||||
std::string s_bo("Block offset"), s_hd("Hex dump"), s_fb("# filter blocks");
|
||||
AppendItem(&result, s_fb, rocksdb::ToString(num_));
|
||||
AppendItem(&result, s_fb, rocksdb::ToString(num));
|
||||
AppendItem(&result, s_bo, s_hd);
|
||||
|
||||
for (size_t index = 0; index < num_; index++) {
|
||||
uint32_t start = DecodeFixed32(offset_ + index * 4);
|
||||
uint32_t limit = DecodeFixed32(offset_ + index * 4 + 4);
|
||||
for (size_t index = 0; index < num; index++) {
|
||||
uint32_t start = DecodeFixed32(offset + index * 4);
|
||||
uint32_t limit = DecodeFixed32(offset + index * 4 + 4);
|
||||
|
||||
if (start != limit) {
|
||||
result.append(" filter block # " + rocksdb::ToString(index + 1) + "\n");
|
||||
Slice filter = Slice(data_ + start, limit - start);
|
||||
Slice filter = Slice(data + start, limit - start);
|
||||
AppendItem(&result, start, filter.ToString(true));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -22,7 +22,8 @@
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "table/block_based/filter_block.h"
|
||||
#include "table/block_based/filter_block_reader_common.h"
|
||||
#include "table/format.h"
|
||||
#include "util/hash.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -75,42 +76,42 @@ class BlockBasedFilterBlockBuilder : public FilterBlockBuilder {
|
||||
|
||||
// A FilterBlockReader is used to parse filter from SST table.
|
||||
// KeyMayMatch and PrefixMayMatch would trigger filter checking
|
||||
class BlockBasedFilterBlockReader : public FilterBlockReader {
|
||||
class BlockBasedFilterBlockReader
|
||||
: public FilterBlockReaderCommon<BlockContents> {
|
||||
public:
|
||||
// REQUIRES: "contents" and *policy must stay live while *this is live.
|
||||
BlockBasedFilterBlockReader(const SliceTransform* prefix_extractor,
|
||||
const BlockBasedTableOptions& table_opt,
|
||||
bool whole_key_filtering,
|
||||
BlockContents&& contents, Statistics* statistics);
|
||||
BlockBasedFilterBlockReader(const BlockBasedTable* t,
|
||||
CachableEntry<BlockContents>&& filter_block);
|
||||
|
||||
static std::unique_ptr<FilterBlockReader> Create(
|
||||
const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
|
||||
bool use_cache, bool prefetch, bool pin,
|
||||
BlockCacheLookupContext* lookup_context);
|
||||
|
||||
bool IsBlockBased() override { return true; }
|
||||
|
||||
bool KeyMayMatch(const Slice& key, const SliceTransform* prefix_extractor,
|
||||
uint64_t block_offset, const bool no_io,
|
||||
const Slice* const const_ikey_ptr,
|
||||
BlockCacheLookupContext* context) override;
|
||||
const Slice* const const_ikey_ptr, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) override;
|
||||
bool PrefixMayMatch(const Slice& prefix,
|
||||
const SliceTransform* prefix_extractor,
|
||||
uint64_t block_offset, const bool no_io,
|
||||
const Slice* const const_ikey_ptr,
|
||||
BlockCacheLookupContext* context) override;
|
||||
GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) override;
|
||||
size_t ApproximateMemoryUsage() const override;
|
||||
|
||||
// convert this object to a human readable form
|
||||
std::string ToString() const override;
|
||||
|
||||
private:
|
||||
const FilterPolicy* policy_;
|
||||
const SliceTransform* prefix_extractor_;
|
||||
const char* data_; // Pointer to filter data (at block-start)
|
||||
const char* offset_; // Pointer to beginning of offset array (at block-end)
|
||||
size_t num_; // Number of entries in offset array
|
||||
size_t base_lg_; // Encoding parameter (see kFilterBaseLg in .cc file)
|
||||
BlockContents contents_;
|
||||
static bool ParseFieldsFromBlock(const BlockContents& contents,
|
||||
const char** data, const char** offset,
|
||||
size_t* num, size_t* base_lg);
|
||||
|
||||
bool MayMatch(const Slice& entry, uint64_t block_offset);
|
||||
|
||||
// No copying allowed
|
||||
BlockBasedFilterBlockReader(const BlockBasedFilterBlockReader&);
|
||||
void operator=(const BlockBasedFilterBlockReader&);
|
||||
bool MayMatch(const Slice& entry, uint64_t block_offset, bool no_io,
|
||||
GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) const;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "table/block_based/block_based_filter_block.h"
|
||||
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/coding.h"
|
||||
@@ -41,28 +42,58 @@ class TestHashFilter : public FilterPolicy {
|
||||
}
|
||||
};
|
||||
|
||||
class MockBlockBasedTable : public BlockBasedTable {
|
||||
public:
|
||||
explicit MockBlockBasedTable(Rep* rep)
|
||||
: BlockBasedTable(rep, nullptr /* block_cache_tracer */) {
|
||||
// Initialize what Open normally does as much as necessary for the test
|
||||
rep->cache_key_prefix_size = 10;
|
||||
}
|
||||
};
|
||||
|
||||
class FilterBlockTest : public testing::Test {
|
||||
public:
|
||||
TestHashFilter policy_;
|
||||
Options options_;
|
||||
ImmutableCFOptions ioptions_;
|
||||
EnvOptions env_options_;
|
||||
BlockBasedTableOptions table_options_;
|
||||
InternalKeyComparator icomp_;
|
||||
std::unique_ptr<BlockBasedTable> table_;
|
||||
|
||||
FilterBlockTest() {
|
||||
table_options_.filter_policy.reset(new TestHashFilter());
|
||||
FilterBlockTest()
|
||||
: ioptions_(options_),
|
||||
env_options_(options_),
|
||||
icomp_(options_.comparator) {
|
||||
table_options_.no_block_cache = true;
|
||||
table_options_.filter_policy.reset(new TestHashFilter);
|
||||
|
||||
constexpr bool skip_filters = false;
|
||||
constexpr int level = 0;
|
||||
constexpr bool immortal_table = false;
|
||||
table_.reset(new MockBlockBasedTable(
|
||||
new BlockBasedTable::Rep(ioptions_, env_options_, table_options_,
|
||||
icomp_, skip_filters, level, immortal_table)));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FilterBlockTest, EmptyBuilder) {
|
||||
BlockBasedFilterBlockBuilder builder(nullptr, table_options_);
|
||||
BlockContents block(builder.Finish());
|
||||
ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(block.data));
|
||||
BlockBasedFilterBlockReader reader(nullptr, table_options_, true,
|
||||
std::move(block), nullptr);
|
||||
Slice slice(builder.Finish());
|
||||
ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(slice));
|
||||
|
||||
CachableEntry<BlockContents> block(
|
||||
new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
|
||||
true /* own_value */);
|
||||
|
||||
BlockBasedFilterBlockReader reader(table_.get(), std::move(block));
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/100000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
}
|
||||
|
||||
TEST_F(FilterBlockTest, SingleChunk) {
|
||||
@@ -77,30 +108,46 @@ TEST_F(FilterBlockTest, SingleChunk) {
|
||||
builder.StartBlock(300);
|
||||
builder.Add("hello");
|
||||
ASSERT_EQ(5, builder.NumAdded());
|
||||
BlockContents block(builder.Finish());
|
||||
BlockBasedFilterBlockReader reader(nullptr, table_options_, true,
|
||||
std::move(block), nullptr);
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
Slice slice(builder.Finish());
|
||||
|
||||
CachableEntry<BlockContents> block(
|
||||
new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
|
||||
true /* own_value */);
|
||||
|
||||
BlockBasedFilterBlockReader reader(table_.get(), std::move(block));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("bar", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("box", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("hello", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"missing", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"other", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
}
|
||||
|
||||
TEST_F(FilterBlockTest, MultiChunk) {
|
||||
@@ -123,93 +170,139 @@ TEST_F(FilterBlockTest, MultiChunk) {
|
||||
builder.Add("box");
|
||||
builder.Add("hello");
|
||||
|
||||
BlockContents block(builder.Finish());
|
||||
BlockBasedFilterBlockReader reader(nullptr, table_options_, true,
|
||||
std::move(block), nullptr);
|
||||
Slice slice(builder.Finish());
|
||||
|
||||
CachableEntry<BlockContents> block(
|
||||
new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
|
||||
true /* own_value */);
|
||||
|
||||
BlockBasedFilterBlockReader reader(table_.get(), std::move(block));
|
||||
|
||||
// Check first filter
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/2000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("foo", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/uint64_t{0},
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("bar", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/2000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
|
||||
// Check second filter
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("box", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/3100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
|
||||
// Check third filter (empty)
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
|
||||
// Check last filter
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("box", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader.KeyMayMatch("hello", /*prefix_extractor=*/nullptr,
|
||||
/*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr,
|
||||
/*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader.KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
}
|
||||
|
||||
// Test for block based filter block
|
||||
// use new interface in FilterPolicy to create filter builder/reader
|
||||
class BlockBasedFilterBlockTest : public testing::Test {
|
||||
public:
|
||||
Options options_;
|
||||
ImmutableCFOptions ioptions_;
|
||||
EnvOptions env_options_;
|
||||
BlockBasedTableOptions table_options_;
|
||||
InternalKeyComparator icomp_;
|
||||
std::unique_ptr<BlockBasedTable> table_;
|
||||
|
||||
BlockBasedFilterBlockTest() {
|
||||
BlockBasedFilterBlockTest()
|
||||
: ioptions_(options_),
|
||||
env_options_(options_),
|
||||
icomp_(options_.comparator) {
|
||||
table_options_.no_block_cache = true;
|
||||
table_options_.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
}
|
||||
|
||||
~BlockBasedFilterBlockTest() override {}
|
||||
constexpr bool skip_filters = false;
|
||||
constexpr int level = 0;
|
||||
constexpr bool immortal_table = false;
|
||||
table_.reset(new MockBlockBasedTable(
|
||||
new BlockBasedTable::Rep(ioptions_, env_options_, table_options_,
|
||||
icomp_, skip_filters, level, immortal_table)));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BlockBasedFilterBlockTest, BlockBasedEmptyBuilder) {
|
||||
FilterBlockBuilder* builder =
|
||||
new BlockBasedFilterBlockBuilder(nullptr, table_options_);
|
||||
BlockContents block(builder->Finish());
|
||||
ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(block.data));
|
||||
FilterBlockReader* reader = new BlockBasedFilterBlockReader(
|
||||
nullptr, table_options_, true, std::move(block), nullptr);
|
||||
Slice slice(builder->Finish());
|
||||
ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(slice));
|
||||
|
||||
CachableEntry<BlockContents> block(
|
||||
new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
|
||||
true /* own_value */);
|
||||
|
||||
FilterBlockReader* reader =
|
||||
new BlockBasedFilterBlockReader(table_.get(), std::move(block));
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/10000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
|
||||
delete builder;
|
||||
delete reader;
|
||||
@@ -226,30 +319,42 @@ TEST_F(BlockBasedFilterBlockTest, BlockBasedSingleChunk) {
|
||||
builder->Add("box");
|
||||
builder->StartBlock(300);
|
||||
builder->Add("hello");
|
||||
BlockContents block(builder->Finish());
|
||||
FilterBlockReader* reader = new BlockBasedFilterBlockReader(
|
||||
nullptr, table_options_, true, std::move(block), nullptr);
|
||||
Slice slice(builder->Finish());
|
||||
|
||||
CachableEntry<BlockContents> block(
|
||||
new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
|
||||
true /* own_value */);
|
||||
|
||||
FilterBlockReader* reader =
|
||||
new BlockBasedFilterBlockReader(table_.get(), std::move(block));
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"missing", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"other", /*prefix_extractor=*/nullptr, /*block_offset=*/100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
|
||||
delete builder;
|
||||
delete reader;
|
||||
@@ -276,65 +381,86 @@ TEST_F(BlockBasedFilterBlockTest, BlockBasedMultiChunk) {
|
||||
builder->Add("box");
|
||||
builder->Add("hello");
|
||||
|
||||
BlockContents block(builder->Finish());
|
||||
FilterBlockReader* reader = new BlockBasedFilterBlockReader(
|
||||
nullptr, table_options_, true, std::move(block), nullptr);
|
||||
Slice slice(builder->Finish());
|
||||
|
||||
CachableEntry<BlockContents> block(
|
||||
new BlockContents(slice), nullptr /* cache */, nullptr /* cache_handle */,
|
||||
true /* own_value */);
|
||||
|
||||
FilterBlockReader* reader =
|
||||
new BlockBasedFilterBlockReader(table_.get(), std::move(block));
|
||||
|
||||
// Check first filter
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/2000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/uint64_t{0},
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
|
||||
// Check second filter
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/3100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
|
||||
// Check third filter (empty)
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/4100,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
|
||||
// Check last filter
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"box", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(reader->KeyMayMatch(
|
||||
"hello", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"foo", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
ASSERT_TRUE(!reader->KeyMayMatch(
|
||||
"bar", /*prefix_extractor=*/nullptr, /*block_offset=*/9000,
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*context=*/nullptr));
|
||||
/*no_io=*/false, /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
|
||||
delete builder;
|
||||
delete reader;
|
||||
|
||||
@@ -167,7 +167,12 @@ BlockBasedTableFactory::BlockBasedTableFactory(
|
||||
if (table_options_.no_block_cache) {
|
||||
table_options_.block_cache.reset();
|
||||
} else if (table_options_.block_cache == nullptr) {
|
||||
table_options_.block_cache = NewLRUCache(8 << 20);
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 8 << 20;
|
||||
// It makes little sense to pay overhead for mid-point insertion while the
|
||||
// block size is only 8MB.
|
||||
co.high_pri_pool_ratio = 0.0;
|
||||
table_options_.block_cache = NewLRUCache(co);
|
||||
}
|
||||
if (table_options_.block_size_deviation < 0 ||
|
||||
table_options_.block_size_deviation > 100) {
|
||||
@@ -257,6 +262,10 @@ Status BlockBasedTableFactory::SanitizeOptions(
|
||||
return Status::InvalidArgument(
|
||||
"Block alignment requested but block size is not a power of 2");
|
||||
}
|
||||
if (table_options_.block_size > port::kMaxUint32) {
|
||||
return Status::InvalidArgument(
|
||||
"block size exceeds maximum number (4GiB) allowed");
|
||||
}
|
||||
if (table_options_.data_block_index_type ==
|
||||
BlockBasedTableOptions::kDataBlockBinaryAndHash &&
|
||||
table_options_.data_block_hash_table_util_ratio <= 0) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -43,7 +43,6 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class BlockHandle;
|
||||
class Cache;
|
||||
class FilterBlockReader;
|
||||
class BlockBasedFilterBlockReader;
|
||||
@@ -123,15 +122,13 @@ class BlockBasedTable : public TableReader {
|
||||
// The result of NewIterator() is initially invalid (caller must
|
||||
// call one of the Seek methods on the iterator before using it).
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
InternalIterator* NewIterator(
|
||||
const ReadOptions&, const SliceTransform* prefix_extractor,
|
||||
Arena* arena = nullptr, bool skip_filters = false,
|
||||
// TODO(haoyu) 1. External SST ingestion sets for_compaction as false. 2.
|
||||
// Compaction also sets it to false when paranoid_file_checks is true,
|
||||
// i.e., it will populate the block cache with blocks in the new SST
|
||||
// files. We treat those as a user is calling iterator for now. We should
|
||||
// differentiate the callers.
|
||||
bool for_compaction = false) override;
|
||||
// compaction_readahead_size: its value will only be used if caller =
|
||||
// kCompaction.
|
||||
InternalIterator* NewIterator(const ReadOptions&,
|
||||
const SliceTransform* prefix_extractor,
|
||||
Arena* arena, bool skip_filters,
|
||||
TableReaderCaller caller,
|
||||
size_t compaction_readahead_size = 0) override;
|
||||
|
||||
FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator(
|
||||
const ReadOptions& read_options) override;
|
||||
@@ -157,7 +154,8 @@ class BlockBasedTable : public TableReader {
|
||||
// bytes, and so includes effects like compression of the underlying data.
|
||||
// E.g., the approximate offset of the last key in the table will
|
||||
// be close to the file length.
|
||||
uint64_t ApproximateOffsetOf(const Slice& key, bool for_compaction) override;
|
||||
uint64_t ApproximateOffsetOf(const Slice& key,
|
||||
TableReaderCaller caller) override;
|
||||
|
||||
bool TEST_BlockInCache(const BlockHandle& handle) const;
|
||||
|
||||
@@ -174,16 +172,15 @@ class BlockBasedTable : public TableReader {
|
||||
size_t ApproximateMemoryUsage() const override;
|
||||
|
||||
// convert SST file to a human readable form
|
||||
Status DumpTable(WritableFile* out_file,
|
||||
const SliceTransform* prefix_extractor = nullptr) override;
|
||||
Status DumpTable(WritableFile* out_file) override;
|
||||
|
||||
Status VerifyChecksum() override;
|
||||
Status VerifyChecksum(TableReaderCaller caller) override;
|
||||
|
||||
void Close() override;
|
||||
|
||||
~BlockBasedTable();
|
||||
|
||||
bool TEST_filter_block_preloaded() const;
|
||||
bool TEST_FilterBlockInCache() const;
|
||||
bool TEST_IndexBlockInCache() const;
|
||||
|
||||
// IndexReader is the interface that provides the functionality for index
|
||||
@@ -199,7 +196,7 @@ class BlockBasedTable : public TableReader {
|
||||
// wraps the passed iter. In the latter case the return value points
|
||||
// to a different object then iter, and the callee has the ownership of the
|
||||
// returned object.
|
||||
virtual InternalIteratorBase<BlockHandle>* NewIterator(
|
||||
virtual InternalIteratorBase<IndexValue>* NewIterator(
|
||||
const ReadOptions& read_options, bool disable_prefix_seek,
|
||||
IndexBlockIter* iter, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) = 0;
|
||||
@@ -231,10 +228,9 @@ class BlockBasedTable : public TableReader {
|
||||
template <typename TBlockIter>
|
||||
TBlockIter* NewDataBlockIterator(
|
||||
const ReadOptions& ro, const BlockHandle& block_handle,
|
||||
TBlockIter* input_iter, BlockType block_type, bool key_includes_seq,
|
||||
bool index_key_is_full, GetContext* get_context,
|
||||
TBlockIter* input_iter, BlockType block_type, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context, Status s,
|
||||
FilePrefetchBuffer* prefetch_buffer) const;
|
||||
FilePrefetchBuffer* prefetch_buffer, bool for_compaction = false) const;
|
||||
|
||||
// input_iter: if it is not null, update this one and return it as Iterator
|
||||
template <typename TBlockIter>
|
||||
@@ -244,6 +240,8 @@ class BlockBasedTable : public TableReader {
|
||||
|
||||
class PartitionedIndexIteratorState;
|
||||
|
||||
template <typename TBlocklike>
|
||||
friend class FilterBlockReaderCommon;
|
||||
friend class PartitionIndexReader;
|
||||
|
||||
protected:
|
||||
@@ -266,6 +264,12 @@ class BlockBasedTable : public TableReader {
|
||||
BlockType block_type,
|
||||
GetContext* get_context) const;
|
||||
|
||||
// Either Block::NewDataIterator() or Block::NewIndexIterator().
|
||||
template <typename TBlockIter>
|
||||
static TBlockIter* InitBlockIterator(const Rep* rep, Block* block,
|
||||
TBlockIter* input_iter,
|
||||
bool block_contents_pinned);
|
||||
|
||||
// If block cache enabled (compressed or uncompressed), looks for the block
|
||||
// identified by handle in (1) uncompressed cache, (2) compressed cache, and
|
||||
// then (3) file. If found, inserts into the cache(s) that were searched
|
||||
@@ -275,23 +279,25 @@ class BlockBasedTable : public TableReader {
|
||||
// @param block_entry value is set to the uncompressed block if found. If
|
||||
// in uncompressed block cache, also sets cache_handle to reference that
|
||||
// block.
|
||||
template <typename TBlocklike>
|
||||
Status MaybeReadBlockAndLoadToCache(
|
||||
FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
|
||||
const BlockHandle& handle, const UncompressionDict& uncompression_dict,
|
||||
CachableEntry<Block>* block_entry, BlockType block_type,
|
||||
CachableEntry<TBlocklike>* block_entry, BlockType block_type,
|
||||
GetContext* get_context, BlockCacheLookupContext* lookup_context,
|
||||
BlockContents* contents) const;
|
||||
|
||||
// Similar to the above, with one crucial difference: it will retrieve the
|
||||
// block from the file even if there are no caches configured (assuming the
|
||||
// read options allow I/O).
|
||||
template <typename TBlocklike>
|
||||
Status RetrieveBlock(FilePrefetchBuffer* prefetch_buffer,
|
||||
const ReadOptions& ro, const BlockHandle& handle,
|
||||
const UncompressionDict& uncompression_dict,
|
||||
CachableEntry<Block>* block_entry, BlockType block_type,
|
||||
GetContext* get_context,
|
||||
CachableEntry<TBlocklike>* block_entry,
|
||||
BlockType block_type, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
bool use_cache) const;
|
||||
bool for_compaction = false) const;
|
||||
|
||||
Status GetDataBlockFromCache(
|
||||
const ReadOptions& ro, const BlockHandle& handle,
|
||||
@@ -307,19 +313,6 @@ class BlockBasedTable : public TableReader {
|
||||
CachableEntry<Block>, MultiGetContext::MAX_BATCH_SIZE>* results,
|
||||
char* scratch, const UncompressionDict& uncompression_dict) const;
|
||||
|
||||
// For the following two functions:
|
||||
// if `no_io == true`, we will not try to read filter/index from sst file
|
||||
// were they not present in cache yet.
|
||||
CachableEntry<FilterBlockReader> GetFilter(
|
||||
const SliceTransform* prefix_extractor,
|
||||
FilePrefetchBuffer* prefetch_buffer, bool no_io, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) const;
|
||||
virtual CachableEntry<FilterBlockReader> GetFilter(
|
||||
FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_blk_handle,
|
||||
const bool is_a_filter_partition, bool no_io, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
const SliceTransform* prefix_extractor) const;
|
||||
|
||||
CachableEntry<UncompressionDict> GetUncompressionDict(
|
||||
FilePrefetchBuffer* prefetch_buffer, bool no_io, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) const;
|
||||
@@ -334,7 +327,7 @@ class BlockBasedTable : public TableReader {
|
||||
// 2. index is not present in block cache.
|
||||
// 3. We disallowed any io to be performed, that is, read_options ==
|
||||
// kBlockCacheTier
|
||||
InternalIteratorBase<BlockHandle>* NewIndexIterator(
|
||||
InternalIteratorBase<IndexValue>* NewIndexIterator(
|
||||
const ReadOptions& read_options, bool need_upper_bound_check,
|
||||
IndexBlockIter* input_iter, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) const;
|
||||
@@ -345,12 +338,13 @@ class BlockBasedTable : public TableReader {
|
||||
// pointer to the block as well as its block handle.
|
||||
// @param uncompression_dict Data for presetting the compression library's
|
||||
// dictionary.
|
||||
template <typename TBlocklike>
|
||||
Status GetDataBlockFromCache(
|
||||
const Slice& block_cache_key, const Slice& compressed_block_cache_key,
|
||||
Cache* block_cache, Cache* block_cache_compressed,
|
||||
const ReadOptions& read_options, CachableEntry<Block>* block,
|
||||
const ReadOptions& read_options, CachableEntry<TBlocklike>* block,
|
||||
const UncompressionDict& uncompression_dict, BlockType block_type,
|
||||
GetContext* get_context = nullptr) const;
|
||||
GetContext* get_context) const;
|
||||
|
||||
// Put a raw block (maybe compressed) to the corresponding block caches.
|
||||
// This method will perform decompression against raw_block if needed and then
|
||||
@@ -362,11 +356,12 @@ class BlockBasedTable : public TableReader {
|
||||
// PutDataBlockToCache(). After the call, the object will be invalid.
|
||||
// @param uncompression_dict Data for presetting the compression library's
|
||||
// dictionary.
|
||||
template <typename TBlocklike>
|
||||
Status PutDataBlockToCache(
|
||||
const Slice& block_cache_key, const Slice& compressed_block_cache_key,
|
||||
Cache* block_cache, Cache* block_cache_compressed,
|
||||
CachableEntry<Block>* cached_block, BlockContents* raw_block_contents,
|
||||
CompressionType raw_block_comp_type,
|
||||
CachableEntry<TBlocklike>* cached_block,
|
||||
BlockContents* raw_block_contents, CompressionType raw_block_comp_type,
|
||||
const UncompressionDict& uncompression_dict, SequenceNumber seq_no,
|
||||
MemoryAllocator* memory_allocator, BlockType block_type,
|
||||
GetContext* get_context) const;
|
||||
@@ -377,9 +372,6 @@ class BlockBasedTable : public TableReader {
|
||||
friend class TableCache;
|
||||
friend class BlockBasedTableBuilder;
|
||||
|
||||
// Figure the index type, update it in rep_, and also return it.
|
||||
BlockBasedTableOptions::IndexType UpdateIndexType();
|
||||
|
||||
// Create a index reader based on the index type stored in the table.
|
||||
// Optionally, user can pass a preloaded meta_index_iter for the index that
|
||||
// need to access extra meta blocks for index construction. This parameter
|
||||
@@ -387,13 +379,14 @@ class BlockBasedTable : public TableReader {
|
||||
Status CreateIndexReader(FilePrefetchBuffer* prefetch_buffer,
|
||||
InternalIterator* preloaded_meta_index_iter,
|
||||
bool use_cache, bool prefetch, bool pin,
|
||||
IndexReader** index_reader,
|
||||
BlockCacheLookupContext* lookup_context);
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
std::unique_ptr<IndexReader>* index_reader);
|
||||
|
||||
bool FullFilterKeyMayMatch(const ReadOptions& read_options,
|
||||
FilterBlockReader* filter, const Slice& user_key,
|
||||
const bool no_io,
|
||||
const SliceTransform* prefix_extractor,
|
||||
GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) const;
|
||||
|
||||
void FullFilterKeysMayMatch(const ReadOptions& read_options,
|
||||
@@ -432,13 +425,12 @@ class BlockBasedTable : public TableReader {
|
||||
static BlockType GetBlockTypeForMetaBlockByName(const Slice& meta_block_name);
|
||||
|
||||
Status VerifyChecksumInMetaBlocks(InternalIteratorBase<Slice>* index_iter);
|
||||
Status VerifyChecksumInBlocks(InternalIteratorBase<BlockHandle>* index_iter);
|
||||
Status VerifyChecksumInBlocks(InternalIteratorBase<IndexValue>* index_iter);
|
||||
|
||||
// Create the filter from the filter block.
|
||||
virtual FilterBlockReader* ReadFilter(
|
||||
FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_handle,
|
||||
const bool is_a_filter_partition,
|
||||
const SliceTransform* prefix_extractor = nullptr) const;
|
||||
std::unique_ptr<FilterBlockReader> CreateFilterBlockReader(
|
||||
FilePrefetchBuffer* prefetch_buffer, bool use_cache, bool prefetch,
|
||||
bool pin, BlockCacheLookupContext* lookup_context);
|
||||
|
||||
static void SetupCacheKeyPrefix(Rep* rep);
|
||||
|
||||
@@ -468,17 +460,14 @@ class BlockBasedTable::PartitionedIndexIteratorState
|
||||
public:
|
||||
PartitionedIndexIteratorState(
|
||||
const BlockBasedTable* table,
|
||||
std::unordered_map<uint64_t, CachableEntry<Block>>* block_map,
|
||||
const bool index_key_includes_seq, const bool index_key_is_full);
|
||||
InternalIteratorBase<BlockHandle>* NewSecondaryIterator(
|
||||
std::unordered_map<uint64_t, CachableEntry<Block>>* block_map);
|
||||
InternalIteratorBase<IndexValue>* NewSecondaryIterator(
|
||||
const BlockHandle& index_value) override;
|
||||
|
||||
private:
|
||||
// Don't own table_
|
||||
const BlockBasedTable* table_;
|
||||
std::unordered_map<uint64_t, CachableEntry<Block>>* block_map_;
|
||||
bool index_key_includes_seq_;
|
||||
bool index_key_is_full_;
|
||||
};
|
||||
|
||||
// Stores all the properties associated with a BlockBasedTable.
|
||||
@@ -519,17 +508,7 @@ struct BlockBasedTable::Rep {
|
||||
|
||||
// Footer contains the fixed table information
|
||||
Footer footer;
|
||||
// `filter` and `uncompression_dict` will be populated (i.e., non-nullptr)
|
||||
// and used only when options.block_cache is nullptr or when
|
||||
// `cache_index_and_filter_blocks == false`. Otherwise, we will get the
|
||||
// filter and compression dictionary blocks via the block cache. In that case,
|
||||
// `filter_handle`, and `compression_dict_handle` are used to lookup these
|
||||
// meta-blocks in block cache.
|
||||
//
|
||||
// Note: the IndexReader object is always stored in this member variable;
|
||||
// the index block itself, however, may or may not be in the block cache
|
||||
// based on the settings above. We plan to change the handling of the
|
||||
// filter and compression dictionary similarly.
|
||||
|
||||
std::unique_ptr<IndexReader> index_reader;
|
||||
std::unique_ptr<FilterBlockReader> filter;
|
||||
std::unique_ptr<UncompressionDict> uncompression_dict;
|
||||
@@ -556,13 +535,6 @@ struct BlockBasedTable::Rep {
|
||||
std::unique_ptr<SliceTransform> internal_prefix_transform;
|
||||
std::shared_ptr<const SliceTransform> table_prefix_extractor;
|
||||
|
||||
// only used in level 0 files when pin_l0_filter_and_index_blocks_in_cache is
|
||||
// true or in all levels when pin_top_level_index_and_filter is set in
|
||||
// combination with partitioned filters: then we do use the LRU cache,
|
||||
// but we always keep the filter block's handle checked out here (=we
|
||||
// don't call Release()), plus the parsed out objects the LRU cache will never
|
||||
// push flush them out, hence they're pinned
|
||||
CachableEntry<FilterBlockReader> filter_entry;
|
||||
std::shared_ptr<const FragmentedRangeTombstoneList> fragmented_range_dels;
|
||||
|
||||
// If global_seqno is used, all Keys in this file will have the same
|
||||
@@ -586,12 +558,16 @@ struct BlockBasedTable::Rep {
|
||||
// still work, just not as quickly.
|
||||
bool blocks_definitely_zstd_compressed = false;
|
||||
|
||||
// These describe how index is encoded.
|
||||
bool index_has_first_key = false;
|
||||
bool index_key_includes_seq = true;
|
||||
bool index_value_is_full = true;
|
||||
|
||||
bool closed = false;
|
||||
const bool immortal_table;
|
||||
|
||||
SequenceNumber get_global_seqno(BlockType block_type) const {
|
||||
return (block_type == BlockType::kFilter ||
|
||||
block_type == BlockType::kIndex ||
|
||||
block_type == BlockType::kCompressionDictionary)
|
||||
? kDisableGlobalSequenceNumber
|
||||
: global_seqno;
|
||||
@@ -618,17 +594,19 @@ struct BlockBasedTable::Rep {
|
||||
// Iterates over the contents of BlockBasedTable.
|
||||
template <class TBlockIter, typename TValue = Slice>
|
||||
class BlockBasedTableIterator : public InternalIteratorBase<TValue> {
|
||||
// compaction_readahead_size: its value will only be used if for_compaction =
|
||||
// true
|
||||
public:
|
||||
BlockBasedTableIterator(const BlockBasedTable* table,
|
||||
const ReadOptions& read_options,
|
||||
const InternalKeyComparator& icomp,
|
||||
InternalIteratorBase<BlockHandle>* index_iter,
|
||||
InternalIteratorBase<IndexValue>* index_iter,
|
||||
bool check_filter, bool need_upper_bound_check,
|
||||
const SliceTransform* prefix_extractor,
|
||||
BlockType block_type, bool key_includes_seq = true,
|
||||
bool index_key_is_full = true,
|
||||
bool for_compaction = false)
|
||||
: table_(table),
|
||||
BlockType block_type, TableReaderCaller caller,
|
||||
size_t compaction_readahead_size = 0)
|
||||
: InternalIteratorBase<TValue>(false),
|
||||
table_(table),
|
||||
read_options_(read_options),
|
||||
icomp_(icomp),
|
||||
user_comparator_(icomp.user_comparator()),
|
||||
@@ -639,12 +617,8 @@ class BlockBasedTableIterator : public InternalIteratorBase<TValue> {
|
||||
need_upper_bound_check_(need_upper_bound_check),
|
||||
prefix_extractor_(prefix_extractor),
|
||||
block_type_(block_type),
|
||||
key_includes_seq_(key_includes_seq),
|
||||
index_key_is_full_(index_key_is_full),
|
||||
for_compaction_(for_compaction),
|
||||
lookup_context_(for_compaction
|
||||
? BlockCacheLookupCaller::kCompaction
|
||||
: BlockCacheLookupCaller::kUserIterator) {}
|
||||
lookup_context_(caller),
|
||||
compaction_readahead_size_(compaction_readahead_size) {}
|
||||
|
||||
~BlockBasedTableIterator() { delete index_iter_; }
|
||||
|
||||
@@ -653,22 +627,41 @@ class BlockBasedTableIterator : public InternalIteratorBase<TValue> {
|
||||
void SeekToFirst() override;
|
||||
void SeekToLast() override;
|
||||
void Next() final override;
|
||||
bool NextAndGetResult(Slice* ret_key) override;
|
||||
bool NextAndGetResult(IterateResult* result) override;
|
||||
void Prev() override;
|
||||
bool Valid() const override {
|
||||
return !is_out_of_bound_ && block_iter_points_to_real_block_ &&
|
||||
block_iter_.Valid();
|
||||
return !is_out_of_bound_ &&
|
||||
(is_at_first_key_from_index_ ||
|
||||
(block_iter_points_to_real_block_ && block_iter_.Valid()));
|
||||
}
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return block_iter_.key();
|
||||
if (is_at_first_key_from_index_) {
|
||||
return index_iter_->value().first_internal_key;
|
||||
} else {
|
||||
return block_iter_.key();
|
||||
}
|
||||
}
|
||||
Slice user_key() const override {
|
||||
assert(Valid());
|
||||
return block_iter_.user_key();
|
||||
if (is_at_first_key_from_index_) {
|
||||
return ExtractUserKey(index_iter_->value().first_internal_key);
|
||||
} else {
|
||||
return block_iter_.user_key();
|
||||
}
|
||||
}
|
||||
TValue value() const override {
|
||||
assert(Valid());
|
||||
|
||||
// Load current block if not loaded.
|
||||
if (is_at_first_key_from_index_ &&
|
||||
!const_cast<BlockBasedTableIterator*>(this)
|
||||
->MaterializeCurrentBlock()) {
|
||||
// Oops, index is not consistent with block contents, but we have
|
||||
// no good way to report error at this point. Let's return empty value.
|
||||
return TValue();
|
||||
}
|
||||
|
||||
return block_iter_.value();
|
||||
}
|
||||
Status status() const override {
|
||||
@@ -684,14 +677,26 @@ class BlockBasedTableIterator : public InternalIteratorBase<TValue> {
|
||||
// Whether iterator invalidated for being out of bound.
|
||||
bool IsOutOfBound() override { return is_out_of_bound_; }
|
||||
|
||||
inline bool MayBeOutOfUpperBound() override {
|
||||
assert(Valid());
|
||||
return !data_block_within_upper_bound_;
|
||||
}
|
||||
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
pinned_iters_mgr_ = pinned_iters_mgr;
|
||||
}
|
||||
bool IsKeyPinned() const override {
|
||||
// Our key comes either from block_iter_'s current key
|
||||
// or index_iter_'s current *value*.
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
block_iter_points_to_real_block_ && block_iter_.IsKeyPinned();
|
||||
((is_at_first_key_from_index_ && index_iter_->IsValuePinned()) ||
|
||||
(block_iter_points_to_real_block_ && block_iter_.IsKeyPinned()));
|
||||
}
|
||||
bool IsValuePinned() const override {
|
||||
// Load current block if not loaded.
|
||||
if (is_at_first_key_from_index_) {
|
||||
const_cast<BlockBasedTableIterator*>(this)->MaterializeCurrentBlock();
|
||||
}
|
||||
// BlockIter::IsValuePinned() is always true. No need to check
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
block_iter_points_to_real_block_;
|
||||
@@ -725,38 +730,39 @@ class BlockBasedTableIterator : public InternalIteratorBase<TValue> {
|
||||
if (block_iter_points_to_real_block_) {
|
||||
// Reseek. If they end up with the same data block, we shouldn't re-fetch
|
||||
// the same data block.
|
||||
prev_index_value_ = index_iter_->value();
|
||||
prev_block_offset_ = index_iter_->value().handle.offset();
|
||||
}
|
||||
}
|
||||
|
||||
void InitDataBlock();
|
||||
inline void FindKeyForward();
|
||||
void FindBlockForward();
|
||||
void FindKeyBackward();
|
||||
void CheckOutOfBound();
|
||||
|
||||
private:
|
||||
const BlockBasedTable* table_;
|
||||
const ReadOptions read_options_;
|
||||
const InternalKeyComparator& icomp_;
|
||||
UserComparatorWrapper user_comparator_;
|
||||
InternalIteratorBase<BlockHandle>* index_iter_;
|
||||
InternalIteratorBase<IndexValue>* index_iter_;
|
||||
PinnedIteratorsManager* pinned_iters_mgr_;
|
||||
TBlockIter block_iter_;
|
||||
|
||||
// True if block_iter_ is initialized and points to the same block
|
||||
// as index iterator.
|
||||
bool block_iter_points_to_real_block_;
|
||||
// See InternalIteratorBase::IsOutOfBound().
|
||||
bool is_out_of_bound_ = false;
|
||||
// Whether current data block being fully within iterate upper bound.
|
||||
bool data_block_within_upper_bound_ = false;
|
||||
// True if we're standing at the first key of a block, and we haven't loaded
|
||||
// that block yet. A call to value() will trigger loading the block.
|
||||
bool is_at_first_key_from_index_ = false;
|
||||
bool check_filter_;
|
||||
// TODO(Zhongyi): pick a better name
|
||||
bool need_upper_bound_check_;
|
||||
const SliceTransform* prefix_extractor_;
|
||||
BlockType block_type_;
|
||||
// If the keys in the blocks over which we iterate include 8 byte sequence
|
||||
bool key_includes_seq_;
|
||||
bool index_key_is_full_;
|
||||
// If this iterator is created for compaction
|
||||
bool for_compaction_;
|
||||
BlockHandle prev_index_value_;
|
||||
uint64_t prev_block_offset_ = std::numeric_limits<uint64_t>::max();
|
||||
BlockCacheLookupContext lookup_context_;
|
||||
// Readahead size used in compaction, its value is used only if
|
||||
// lookup_context_.caller = kCompaction.
|
||||
size_t compaction_readahead_size_;
|
||||
|
||||
// All the below fields control iterator readahead
|
||||
static const size_t kInitAutoReadaheadSize = 8 * 1024;
|
||||
@@ -768,6 +774,22 @@ class BlockBasedTableIterator : public InternalIteratorBase<TValue> {
|
||||
size_t readahead_limit_ = 0;
|
||||
int64_t num_file_reads_ = 0;
|
||||
std::unique_ptr<FilePrefetchBuffer> prefetch_buffer_;
|
||||
|
||||
// If `target` is null, seek to first.
|
||||
void SeekImpl(const Slice* target);
|
||||
|
||||
void InitDataBlock();
|
||||
bool MaterializeCurrentBlock();
|
||||
void FindKeyForward();
|
||||
void FindBlockForward();
|
||||
void FindKeyBackward();
|
||||
void CheckOutOfBound();
|
||||
|
||||
// Check if data block is fully within iterate_upper_bound.
|
||||
//
|
||||
// Note MyRocks may update iterate bounds between seek. To workaround it,
|
||||
// we need to check and update data_block_within_upper_bound_ accordingly.
|
||||
void CheckDataBlockWithinUpperBound();
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+135
-118
@@ -69,37 +69,12 @@ void GenerateRandomKVs(std::vector<std::string> *keys,
|
||||
}
|
||||
}
|
||||
|
||||
// Same as GenerateRandomKVs but the values are BlockHandle
|
||||
void GenerateRandomKBHs(std::vector<std::string> *keys,
|
||||
std::vector<BlockHandle> *values, const int from,
|
||||
const int len, const int step = 1,
|
||||
const int padding_size = 0,
|
||||
const int keys_share_prefix = 1) {
|
||||
Random rnd(302);
|
||||
uint64_t offset = 0;
|
||||
|
||||
// generate different prefix
|
||||
for (int i = from; i < from + len; i += step) {
|
||||
// generate keys that shares the prefix
|
||||
for (int j = 0; j < keys_share_prefix; ++j) {
|
||||
keys->emplace_back(GenerateKey(i, j, padding_size, &rnd));
|
||||
|
||||
uint64_t size = rnd.Uniform(1024 * 16);
|
||||
BlockHandle handle(offset, size);
|
||||
offset += size + kBlockTrailerSize;
|
||||
values->emplace_back(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BlockTest : public testing::Test {};
|
||||
|
||||
// block test
|
||||
TEST_F(BlockTest, SimpleTest) {
|
||||
Random rnd(301);
|
||||
Options options = Options();
|
||||
std::unique_ptr<InternalKeyComparator> ic;
|
||||
ic.reset(new test::PlainInternalKeyComparator(options.comparator));
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
@@ -123,7 +98,7 @@ TEST_F(BlockTest, SimpleTest) {
|
||||
// read contents of block sequentially
|
||||
int count = 0;
|
||||
InternalIterator *iter =
|
||||
reader.NewIterator<DataBlockIter>(options.comparator, options.comparator);
|
||||
reader.NewDataIterator(options.comparator, options.comparator);
|
||||
for (iter->SeekToFirst(); iter->Valid(); count++, iter->Next()) {
|
||||
// read kv from block
|
||||
Slice k = iter->key();
|
||||
@@ -136,8 +111,7 @@ TEST_F(BlockTest, SimpleTest) {
|
||||
delete iter;
|
||||
|
||||
// read block contents randomly
|
||||
iter =
|
||||
reader.NewIterator<DataBlockIter>(options.comparator, options.comparator);
|
||||
iter = reader.NewDataIterator(options.comparator, options.comparator);
|
||||
for (int i = 0; i < num_records; i++) {
|
||||
// find a random key in the lookaside array
|
||||
int index = rnd.Uniform(num_records);
|
||||
@@ -152,83 +126,6 @@ TEST_F(BlockTest, SimpleTest) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(BlockTest, ValueDeltaEncodingTest) {
|
||||
Random rnd(301);
|
||||
Options options = Options();
|
||||
std::unique_ptr<InternalKeyComparator> ic;
|
||||
ic.reset(new test::PlainInternalKeyComparator(options.comparator));
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<BlockHandle> values;
|
||||
const bool kUseDeltaEncoding = true;
|
||||
const bool kUseValueDeltaEncoding = true;
|
||||
BlockBuilder builder(16, kUseDeltaEncoding, kUseValueDeltaEncoding);
|
||||
int num_records = 100;
|
||||
|
||||
GenerateRandomKBHs(&keys, &values, 0, num_records);
|
||||
// add a bunch of records to a block
|
||||
BlockHandle last_encoded_handle;
|
||||
for (int i = 0; i < num_records; i++) {
|
||||
auto block_handle = values[i];
|
||||
std::string handle_encoding;
|
||||
block_handle.EncodeTo(&handle_encoding);
|
||||
std::string handle_delta_encoding;
|
||||
PutVarsignedint64(&handle_delta_encoding,
|
||||
block_handle.size() - last_encoded_handle.size());
|
||||
last_encoded_handle = block_handle;
|
||||
const Slice handle_delta_encoding_slice(handle_delta_encoding);
|
||||
builder.Add(keys[i], handle_encoding, &handle_delta_encoding_slice);
|
||||
}
|
||||
|
||||
// read serialized contents of the block
|
||||
Slice rawblock = builder.Finish();
|
||||
|
||||
// create block reader
|
||||
BlockContents contents;
|
||||
contents.data = rawblock;
|
||||
Block reader(std::move(contents), kDisableGlobalSequenceNumber);
|
||||
|
||||
const bool kTotalOrderSeek = true;
|
||||
const bool kIncludesSeq = true;
|
||||
const bool kValueIsFull = !kUseValueDeltaEncoding;
|
||||
IndexBlockIter *kNullIter = nullptr;
|
||||
Statistics *kNullStats = nullptr;
|
||||
// read contents of block sequentially
|
||||
int count = 0;
|
||||
InternalIteratorBase<BlockHandle> *iter = reader.NewIterator<IndexBlockIter>(
|
||||
options.comparator, options.comparator, kNullIter, kNullStats,
|
||||
kTotalOrderSeek, kIncludesSeq, kValueIsFull);
|
||||
for (iter->SeekToFirst(); iter->Valid(); count++, iter->Next()) {
|
||||
// read kv from block
|
||||
Slice k = iter->key();
|
||||
BlockHandle handle = iter->value();
|
||||
|
||||
// compare with lookaside array
|
||||
ASSERT_EQ(k.ToString().compare(keys[count]), 0);
|
||||
|
||||
ASSERT_EQ(values[count].offset(), handle.offset());
|
||||
ASSERT_EQ(values[count].size(), handle.size());
|
||||
}
|
||||
delete iter;
|
||||
|
||||
// read block contents randomly
|
||||
iter = reader.NewIterator<IndexBlockIter>(
|
||||
options.comparator, options.comparator, kNullIter, kNullStats,
|
||||
kTotalOrderSeek, kIncludesSeq, kValueIsFull);
|
||||
for (int i = 0; i < num_records; i++) {
|
||||
// find a random key in the lookaside array
|
||||
int index = rnd.Uniform(num_records);
|
||||
Slice k(keys[index]);
|
||||
|
||||
// search in block for this key
|
||||
iter->Seek(k);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
BlockHandle handle = iter->value();
|
||||
ASSERT_EQ(values[index].offset(), handle.offset());
|
||||
ASSERT_EQ(values[index].size(), handle.size());
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
// return the block contents
|
||||
BlockContents GetBlockContents(std::unique_ptr<BlockBuilder> *builder,
|
||||
const std::vector<std::string> &keys,
|
||||
@@ -261,8 +158,7 @@ void CheckBlockContents(BlockContents contents, const int max_key,
|
||||
NewFixedPrefixTransform(prefix_size));
|
||||
|
||||
std::unique_ptr<InternalIterator> regular_iter(
|
||||
reader2.NewIterator<DataBlockIter>(BytewiseComparator(),
|
||||
BytewiseComparator()));
|
||||
reader2.NewDataIterator(BytewiseComparator(), BytewiseComparator()));
|
||||
|
||||
// Seek existent keys
|
||||
for (size_t i = 0; i < keys.size(); i++) {
|
||||
@@ -457,8 +353,6 @@ TEST_F(BlockTest, BlockReadAmpBitmap) {
|
||||
TEST_F(BlockTest, BlockWithReadAmpBitmap) {
|
||||
Random rnd(301);
|
||||
Options options = Options();
|
||||
std::unique_ptr<InternalKeyComparator> ic;
|
||||
ic.reset(new test::PlainInternalKeyComparator(options.comparator));
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
@@ -486,9 +380,8 @@ TEST_F(BlockTest, BlockWithReadAmpBitmap) {
|
||||
|
||||
// read contents of block sequentially
|
||||
size_t read_bytes = 0;
|
||||
DataBlockIter *iter =
|
||||
static_cast<DataBlockIter *>(reader.NewIterator<DataBlockIter>(
|
||||
options.comparator, options.comparator, nullptr, stats.get()));
|
||||
DataBlockIter *iter = reader.NewDataIterator(
|
||||
options.comparator, options.comparator, nullptr, stats.get());
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
iter->value();
|
||||
read_bytes += iter->TEST_CurrentEntrySize();
|
||||
@@ -519,9 +412,8 @@ TEST_F(BlockTest, BlockWithReadAmpBitmap) {
|
||||
kBytesPerBit, stats.get());
|
||||
|
||||
size_t read_bytes = 0;
|
||||
DataBlockIter *iter =
|
||||
static_cast<DataBlockIter *>(reader.NewIterator<DataBlockIter>(
|
||||
options.comparator, options.comparator, nullptr, stats.get()));
|
||||
DataBlockIter *iter = reader.NewDataIterator(
|
||||
options.comparator, options.comparator, nullptr, stats.get());
|
||||
for (int i = 0; i < num_records; i++) {
|
||||
Slice k(keys[i]);
|
||||
|
||||
@@ -555,9 +447,8 @@ TEST_F(BlockTest, BlockWithReadAmpBitmap) {
|
||||
kBytesPerBit, stats.get());
|
||||
|
||||
size_t read_bytes = 0;
|
||||
DataBlockIter *iter =
|
||||
static_cast<DataBlockIter *>(reader.NewIterator<DataBlockIter>(
|
||||
options.comparator, options.comparator, nullptr, stats.get()));
|
||||
DataBlockIter *iter = reader.NewDataIterator(
|
||||
options.comparator, options.comparator, nullptr, stats.get());
|
||||
std::unordered_set<int> read_keys;
|
||||
for (int i = 0; i < num_records; i++) {
|
||||
int index = rnd.Uniform(num_records);
|
||||
@@ -602,6 +493,132 @@ TEST_F(BlockTest, ReadAmpBitmapPow2) {
|
||||
ASSERT_EQ(BlockReadAmpBitmap(100, 35, stats.get()).GetBytesPerBit(), 32);
|
||||
}
|
||||
|
||||
class IndexBlockTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
IndexBlockTest() = default;
|
||||
|
||||
bool useValueDeltaEncoding() const { return std::get<0>(GetParam()); }
|
||||
bool includeFirstKey() const { return std::get<1>(GetParam()); }
|
||||
};
|
||||
|
||||
// Similar to GenerateRandomKVs but for index block contents.
|
||||
void GenerateRandomIndexEntries(std::vector<std::string> *separators,
|
||||
std::vector<BlockHandle> *block_handles,
|
||||
std::vector<std::string> *first_keys,
|
||||
const int len) {
|
||||
Random rnd(42);
|
||||
|
||||
// For each of `len` blocks, we need to generate a first and last key.
|
||||
// Let's generate n*2 random keys, sort them, group into consecutive pairs.
|
||||
std::set<std::string> keys;
|
||||
while ((int)keys.size() < len * 2) {
|
||||
// Keys need to be at least 8 bytes long to look like internal keys.
|
||||
keys.insert(test::RandomKey(&rnd, 12));
|
||||
}
|
||||
|
||||
uint64_t offset = 0;
|
||||
for (auto it = keys.begin(); it != keys.end();) {
|
||||
first_keys->emplace_back(*it++);
|
||||
separators->emplace_back(*it++);
|
||||
uint64_t size = rnd.Uniform(1024 * 16);
|
||||
BlockHandle handle(offset, size);
|
||||
offset += size + kBlockTrailerSize;
|
||||
block_handles->emplace_back(handle);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(IndexBlockTest, IndexValueEncodingTest) {
|
||||
Random rnd(301);
|
||||
Options options = Options();
|
||||
|
||||
std::vector<std::string> separators;
|
||||
std::vector<BlockHandle> block_handles;
|
||||
std::vector<std::string> first_keys;
|
||||
const bool kUseDeltaEncoding = true;
|
||||
BlockBuilder builder(16, kUseDeltaEncoding, useValueDeltaEncoding());
|
||||
int num_records = 100;
|
||||
|
||||
GenerateRandomIndexEntries(&separators, &block_handles, &first_keys,
|
||||
num_records);
|
||||
BlockHandle last_encoded_handle;
|
||||
for (int i = 0; i < num_records; i++) {
|
||||
IndexValue entry(block_handles[i], first_keys[i]);
|
||||
std::string encoded_entry;
|
||||
std::string delta_encoded_entry;
|
||||
entry.EncodeTo(&encoded_entry, includeFirstKey(), nullptr);
|
||||
if (useValueDeltaEncoding() && i > 0) {
|
||||
entry.EncodeTo(&delta_encoded_entry, includeFirstKey(),
|
||||
&last_encoded_handle);
|
||||
}
|
||||
last_encoded_handle = entry.handle;
|
||||
const Slice delta_encoded_entry_slice(delta_encoded_entry);
|
||||
builder.Add(separators[i], encoded_entry, &delta_encoded_entry_slice);
|
||||
}
|
||||
|
||||
// read serialized contents of the block
|
||||
Slice rawblock = builder.Finish();
|
||||
|
||||
// create block reader
|
||||
BlockContents contents;
|
||||
contents.data = rawblock;
|
||||
Block reader(std::move(contents), kDisableGlobalSequenceNumber);
|
||||
|
||||
const bool kTotalOrderSeek = true;
|
||||
const bool kIncludesSeq = true;
|
||||
const bool kValueIsFull = !useValueDeltaEncoding();
|
||||
IndexBlockIter *kNullIter = nullptr;
|
||||
Statistics *kNullStats = nullptr;
|
||||
// read contents of block sequentially
|
||||
InternalIteratorBase<IndexValue> *iter = reader.NewIndexIterator(
|
||||
options.comparator, options.comparator, kNullIter, kNullStats,
|
||||
kTotalOrderSeek, includeFirstKey(), kIncludesSeq, kValueIsFull);
|
||||
iter->SeekToFirst();
|
||||
for (int index = 0; index < num_records; ++index) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
|
||||
Slice k = iter->key();
|
||||
IndexValue v = iter->value();
|
||||
|
||||
EXPECT_EQ(separators[index], k.ToString());
|
||||
EXPECT_EQ(block_handles[index].offset(), v.handle.offset());
|
||||
EXPECT_EQ(block_handles[index].size(), v.handle.size());
|
||||
EXPECT_EQ(includeFirstKey() ? first_keys[index] : "",
|
||||
v.first_internal_key.ToString());
|
||||
|
||||
iter->Next();
|
||||
}
|
||||
delete iter;
|
||||
|
||||
// read block contents randomly
|
||||
iter = reader.NewIndexIterator(options.comparator, options.comparator,
|
||||
kNullIter, kNullStats, kTotalOrderSeek,
|
||||
includeFirstKey(), kIncludesSeq, kValueIsFull);
|
||||
for (int i = 0; i < num_records * 2; i++) {
|
||||
// find a random key in the lookaside array
|
||||
int index = rnd.Uniform(num_records);
|
||||
Slice k(separators[index]);
|
||||
|
||||
// search in block for this key
|
||||
iter->Seek(k);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
IndexValue v = iter->value();
|
||||
EXPECT_EQ(separators[index], iter->key().ToString());
|
||||
EXPECT_EQ(block_handles[index].offset(), v.handle.offset());
|
||||
EXPECT_EQ(block_handles[index].size(), v.handle.size());
|
||||
EXPECT_EQ(includeFirstKey() ? first_keys[index] : "",
|
||||
v.first_internal_key.ToString());
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(P, IndexBlockTest,
|
||||
::testing::Values(std::make_tuple(false, false),
|
||||
std::make_tuple(false, true),
|
||||
std::make_tuple(true, false),
|
||||
std::make_tuple(true, true)));
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/cleanable.h"
|
||||
|
||||
|
||||
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user