mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e69ce1041f | ||
|
|
e3169e3ea8 | ||
|
|
62976c633e | ||
|
|
d46141e11d | ||
|
|
0eeeda89ff | ||
|
|
3c0bb7fdd4 | ||
|
|
e1eb14133a | ||
|
|
650acf64d6 | ||
|
|
172b9ec84c | ||
|
|
2407812292 | ||
|
|
d70f081aa7 | ||
|
|
b96d31b60f | ||
|
|
9840ed9eef | ||
|
|
7ff41fea6e | ||
|
|
a372c5b00f | ||
|
|
3a10ffe954 | ||
|
|
a28b2cf9ed | ||
|
|
1d65661837 | ||
|
|
fdc9b5248b | ||
|
|
d039c41b43 | ||
|
|
08d5a83499 | ||
|
|
ab3e6f6476 | ||
|
|
5042d04a96 | ||
|
|
7b1a12f182 | ||
|
|
ada6e87f5a | ||
|
|
4f3ed8622e | ||
|
|
222f84e2c3 | ||
|
|
b538e756c2 | ||
|
|
265db3ebb5 | ||
|
|
55f4f5486d | ||
|
|
849a8c0ae0 | ||
|
|
399f477818 | ||
|
|
e648c1d9eb | ||
|
|
80d7067cb2 | ||
|
|
3f89af1c39 | ||
|
|
70c7302fb5 | ||
|
|
41df734830 | ||
|
|
3617287e0e | ||
|
|
230b909da8 | ||
|
|
74782cec32 | ||
|
|
9625a2bc2b | ||
|
|
ae152ee666 | ||
|
|
0d16fad51b | ||
|
|
d9dc6b4637 | ||
|
|
7260347fd1 | ||
|
|
5daa426a18 | ||
|
|
66b524a911 | ||
|
|
f5b951f7b6 | ||
|
|
cfcf045acc | ||
|
|
092f417037 | ||
|
|
6b7fcc0d5f | ||
|
|
3782accf7d | ||
|
|
112702ac6c | ||
|
|
eae832740b | ||
|
|
327c4807a7 | ||
|
|
66b5613d0c | ||
|
|
3778470061 | ||
|
|
a78503bd6c | ||
|
|
0be1feec21 | ||
|
|
4f7ba3aaed | ||
|
|
c129c75fb7 | ||
|
|
6bb3b4b567 |
No files matched your search
@@ -24,6 +24,7 @@ make_config.mk
|
||||
*.vcxproj.filters
|
||||
*.sln
|
||||
*.cmake
|
||||
.watchmanconfig
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
build/
|
||||
@@ -33,6 +34,7 @@ manifest_dump
|
||||
sst_dump
|
||||
blob_dump
|
||||
block_cache_trace_analyzer
|
||||
tools/block_cache_analyzer/*.pyc
|
||||
column_aware_encoding_exp
|
||||
util/build_version.cc
|
||||
build_tools/VALGRIND_LOGS/
|
||||
@@ -48,6 +50,7 @@ rocksdb_undump
|
||||
db_test2
|
||||
trace_analyzer
|
||||
trace_analyzer_test
|
||||
block_cache_trace_analyzer
|
||||
.DS_Store
|
||||
|
||||
java/out
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"content_hash_warming": true,
|
||||
"content_hash_max_items": 333333,
|
||||
"hint_num_files_per_dir": 8,
|
||||
"fsevents_latency": 0.05
|
||||
}
|
||||
+5
-3
@@ -520,7 +520,7 @@ set(SOURCES
|
||||
db/flush_job.cc
|
||||
db/flush_scheduler.cc
|
||||
db/forward_iterator.cc
|
||||
db/import_column_family_job.cc
|
||||
db/import_column_family_job.cc
|
||||
db/internal_stats.cc
|
||||
db/logs_with_prep_tracker.cc
|
||||
db/log_reader.cc
|
||||
@@ -601,6 +601,7 @@ set(SOURCES
|
||||
table/block_based/full_filter_block.cc
|
||||
table/block_based/index_builder.cc
|
||||
table/block_based/partitioned_filter_block.cc
|
||||
table/block_based/uncompression_dict_reader.cc
|
||||
table/block_fetcher.cc
|
||||
table/bloom_block.cc
|
||||
table/cuckoo/cuckoo_table_builder.cc
|
||||
@@ -625,7 +626,7 @@ set(SOURCES
|
||||
test_util/sync_point_impl.cc
|
||||
test_util/testutil.cc
|
||||
test_util/transaction_test_util.cc
|
||||
tools/block_cache_trace_analyzer.cc
|
||||
tools/block_cache_analyzer/block_cache_trace_analyzer.cc
|
||||
tools/db_bench_tool.cc
|
||||
tools/dump/db_dump_tool.cc
|
||||
tools/ldb_cmd.cc
|
||||
@@ -680,6 +681,7 @@ set(SOURCES
|
||||
utilities/merge_operators/string_append/stringappend.cc
|
||||
utilities/merge_operators/string_append/stringappend2.cc
|
||||
utilities/merge_operators/uint64add.cc
|
||||
utilities/object_registry.cc
|
||||
utilities/option_change_migration/option_change_migration.cc
|
||||
utilities/options/options_util.cc
|
||||
utilities/persistent_cache/block_cache_tier.cc
|
||||
@@ -974,7 +976,7 @@ if(WITH_TESTS)
|
||||
table/merger_test.cc
|
||||
table/sst_file_reader_test.cc
|
||||
table/table_test.cc
|
||||
tools/block_cache_trace_analyzer_test.cc
|
||||
tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc
|
||||
tools/ldb_cmd_test.cc
|
||||
tools/reduce_levels_test.cc
|
||||
tools/sst_dump_test.cc
|
||||
|
||||
+58
-12
@@ -1,36 +1,84 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 6.4.6 (10/16/2019)
|
||||
### Bug Fixes
|
||||
* Fix a bug when partitioned filters and prefix search are used in conjunction, ::SeekForPrev could return invalid for an existing prefix. ::SeekForPrev might be called by the user, or internally on ::Prev, or within ::Seek if the return value involves Delete or a Merge operand.
|
||||
|
||||
## 6.4.5 (10/1/2019)
|
||||
### Bug Fixes
|
||||
* Revert the feature "Merging iterator to avoid child iterator reseek for some cases (#5286)" since it might cause strange results when reseek happens with a different iterator upper bound.
|
||||
* Fix a bug in BlockBasedTableIterator that might return incorrect results when reseek happens with a different iterator upper bound.
|
||||
|
||||
## 6.4.4 (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.4.2 (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.4.1 (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.4.0 (7/30/2019)
|
||||
### 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.
|
||||
|
||||
### Public API Change
|
||||
* Filter and compression dictionary 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, filter and compression dictionary blocks (as well as filter partitions) no longer get evicted from the cache when a table is closed.
|
||||
* Due to the above refactoring, block cache eviction statistics for filter and compression dictionary blocks are temporarily broken. We plan to reintroduce them in a later phase.
|
||||
* The semantics of the per-block-type block read counts in the performance context now match those of the generic block_read_count.
|
||||
* Errors related to the retrieval of the compression dictionary are now propagated to the user.
|
||||
* db_bench adds a "benchmark" stats_history, which prints out the whole stats history.
|
||||
* 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
|
||||
* ldb sometimes uses a string-append merge operator if no merge operator is passed in. This is to allow users to print keys from a DB with a merge operator.
|
||||
* Replaces old Registra with ObjectRegistry to allow user to create custom object from string, also add LoadEnv() to Env.
|
||||
* Added new overload of GetApproximateSizes which gets SizeApproximationOptions object and returns a Status. The older overloads are redirecting their calls to this new method and no longer assert if the include_flags doesn't have either of INCLUDE_MEMTABLES or INCLUDE_FILES bits set. It's recommended to use the new method only, as it is more type safe and returns a meaningful status in case of errors.
|
||||
|
||||
### New Features
|
||||
* Add argument `--secondary_path` to ldb to open the database as the secondary instance. This would keep the original DB intact.
|
||||
* Compression dictionary blocks are now prefetched and pinned in the cache (based on the customer's settings) the same way as index and filter blocks.
|
||||
* Added DBOptions::log_readahead_size which specifies the number of bytes to prefetch when reading the log. This is mostly useful for reading a remotely located log, as it can save the number of round-trips. If 0 (default), then the prefetching is disabled.
|
||||
* Support loading custom objects in unit tests. In the affected unit tests, RocksDB will create custom Env objects based on environment variable TEST_ENV_URI. Users need to make sure custom object types are properly registered. For example, a static library should expose a `RegisterCustomObjects` function. By linking the unit test binary with the static library, the unit test can execute this function.
|
||||
|
||||
### Performance Improvements
|
||||
* Reduce iterator key comparision for upper/lower bound check.
|
||||
* Improve performance of row_cache: make reads with newer snapshots than data in an SST file share the same cache key, except in some transaction cases.
|
||||
* The compression dictionary is no longer copied to a new object upon retrieval.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix ingested file and directory not being fsync.
|
||||
* Return TryAgain status in place of Corruption when new tail is not visible to TransactionLogIterator.
|
||||
* 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/filters 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 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).
|
||||
* 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.
|
||||
* Partitions of partitioned indexes no longer affect the read amplification statistics.
|
||||
* 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.
|
||||
* Due to the above refactoring, block cache eviction statistics for indexes 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.1s) 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) 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
|
||||
|
||||
@@ -42,8 +90,6 @@
|
||||
* 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
|
||||
|
||||
|
||||
@@ -442,7 +442,6 @@ TESTS = \
|
||||
db_block_cache_test \
|
||||
db_test \
|
||||
db_blob_index_test \
|
||||
db_bloom_filter_test \
|
||||
db_iter_test \
|
||||
db_iter_stress_test \
|
||||
db_log_iter_test \
|
||||
@@ -480,7 +479,6 @@ TESTS = \
|
||||
fault_injection_test \
|
||||
filelock_test \
|
||||
filename_test \
|
||||
file_reader_writer_test \
|
||||
block_based_filter_block_test \
|
||||
full_filter_block_test \
|
||||
partitioned_filter_block_test \
|
||||
@@ -570,6 +568,7 @@ TESTS = \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
db_bloom_filter_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_merge_operator_test \
|
||||
@@ -580,6 +579,7 @@ PARALLEL_TEST = \
|
||||
external_sst_file_test \
|
||||
import_column_family_test \
|
||||
fault_injection_test \
|
||||
file_reader_writer_test \
|
||||
inlineskiplist_test \
|
||||
manual_compaction_test \
|
||||
persistent_cache_test \
|
||||
@@ -1068,7 +1068,7 @@ rocksdb.h rocksdb.cc: build_tools/amalgamate.py Makefile $(LIB_SOURCES) unity.cc
|
||||
build_tools/amalgamate.py -I. -i./include unity.cc -x include/rocksdb/c.h -H rocksdb.h -o rocksdb.cc
|
||||
|
||||
clean:
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(LIBRARY) $(SHARED)
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(LIBRARY) $(SHARED)
|
||||
rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
|
||||
$(FIND) . -name "*.[oda]" -exec rm -f {} \;
|
||||
$(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
@@ -1114,7 +1114,7 @@ db_bench: tools/db_bench.o $(BENCHTOOLOBJECTS)
|
||||
trace_analyzer: tools/trace_analyzer.o $(ANALYZETOOLOBJECTS) $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
block_cache_trace_analyzer: tools/block_cache_trace_analyzer_tool.o $(ANALYZETOOLOBJECTS) $(LIBOBJECTS)
|
||||
block_cache_trace_analyzer: tools/block_cache_analyzer/block_cache_trace_analyzer_tool.o $(ANALYZETOOLOBJECTS) $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
cache_bench: cache/cache_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
@@ -1614,7 +1614,7 @@ db_secondary_test: db/db_impl/db_secondary_test.o db/db_test_util.o $(LIBOBJECTS
|
||||
block_cache_tracer_test: trace_replay/block_cache_tracer_test.o trace_replay/block_cache_tracer.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
block_cache_trace_analyzer_test: tools/block_cache_trace_analyzer_test.o tools/block_cache_trace_analyzer.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
block_cache_trace_analyzer_test: tools/block_cache_analyzer/block_cache_trace_analyzer_test.o tools/block_cache_analyzer/block_cache_trace_analyzer.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
@@ -1662,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 sparc64 aarch64))
|
||||
ifneq (,$(filter $(MACHINE), amd64 arm64 aarch64 sparc64))
|
||||
ARCH := 64
|
||||
else
|
||||
ARCH := 32
|
||||
@@ -1671,14 +1671,11 @@ else
|
||||
ARCH := $(shell getconf LONG_BIT)
|
||||
endif
|
||||
|
||||
ifeq (,$(findstring ppc,$(MACHINE)))
|
||||
ifeq (,$(filter $(MACHINE), ppc arm64 aarch64 sparc64))
|
||||
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
|
||||
@@ -1875,6 +1872,14 @@ 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
|
||||
|
||||
@@ -4,12 +4,31 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from targets_builder import TARGETSBuilder
|
||||
import json
|
||||
import os
|
||||
import fnmatch
|
||||
import sys
|
||||
|
||||
from util import ColorString
|
||||
|
||||
# This script generates TARGETS file for Buck.
|
||||
# Buck is a build tool specifying dependencies among different build targets.
|
||||
# User can pass extra dependencies as a JSON object via command line, and this
|
||||
# script can include these dependencies in the generate TARGETS file.
|
||||
# Usage:
|
||||
# $python buckifier/buckify_rocksdb.py
|
||||
# (This generates a TARGET file without user-specified dependency for unit
|
||||
# tests.)
|
||||
# $python buckifier/buckify_rocksdb.py \
|
||||
# '{"fake": { \
|
||||
# "extra_deps": [":test_dep", "//fakes/module:mock1"], \
|
||||
# "extra_compiler_flags": ["-DROCKSDB_LITE", "-Os"], \
|
||||
# } \
|
||||
# }'
|
||||
# (Generated TARGETS file has test_dep and mock1 as dependencies for RocksDB
|
||||
# unit tests, and will use the extra_compiler_flags to compile the unit test
|
||||
# source.)
|
||||
|
||||
# tests to export as libraries for inclusion in other projects
|
||||
_EXPORTED_TEST_LIBS = ["env_basic_test"]
|
||||
|
||||
@@ -86,8 +105,38 @@ def get_tests(repo_path):
|
||||
return tests
|
||||
|
||||
|
||||
# Parse extra dependencies passed by user from command line
|
||||
def get_dependencies():
|
||||
deps_map = {
|
||||
''.encode('ascii'): {
|
||||
'extra_deps'.encode('ascii'): [],
|
||||
'extra_compiler_flags'.encode('ascii'): []
|
||||
}
|
||||
}
|
||||
if len(sys.argv) < 2:
|
||||
return deps_map
|
||||
|
||||
def encode_dict(data):
|
||||
rv = {}
|
||||
for k, v in data.items():
|
||||
if isinstance(k, unicode):
|
||||
k = k.encode('ascii')
|
||||
if isinstance(v, unicode):
|
||||
v = v.encode('ascii')
|
||||
elif isinstance(v, list):
|
||||
v = [x.encode('ascii') for x in v]
|
||||
elif isinstance(v, dict):
|
||||
v = encode_dict(v)
|
||||
rv[k] = v
|
||||
return rv
|
||||
extra_deps = json.loads(sys.argv[1], object_hook=encode_dict)
|
||||
for target_alias, deps in extra_deps.items():
|
||||
deps_map[target_alias] = deps
|
||||
return deps_map
|
||||
|
||||
|
||||
# Prepare TARGETS file for buck
|
||||
def generate_targets(repo_path):
|
||||
def generate_targets(repo_path, deps_map):
|
||||
print(ColorString.info("Generating TARGETS"))
|
||||
# parsed src.mk file
|
||||
src_mk = parse_src_mk(repo_path)
|
||||
@@ -121,24 +170,33 @@ def generate_targets(repo_path):
|
||||
["test_util/testutil.cc"],
|
||||
[":rocksdb_lib"])
|
||||
|
||||
print("Extra dependencies:\n{0}".format(str(deps_map)))
|
||||
# test for every test we found in the Makefile
|
||||
for test in sorted(tests):
|
||||
match_src = [src for src in cc_files if ("/%s.c" % test) in src]
|
||||
if len(match_src) == 0:
|
||||
print(ColorString.warning("Cannot find .cc file for %s" % test))
|
||||
continue
|
||||
elif len(match_src) > 1:
|
||||
print(ColorString.warning("Found more than one .cc for %s" % test))
|
||||
print(match_src)
|
||||
continue
|
||||
for target_alias, deps in deps_map.items():
|
||||
for test in sorted(tests):
|
||||
match_src = [src for src in cc_files if ("/%s.c" % test) in src]
|
||||
if len(match_src) == 0:
|
||||
print(ColorString.warning("Cannot find .cc file for %s" % test))
|
||||
continue
|
||||
elif len(match_src) > 1:
|
||||
print(ColorString.warning("Found more than one .cc for %s" % test))
|
||||
print(match_src)
|
||||
continue
|
||||
|
||||
assert(len(match_src) == 1)
|
||||
is_parallel = tests[test]
|
||||
TARGETS.register_test(test, match_src[0], is_parallel)
|
||||
assert(len(match_src) == 1)
|
||||
is_parallel = tests[test]
|
||||
test_target_name = \
|
||||
test if not target_alias else test + "_" + target_alias
|
||||
TARGETS.register_test(
|
||||
test_target_name,
|
||||
match_src[0],
|
||||
is_parallel,
|
||||
deps['extra_deps'],
|
||||
deps['extra_compiler_flags'])
|
||||
|
||||
if test in _EXPORTED_TEST_LIBS:
|
||||
test_library = "%s_lib" % test
|
||||
TARGETS.add_library(test_library, match_src, [":rocksdb_test_lib"])
|
||||
if test in _EXPORTED_TEST_LIBS:
|
||||
test_library = "%s_lib" % test_target_name
|
||||
TARGETS.add_library(test_library, match_src, [":rocksdb_test_lib"])
|
||||
TARGETS.flush_tests()
|
||||
|
||||
print(ColorString.info("Generated TARGETS Summary:"))
|
||||
@@ -163,8 +221,9 @@ def exit_with_error(msg):
|
||||
|
||||
|
||||
def main():
|
||||
deps_map = get_dependencies()
|
||||
# Generate TARGETS file for buck
|
||||
ok = generate_targets(get_rocksdb_path())
|
||||
ok = generate_targets(get_rocksdb_path(), deps_map)
|
||||
if not ok:
|
||||
exit_with_error("Failed to generate TARGETS files")
|
||||
|
||||
|
||||
@@ -51,14 +51,21 @@ class TARGETSBuilder:
|
||||
pretty_list(deps)))
|
||||
self.total_bin = self.total_bin + 1
|
||||
|
||||
def register_test(self, test_name, src, is_parallel):
|
||||
def register_test(self,
|
||||
test_name,
|
||||
src,
|
||||
is_parallel,
|
||||
extra_deps,
|
||||
extra_compiler_flags):
|
||||
exec_mode = "serial"
|
||||
if is_parallel:
|
||||
exec_mode = "parallel"
|
||||
self.tests_cfg += targets_cfg.test_cfg_template % (
|
||||
test_name,
|
||||
str(src),
|
||||
str(exec_mode))
|
||||
str(exec_mode),
|
||||
extra_deps,
|
||||
extra_compiler_flags)
|
||||
|
||||
self.total_test = self.total_test + 1
|
||||
|
||||
|
||||
+62
-26
@@ -11,29 +11,9 @@ REPO_PATH = package_name() + "/"
|
||||
|
||||
ROCKSDB_COMPILER_FLAGS = [
|
||||
"-fno-builtin-memcmp",
|
||||
"-DROCKSDB_PLATFORM_POSIX",
|
||||
"-DROCKSDB_LIB_IO_POSIX",
|
||||
"-DROCKSDB_FALLOCATE_PRESENT",
|
||||
"-DROCKSDB_MALLOC_USABLE_SIZE",
|
||||
"-DROCKSDB_RANGESYNC_PRESENT",
|
||||
"-DROCKSDB_SCHED_GETCPU_PRESENT",
|
||||
"-DROCKSDB_SUPPORT_THREAD_LOCAL",
|
||||
"-DOS_LINUX",
|
||||
# Flags to enable libs we include
|
||||
"-DSNAPPY",
|
||||
"-DZLIB",
|
||||
"-DBZIP2",
|
||||
"-DLZ4",
|
||||
"-DZSTD",
|
||||
"-DZSTD_STATIC_LINKING_ONLY",
|
||||
"-DGFLAGS=gflags",
|
||||
"-DNUMA",
|
||||
"-DTBB",
|
||||
# Needed to compile in fbcode
|
||||
"-Wno-expansion-to-defined",
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
|
||||
"-DROCKSDB_BACKTRACE",
|
||||
"-Wnarrowing",
|
||||
"-DROCKSDB_NO_DYNAMIC_EXTENSION",
|
||||
]
|
||||
@@ -46,11 +26,54 @@ ROCKSDB_EXTERNAL_DEPS = [
|
||||
("lz4", None, "lz4"),
|
||||
("zstd", None),
|
||||
("tbb", None),
|
||||
("numa", None, "numa"),
|
||||
("googletest", None, "gtest"),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_DEPS = [
|
||||
(
|
||||
"linux",
|
||||
["third-party//numa:numa"],
|
||||
),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS = [
|
||||
(
|
||||
"linux",
|
||||
[
|
||||
"-DOS_LINUX",
|
||||
"-DROCKSDB_FALLOCATE_PRESENT",
|
||||
"-DROCKSDB_MALLOC_USABLE_SIZE",
|
||||
"-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
|
||||
"-DROCKSDB_RANGESYNC_PRESENT",
|
||||
"-DROCKSDB_SCHED_GETCPU_PRESENT",
|
||||
"-DHAVE_SSE42",
|
||||
"-DNUMA",
|
||||
],
|
||||
),
|
||||
(
|
||||
"macos",
|
||||
["-DOS_MACOSX"],
|
||||
),
|
||||
]
|
||||
|
||||
ROCKSDB_PREPROCESSOR_FLAGS = [
|
||||
"-DROCKSDB_PLATFORM_POSIX",
|
||||
"-DROCKSDB_LIB_IO_POSIX",
|
||||
"-DROCKSDB_SUPPORT_THREAD_LOCAL",
|
||||
|
||||
# Flags to enable libs we include
|
||||
"-DSNAPPY",
|
||||
"-DZLIB",
|
||||
"-DBZIP2",
|
||||
"-DLZ4",
|
||||
"-DZSTD",
|
||||
"-DZSTD_STATIC_LINKING_ONLY",
|
||||
"-DGFLAGS=gflags",
|
||||
"-DTBB",
|
||||
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-DROCKSDB_BACKTRACE",
|
||||
|
||||
# Directories with files for #include
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
@@ -58,7 +81,6 @@ ROCKSDB_PREPROCESSOR_FLAGS = [
|
||||
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {
|
||||
"x86_64": [
|
||||
"-DHAVE_SSE42",
|
||||
"-DHAVE_PCLMUL",
|
||||
],
|
||||
}
|
||||
@@ -75,9 +97,15 @@ sanitizer = read_config("fbcode", "sanitizer")
|
||||
|
||||
# Do not enable jemalloc if sanitizer presents. RocksDB will further detect
|
||||
# whether the binary is linked with jemalloc at runtime.
|
||||
ROCKSDB_COMPILER_FLAGS += (["-DROCKSDB_JEMALLOC"] if sanitizer == "" else [])
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS += ([(
|
||||
"linux",
|
||||
["-DROCKSDB_JEMALLOC"],
|
||||
)] if sanitizer == "" else [])
|
||||
|
||||
ROCKSDB_EXTERNAL_DEPS += ([("jemalloc", None, "headers")] if sanitizer == "" else [])
|
||||
ROCKSDB_OS_DEPS += ([(
|
||||
"linux",
|
||||
["third-party//jemalloc:headers"],
|
||||
)] if sanitizer == "" else [])
|
||||
"""
|
||||
|
||||
|
||||
@@ -88,6 +116,8 @@ cpp_library(
|
||||
{headers_attr_prefix}headers = {headers},
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
@@ -110,11 +140,13 @@ test_cfg_template = """ [
|
||||
"%s",
|
||||
"%s",
|
||||
"%s",
|
||||
%s,
|
||||
%s,
|
||||
],
|
||||
"""
|
||||
|
||||
unittests_template = """
|
||||
# [test_name, test_src, test_type]
|
||||
# [test_name, test_src, test_type, extra_deps, extra_compiler_flags]
|
||||
ROCKS_TESTS = [
|
||||
%s]
|
||||
|
||||
@@ -123,15 +155,19 @@ ROCKS_TESTS = [
|
||||
# will not be included.
|
||||
[
|
||||
test_binary(
|
||||
extra_compiler_flags = extra_compiler_flags,
|
||||
extra_deps = extra_deps,
|
||||
parallelism = parallelism,
|
||||
rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
rocksdb_os_deps = ROCKSDB_OS_DEPS,
|
||||
rocksdb_os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
test_cc = test_cc,
|
||||
test_name = test_name,
|
||||
)
|
||||
for test_name, test_cc, parallelism in ROCKS_TESTS
|
||||
for test_name, test_cc, parallelism, extra_deps, extra_compiler_flags in ROCKS_TESTS
|
||||
if not is_opt_mode
|
||||
]
|
||||
"""
|
||||
@@ -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_PLATFORM007" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
else
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_5xx" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
#include "options/options_parser.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "test_util/fault_injection_test_env.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
@@ -60,8 +62,20 @@ class EnvCounter : public EnvWrapper {
|
||||
|
||||
class ColumnFamilyTestBase : public testing::Test {
|
||||
public:
|
||||
ColumnFamilyTestBase(uint32_t format) : rnd_(139), format_(format) {
|
||||
env_ = new EnvCounter(Env::Default());
|
||||
explicit ColumnFamilyTestBase(uint32_t format) : rnd_(139), format_(format) {
|
||||
Env* base_env = Env::Default();
|
||||
#ifndef ROCKSDB_LITE
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
if (test_env_uri) {
|
||||
Status s = ObjectRegistry::NewInstance()->NewSharedObject(test_env_uri,
|
||||
&env_guard_);
|
||||
base_env = env_guard_.get();
|
||||
EXPECT_OK(s);
|
||||
EXPECT_NE(Env::Default(), base_env);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
EXPECT_NE(nullptr, base_env);
|
||||
env_ = new EnvCounter(base_env);
|
||||
dbname_ = test::PerThreadDBPath("column_family_test");
|
||||
db_options_.create_if_missing = true;
|
||||
db_options_.fail_if_options_file_error = true;
|
||||
@@ -532,6 +546,7 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
std::string dbname_;
|
||||
DB* db_ = nullptr;
|
||||
EnvCounter* env_;
|
||||
std::shared_ptr<Env> env_guard_;
|
||||
Random rnd_;
|
||||
uint32_t format_;
|
||||
};
|
||||
@@ -3312,7 +3327,17 @@ TEST_P(ColumnFamilyTest, MultipleCFPathsTest) {
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -39,6 +39,7 @@ 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;
|
||||
|
||||
@@ -964,7 +964,7 @@ TEST_F(CompactionJobTest, SnapshotRefresh) {
|
||||
public:
|
||||
SnapshotListFetchCallbackTest(Env* env, Random64& rand,
|
||||
std::vector<SequenceNumber>* snapshots)
|
||||
: SnapshotListFetchCallback(env, 0 /*no time delay*/,
|
||||
: SnapshotListFetchCallback(env, 1 /*short time delay*/,
|
||||
1 /*fetch after each key*/),
|
||||
rand_(rand),
|
||||
snapshots_(snapshots) {}
|
||||
|
||||
@@ -1776,8 +1776,17 @@ INSTANTIATE_TEST_CASE_P(Timestamp, DBBasicTestWithTimestampWithParam,
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+51
-16
@@ -19,6 +19,9 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
size_t hit_count_ = 0;
|
||||
size_t insert_count_ = 0;
|
||||
size_t failure_count_ = 0;
|
||||
size_t compression_dict_miss_count_ = 0;
|
||||
size_t compression_dict_hit_count_ = 0;
|
||||
size_t compression_dict_insert_count_ = 0;
|
||||
size_t compressed_miss_count_ = 0;
|
||||
size_t compressed_hit_count_ = 0;
|
||||
size_t compressed_insert_count_ = 0;
|
||||
@@ -69,6 +72,15 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_ADD_FAILURES);
|
||||
}
|
||||
|
||||
void RecordCacheCountersForCompressionDict(const Options& options) {
|
||||
compression_dict_miss_count_ =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS);
|
||||
compression_dict_hit_count_ =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_HIT);
|
||||
compression_dict_insert_count_ =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD);
|
||||
}
|
||||
|
||||
void CheckCacheCounters(const Options& options, size_t expected_misses,
|
||||
size_t expected_hits, size_t expected_inserts,
|
||||
size_t expected_failures) {
|
||||
@@ -87,6 +99,28 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
failure_count_ = new_failure_count;
|
||||
}
|
||||
|
||||
void CheckCacheCountersForCompressionDict(
|
||||
const Options& options, size_t expected_compression_dict_misses,
|
||||
size_t expected_compression_dict_hits,
|
||||
size_t expected_compression_dict_inserts) {
|
||||
size_t new_compression_dict_miss_count =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS);
|
||||
size_t new_compression_dict_hit_count =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_HIT);
|
||||
size_t new_compression_dict_insert_count =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD);
|
||||
ASSERT_EQ(compression_dict_miss_count_ + expected_compression_dict_misses,
|
||||
new_compression_dict_miss_count);
|
||||
ASSERT_EQ(compression_dict_hit_count_ + expected_compression_dict_hits,
|
||||
new_compression_dict_hit_count);
|
||||
ASSERT_EQ(
|
||||
compression_dict_insert_count_ + expected_compression_dict_inserts,
|
||||
new_compression_dict_insert_count);
|
||||
compression_dict_miss_count_ = new_compression_dict_miss_count;
|
||||
compression_dict_hit_count_ = new_compression_dict_hit_count;
|
||||
compression_dict_insert_count_ = new_compression_dict_insert_count;
|
||||
}
|
||||
|
||||
void CheckCompressedCacheCounters(const Options& options,
|
||||
size_t expected_misses,
|
||||
size_t expected_hits,
|
||||
@@ -671,6 +705,8 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
ASSERT_EQ(i, NumTableFilesAtLevel(0, 0));
|
||||
for (int j = 0; j < kNumEntriesPerFile; ++j) {
|
||||
@@ -683,27 +719,26 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
// Compression dictionary blocks are preloaded.
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, kNumFiles /* expected_compression_dict_misses */,
|
||||
0 /* expected_compression_dict_hits */,
|
||||
kNumFiles /* expected_compression_dict_inserts */);
|
||||
|
||||
// Seek to a key in a file. It should cause the SST's dictionary meta-block
|
||||
// to be read.
|
||||
RecordCacheCounters(options);
|
||||
ASSERT_EQ(0,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD));
|
||||
ASSERT_EQ(
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT),
|
||||
0);
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
ReadOptions read_options;
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1)));
|
||||
// Two blocks missed/added: dictionary and data block
|
||||
// One block hit: index since it's prefetched
|
||||
CheckCacheCounters(options, 2 /* expected_misses */, 1 /* expected_hits */,
|
||||
2 /* expected_inserts */, 0 /* expected_failures */);
|
||||
ASSERT_EQ(1,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD));
|
||||
ASSERT_GT(
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT),
|
||||
0);
|
||||
// Two block hits: index and dictionary since they are prefetched
|
||||
// One block missed/added: data block
|
||||
CheckCacheCounters(options, 1 /* expected_misses */, 2 /* expected_hits */,
|
||||
1 /* expected_inserts */, 0 /* expected_failures */);
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, 0 /* expected_compression_dict_misses */,
|
||||
1 /* expected_compression_dict_hits */,
|
||||
0 /* expected_compression_dict_inserts */);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
-20
@@ -1441,16 +1441,7 @@ ColumnFamilyHandle* DBImpl::PersistentStatsColumnFamily() const {
|
||||
Status DBImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
if (nullptr == read_options.timestamp) {
|
||||
return GetImpl(read_options, column_family, key, value);
|
||||
}
|
||||
Slice akey;
|
||||
std::string buf;
|
||||
Status s = AppendTimestamp(key, *(read_options.timestamp), &akey, &buf);
|
||||
if (s.ok()) {
|
||||
s = GetImpl(read_options, column_family, akey, value);
|
||||
}
|
||||
return s;
|
||||
return GetImpl(read_options, column_family, key, value);
|
||||
}
|
||||
|
||||
Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
@@ -1499,7 +1490,22 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
? versions_->LastSequence()
|
||||
: versions_->LastPublishedSequence();
|
||||
if (callback) {
|
||||
// The unprep_seqs are not published for write unprepared, so it could be
|
||||
// that max_visible_seq is larger. Seek to the std::max of the two.
|
||||
// However, we still want our callback to contain the actual snapshot so
|
||||
// that it can do the correct visibility filtering.
|
||||
callback->Refresh(snapshot);
|
||||
|
||||
// Internally, WriteUnpreparedTxnReadCallback::Refresh would set
|
||||
// max_visible_seq = max(max_visible_seq, snapshot)
|
||||
//
|
||||
// Currently, the commented out assert is broken by
|
||||
// InvalidSnapshotReadCallback, but if write unprepared recovery followed
|
||||
// the regular transaction flow, then this special read callback would not
|
||||
// be needed.
|
||||
//
|
||||
// assert(callback->max_visible_seq() >= snapshot);
|
||||
snapshot = callback->max_visible_seq();
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::GetImpl:3");
|
||||
@@ -1513,7 +1519,7 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
// First look in the memtable, then in the immutable memtable (if any).
|
||||
// s is both in/out. When in, s could either be OK or MergeInProgress.
|
||||
// merge_operands will contain the sequence of merges in the latter case.
|
||||
LookupKey lkey(key, snapshot);
|
||||
LookupKey lkey(key, snapshot, read_options.timestamp);
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
|
||||
bool skip_memtable = (read_options.read_tier == kPersistedTier &&
|
||||
@@ -1851,6 +1857,24 @@ void DBImpl::MultiGetImpl(
|
||||
snapshot = last_seq_same_as_publish_seq_
|
||||
? versions_->LastSequence()
|
||||
: versions_->LastPublishedSequence();
|
||||
if (callback) {
|
||||
// The unprep_seqs are not published for write unprepared, so it could be
|
||||
// that max_visible_seq is larger. Seek to the std::max of the two.
|
||||
// However, we still want our callback to contain the actual snapshot so
|
||||
// that it can do the correct visibility filtering.
|
||||
callback->Refresh(snapshot);
|
||||
|
||||
// Internally, WriteUnpreparedTxnReadCallback::Refresh would set
|
||||
// max_visible_seq = max(max_visible_seq, snapshot)
|
||||
//
|
||||
// Currently, the commented out assert is broken by
|
||||
// InvalidSnapshotReadCallback, but if write unprepared recovery followed
|
||||
// the regular transaction flow, then this special read callback would not
|
||||
// be needed.
|
||||
//
|
||||
// assert(callback->max_visible_seq() >= snapshot);
|
||||
snapshot = callback->max_visible_seq();
|
||||
}
|
||||
}
|
||||
|
||||
// For each of the given keys, apply the entire "get" process as follows:
|
||||
@@ -2764,11 +2788,13 @@ void DBImpl::GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
}
|
||||
|
||||
void DBImpl::GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n, uint64_t* sizes,
|
||||
uint8_t include_flags) {
|
||||
assert(include_flags & DB::SizeApproximationFlags::INCLUDE_FILES ||
|
||||
include_flags & DB::SizeApproximationFlags::INCLUDE_MEMTABLES);
|
||||
Status DBImpl::GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n, uint64_t* sizes) {
|
||||
if (!options.include_memtabtles && !options.include_files) {
|
||||
return Status::InvalidArgument("Invalid options");
|
||||
}
|
||||
|
||||
Version* v;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
@@ -2780,18 +2806,19 @@ void DBImpl::GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
InternalKey k1(range[i].start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k2(range[i].limit, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
sizes[i] = 0;
|
||||
if (include_flags & DB::SizeApproximationFlags::INCLUDE_FILES) {
|
||||
if (options.include_files) {
|
||||
sizes[i] += versions_->ApproximateSize(
|
||||
v, k1.Encode(), k2.Encode(), /*start_level=*/0, /*end_level=*/-1,
|
||||
TableReaderCaller::kUserApproximateSize);
|
||||
}
|
||||
if (include_flags & DB::SizeApproximationFlags::INCLUDE_MEMTABLES) {
|
||||
if (options.include_memtabtles) {
|
||||
sizes[i] += sv->mem->ApproximateStats(k1.Encode(), k2.Encode()).size;
|
||||
sizes[i] += sv->imm->ApproximateStats(k1.Encode(), k2.Encode()).size;
|
||||
}
|
||||
}
|
||||
|
||||
ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::list<uint64_t>::iterator
|
||||
@@ -3669,9 +3696,9 @@ Status DBImpl::IngestExternalFiles(
|
||||
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) {
|
||||
uint64_t start_file_number =
|
||||
next_file_number + args[i - 1].external_files.size();
|
||||
start_file_number += args[i - 1].external_files.size();
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
|
||||
@@ -233,9 +233,10 @@ class DBImpl : public DB {
|
||||
virtual bool GetAggregatedIntProperty(const Slice& property,
|
||||
uint64_t* aggregated_value) override;
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(
|
||||
ColumnFamilyHandle* column_family, const Range* range, int n,
|
||||
uint64_t* sizes, uint8_t include_flags = INCLUDE_FILES) override;
|
||||
virtual Status GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n,
|
||||
uint64_t* sizes) override;
|
||||
using DB::GetApproximateMemTableStats;
|
||||
virtual void GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
const Range& range,
|
||||
|
||||
@@ -1008,8 +1008,10 @@ 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 ? &fetch_callback
|
||||
: nullptr);
|
||||
immutable_db_options_.max_subcompactions <= 1 &&
|
||||
c->mutable_cf_options()->snap_refresh_nanos > 0
|
||||
? &fetch_callback
|
||||
: nullptr);
|
||||
|
||||
// Creating a compaction influences the compaction score because the score
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
@@ -2737,8 +2739,10 @@ 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 ? &fetch_callback
|
||||
: nullptr);
|
||||
immutable_db_options_.max_subcompactions <= 1 &&
|
||||
c->mutable_cf_options()->snap_refresh_nanos > 0
|
||||
? &fetch_callback
|
||||
: nullptr);
|
||||
compaction_job.Prepare();
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
|
||||
@@ -721,7 +721,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
continue;
|
||||
}
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file), fname));
|
||||
file_reader.reset(new SequentialFileReader(
|
||||
std::move(file), fname, immutable_db_options_.log_readahead_size));
|
||||
}
|
||||
|
||||
// Create the log reader.
|
||||
|
||||
@@ -150,7 +150,8 @@ Status DBImplSecondary::MaybeInitLogReader(
|
||||
*log_reader = nullptr;
|
||||
return status;
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file), fname));
|
||||
file_reader.reset(new SequentialFileReader(
|
||||
std::move(file), fname, immutable_db_options_.log_readahead_size));
|
||||
}
|
||||
|
||||
// Create the log reader.
|
||||
|
||||
@@ -172,7 +172,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
&w, w.sequence, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/, this,
|
||||
true /*concurrent_memtable_writes*/, seq_per_batch_, w.batch_cnt);
|
||||
true /*concurrent_memtable_writes*/, seq_per_batch_, w.batch_cnt,
|
||||
batch_per_txn_);
|
||||
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
}
|
||||
@@ -1733,14 +1734,16 @@ Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
}
|
||||
return Write(opt, &batch);
|
||||
}
|
||||
Slice akey;
|
||||
std::string buf;
|
||||
Status s = AppendTimestamp(key, *(opt.timestamp), &akey, &buf);
|
||||
const Slice* ts = opt.timestamp;
|
||||
assert(nullptr != ts);
|
||||
size_t ts_sz = ts->size();
|
||||
WriteBatch batch(key.size() + ts_sz + value.size() + 24, /*max_bytes=*/0,
|
||||
ts_sz);
|
||||
Status s = batch.Put(column_family, key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
WriteBatch batch(akey.size() + value.size() + 24);
|
||||
s = batch.Put(column_family, akey, value);
|
||||
s = batch.AssignTimestamp(*ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
+20
-13
@@ -263,12 +263,6 @@ class DBIter final: public Iterator {
|
||||
bool TooManyInternalKeysSkipped(bool increment = true);
|
||||
inline bool IsVisible(SequenceNumber sequence);
|
||||
|
||||
// CanReseekToSkip() returns whether the iterator can use the optimization
|
||||
// where it reseek by sequence number to get the next key when there are too
|
||||
// many versions. This is disabled for write unprepared because seeking to
|
||||
// sequence number does not guarantee that it is visible.
|
||||
inline bool CanReseekToSkip();
|
||||
|
||||
// Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
|
||||
// is called
|
||||
void TempPinData() {
|
||||
@@ -453,6 +447,11 @@ inline bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check)
|
||||
// greater than that,
|
||||
// - none of the above : saved_key_ can contain anything, it doesn't matter.
|
||||
uint64_t num_skipped = 0;
|
||||
// For write unprepared, the target sequence number in reseek could be larger
|
||||
// than the snapshot, and thus needs to be skipped again. This could result in
|
||||
// an infinite loop of reseeks. To avoid that, we limit the number of reseeks
|
||||
// to one.
|
||||
bool reseek_done = false;
|
||||
|
||||
is_blob_ = false;
|
||||
|
||||
@@ -498,6 +497,7 @@ inline bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check)
|
||||
assert(!skipping || user_comparator_.Compare(
|
||||
ikey_.user_key, saved_key_.GetUserKey()) > 0);
|
||||
num_skipped = 0;
|
||||
reseek_done = false;
|
||||
switch (ikey_.type) {
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
@@ -551,6 +551,7 @@ inline bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check)
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
reseek_done = false;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else if (ikey_.type == kTypeBlobIndex) {
|
||||
if (!allow_blob_) {
|
||||
@@ -581,6 +582,7 @@ inline bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check)
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
reseek_done = false;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else {
|
||||
// By now, we are sure the current ikey is going to yield a
|
||||
@@ -611,14 +613,23 @@ inline bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check)
|
||||
!iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = false;
|
||||
num_skipped = 0;
|
||||
reseek_done = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have sequentially iterated via numerous equal keys, then it's
|
||||
// better to seek so that we can avoid too many key comparisons.
|
||||
if (num_skipped > max_skip_ && CanReseekToSkip()) {
|
||||
//
|
||||
// To avoid infinite loops, do not reseek if we have already attempted to
|
||||
// reseek previously.
|
||||
//
|
||||
// TODO(lth): If we reseek to sequence number greater than ikey_.sequence,
|
||||
// than it does not make sense to reseek as we would actually land further
|
||||
// away from the desired key. There is opportunity for optimization here.
|
||||
if (num_skipped > max_skip_ && !reseek_done) {
|
||||
is_key_seqnum_zero_ = false;
|
||||
num_skipped = 0;
|
||||
reseek_done = true;
|
||||
std::string last_key;
|
||||
if (skipping) {
|
||||
// We're looking for the next user-key but all we see are the same
|
||||
@@ -937,7 +948,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
// This user key has lots of entries.
|
||||
// We're going from old to new, and it's taking too long. Let's do a Seek()
|
||||
// and go from new to old. This helps when a key was overwritten many times.
|
||||
if (num_skipped >= max_skip_ && CanReseekToSkip()) {
|
||||
if (num_skipped >= max_skip_) {
|
||||
return FindValueForCurrentKeyUsingSeek();
|
||||
}
|
||||
|
||||
@@ -1234,7 +1245,7 @@ bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
}
|
||||
|
||||
if (num_skipped >= max_skip_ && CanReseekToSkip()) {
|
||||
if (num_skipped >= max_skip_) {
|
||||
num_skipped = 0;
|
||||
IterKey last_key;
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
@@ -1281,10 +1292,6 @@ bool DBIter::IsVisible(SequenceNumber sequence) {
|
||||
}
|
||||
}
|
||||
|
||||
bool DBIter::CanReseekToSkip() {
|
||||
return read_callback_ == nullptr || read_callback_->CanReseekToSkip();
|
||||
}
|
||||
|
||||
void DBIter::Seek(const Slice& target) {
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
|
||||
StopWatch sw(env_, statistics_, DB_SEEK);
|
||||
|
||||
+27
-69
@@ -182,6 +182,33 @@ TEST_P(DBIteratorTest, IterSeekBeforePrev) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, IterReseekNewUpperBound) {
|
||||
Random rnd(301);
|
||||
Options options = CurrentOptions();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1024;
|
||||
table_options.block_size_deviation = 50;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.compression = kNoCompression;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("a", RandomString(&rnd, 400)));
|
||||
ASSERT_OK(Put("aabb", RandomString(&rnd, 400)));
|
||||
ASSERT_OK(Put("aaef", RandomString(&rnd, 400)));
|
||||
ASSERT_OK(Put("b", RandomString(&rnd, 400)));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ReadOptions opts;
|
||||
Slice ub = Slice("aa");
|
||||
opts.iterate_upper_bound = &ub;
|
||||
auto iter = NewIterator(opts);
|
||||
iter->Seek(Slice("a"));
|
||||
ub = Slice("b");
|
||||
iter->Seek(Slice("aabc"));
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "aaef");
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, IterSeekForPrevBeforeNext) {
|
||||
ASSERT_OK(Put("a", "b"));
|
||||
ASSERT_OK(Put("c", "d"));
|
||||
@@ -2690,75 +2717,6 @@ 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) {
|
||||
|
||||
+14
-4
@@ -2598,13 +2598,14 @@ class ModelDB : public DB {
|
||||
return false;
|
||||
}
|
||||
using DB::GetApproximateSizes;
|
||||
void GetApproximateSizes(ColumnFamilyHandle* /*column_family*/,
|
||||
const Range* /*range*/, int n, uint64_t* sizes,
|
||||
uint8_t /*include_flags*/
|
||||
= INCLUDE_FILES) override {
|
||||
Status GetApproximateSizes(const SizeApproximationOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Range* /*range*/, int n,
|
||||
uint64_t* sizes) override {
|
||||
for (int i = 0; i < n; i++) {
|
||||
sizes[i] = 0;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
using DB::GetApproximateMemTableStats;
|
||||
void GetApproximateMemTableStats(ColumnFamilyHandle* /*column_family*/,
|
||||
@@ -6179,8 +6180,17 @@ TEST_F(DBTest, LargeBlockSizeTest) {
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -3771,10 +3771,105 @@ 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;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest2, RowCacheSnapshot) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.row_cache = NewLRUCache(8192);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "bar1"));
|
||||
|
||||
const Snapshot* s1 = db_->GetSnapshot();
|
||||
|
||||
ASSERT_OK(Put("foo", "bar2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("foo2", "bar"));
|
||||
const Snapshot* s2 = db_->GetSnapshot();
|
||||
ASSERT_OK(Put("foo3", "bar"));
|
||||
const Snapshot* s3 = db_->GetSnapshot();
|
||||
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 0);
|
||||
ASSERT_EQ(Get("foo"), "bar2");
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1);
|
||||
ASSERT_EQ(Get("foo"), "bar2");
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 1);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1);
|
||||
ASSERT_EQ(Get("foo", s1), "bar1");
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 1);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
|
||||
ASSERT_EQ(Get("foo", s2), "bar2");
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
|
||||
ASSERT_EQ(Get("foo", s1), "bar1");
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
|
||||
ASSERT_EQ(Get("foo", s3), "bar2");
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
|
||||
|
||||
db_->ReleaseSnapshot(s1);
|
||||
db_->ReleaseSnapshot(s2);
|
||||
db_->ReleaseSnapshot(s3);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
} // namespace rocksdb
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+25
-12
@@ -10,6 +10,7 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/forward_iterator.h"
|
||||
#include "rocksdb/env_encryption.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -47,20 +48,32 @@ ROT13BlockCipher rot13Cipher_(16);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
DBTestBase::DBTestBase(const std::string path)
|
||||
: mem_env_(!getenv("MEM_ENV") ? nullptr : new MockEnv(Env::Default())),
|
||||
#ifndef ROCKSDB_LITE
|
||||
encrypted_env_(
|
||||
!getenv("ENCRYPTED_ENV")
|
||||
? nullptr
|
||||
: NewEncryptedEnv(mem_env_ ? mem_env_ : Env::Default(),
|
||||
new CTREncryptionProvider(rot13Cipher_))),
|
||||
#else
|
||||
: mem_env_(nullptr),
|
||||
encrypted_env_(nullptr),
|
||||
#endif // ROCKSDB_LITE
|
||||
env_(new SpecialEnv(encrypted_env_
|
||||
? encrypted_env_
|
||||
: (mem_env_ ? mem_env_ : Env::Default()))),
|
||||
option_config_(kDefault) {
|
||||
Env* base_env = Env::Default();
|
||||
#ifndef ROCKSDB_LITE
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
if (test_env_uri) {
|
||||
Status s = ObjectRegistry::NewInstance()->NewSharedObject(test_env_uri,
|
||||
&env_guard_);
|
||||
base_env = env_guard_.get();
|
||||
EXPECT_OK(s);
|
||||
EXPECT_NE(Env::Default(), base_env);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
EXPECT_NE(nullptr, base_env);
|
||||
if (getenv("MEM_ENV")) {
|
||||
mem_env_ = new MockEnv(base_env);
|
||||
}
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (getenv("ENCRYPTED_ENV")) {
|
||||
encrypted_env_ = NewEncryptedEnv(mem_env_ ? mem_env_ : base_env,
|
||||
new CTREncryptionProvider(rot13Cipher_));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
env_ = new SpecialEnv(encrypted_env_ ? encrypted_env_
|
||||
: (mem_env_ ? mem_env_ : base_env));
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
dbname_ = test::PerThreadDBPath(env_, path);
|
||||
|
||||
@@ -702,6 +702,7 @@ class DBTestBase : public testing::Test {
|
||||
MockEnv* mem_env_;
|
||||
Env* encrypted_env_;
|
||||
SpecialEnv* env_;
|
||||
std::shared_ptr<Env> env_guard_;
|
||||
DB* db_;
|
||||
std::vector<ColumnFamilyHandle*> handles_;
|
||||
|
||||
|
||||
+9
-3
@@ -159,9 +159,11 @@ void InternalKeyComparator::FindShortSuccessor(std::string* key) const {
|
||||
}
|
||||
}
|
||||
|
||||
LookupKey::LookupKey(const Slice& _user_key, SequenceNumber s) {
|
||||
LookupKey::LookupKey(const Slice& _user_key, SequenceNumber s,
|
||||
const Slice* ts) {
|
||||
size_t usize = _user_key.size();
|
||||
size_t needed = usize + 13; // A conservative estimate
|
||||
size_t ts_sz = (nullptr == ts) ? 0 : ts->size();
|
||||
size_t needed = usize + ts_sz + 13; // A conservative estimate
|
||||
char* dst;
|
||||
if (needed <= sizeof(space_)) {
|
||||
dst = space_;
|
||||
@@ -170,10 +172,14 @@ LookupKey::LookupKey(const Slice& _user_key, SequenceNumber s) {
|
||||
}
|
||||
start_ = dst;
|
||||
// NOTE: We don't support users keys of more than 2GB :)
|
||||
dst = EncodeVarint32(dst, static_cast<uint32_t>(usize + 8));
|
||||
dst = EncodeVarint32(dst, static_cast<uint32_t>(usize + ts_sz + 8));
|
||||
kstart_ = dst;
|
||||
memcpy(dst, _user_key.data(), usize);
|
||||
dst += usize;
|
||||
if (nullptr != ts) {
|
||||
memcpy(dst, ts->data(), ts_sz);
|
||||
dst += ts_sz;
|
||||
}
|
||||
EncodeFixed64(dst, PackSequenceAndType(s, kValueTypeForSeek));
|
||||
dst += 8;
|
||||
end_ = dst;
|
||||
|
||||
@@ -669,20 +669,4 @@ struct ParsedInternalKeyComparator {
|
||||
const InternalKeyComparator* cmp;
|
||||
};
|
||||
|
||||
// TODO (yanqin): this causes extra memory allocation and copy. Should be
|
||||
// addressed in the future.
|
||||
inline Status AppendTimestamp(const Slice& key, const Slice& timestamp,
|
||||
Slice* ret_key, std::string* ret_buf) {
|
||||
assert(ret_key != nullptr);
|
||||
assert(ret_buf != nullptr);
|
||||
if (key.data() + key.size() == timestamp.data()) {
|
||||
*ret_key = Slice(key.data(), key.size() + timestamp.size());
|
||||
} else {
|
||||
ret_buf->assign(key.data(), key.size());
|
||||
ret_buf->append(timestamp.data(), timestamp.size());
|
||||
*ret_key = Slice(*ret_buf);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+1
-1
@@ -106,7 +106,7 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
<< "num_entries" << table_properties.num_entries
|
||||
<< "num_deletions" << table_properties.num_deletions
|
||||
<< "num_merge_operands" << table_properties.num_merge_operands
|
||||
<< "num_range_deletions" << table_properties.num_merge_operands
|
||||
<< "num_range_deletions" << table_properties.num_range_deletions
|
||||
<< "format_version" << table_properties.format_version
|
||||
<< "fixed_key_len" << table_properties.fixed_key_len
|
||||
<< "filter_policy" << table_properties.filter_policy_name
|
||||
|
||||
@@ -160,7 +160,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
// We failed, remove all files that we copied into the db
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.internal_file_path.empty()) {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
Status s = env_->DeleteFile(f.internal_file_path);
|
||||
if (!s.ok()) {
|
||||
@@ -291,6 +291,9 @@ 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,
|
||||
|
||||
@@ -2369,10 +2369,11 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
new FaultInjectionTestEnv(env_));
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_injection_env.get();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, 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
|
||||
@@ -2386,6 +2387,9 @@ 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());
|
||||
@@ -2393,8 +2397,9 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
-1, true, true_data);
|
||||
ASSERT_OK(s);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2426,10 +2431,11 @@ TEST_P(ExternalSSTFileTest,
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_injection_env.get();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, 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"}}};
|
||||
{{"bar1", "bv1_0"}, {"bar2", "bv2_0"}, {"bar3", "bv3_0"}},
|
||||
{{"bar4", "bv4_0"}, {"bar5", "bv5_0"}, {"bar6", "bv6_0"}}};
|
||||
for (size_t i = 0; i != handles_.size(); ++i) {
|
||||
int cf = static_cast<int>(i);
|
||||
const auto& orig_data = data_before_ingestion[i];
|
||||
@@ -2442,6 +2448,7 @@ 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
|
||||
@@ -2455,6 +2462,8 @@ 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());
|
||||
@@ -2508,10 +2517,11 @@ TEST_P(ExternalSSTFileTest,
|
||||
dbfull()->ReleaseSnapshot(read_opts.snapshot);
|
||||
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
// Should see consistent state after ingestion for all column families even
|
||||
// without snapshot.
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2541,10 +2551,11 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) {
|
||||
"DBImpl::IngestExternalFiles:BeforeLastJobPrepare:1"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, 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
|
||||
@@ -2558,6 +2569,9 @@ 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());
|
||||
@@ -2577,8 +2591,9 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) {
|
||||
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2607,10 +2622,11 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) {
|
||||
"DBImpl::IngestExternalFiles:BeforeJobsRun:1"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, 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
|
||||
@@ -2624,6 +2640,8 @@ 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());
|
||||
@@ -2643,8 +2661,9 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) {
|
||||
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2664,7 +2683,7 @@ TEST_P(ExternalSSTFileTest,
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_injection_env.get();
|
||||
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
|
||||
SyncPoint::GetInstance()->ClearTrace();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -2682,6 +2701,7 @@ 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
|
||||
@@ -2695,6 +2715,8 @@ 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());
|
||||
@@ -2715,8 +2737,9 @@ TEST_P(ExternalSSTFileTest,
|
||||
fault_injection_env->DropUnsyncedFileData();
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
|
||||
@@ -427,6 +427,7 @@ TEST_F(ImportColumnFamilyTest, ImportExportedSSTFromAnotherDB) {
|
||||
}
|
||||
db_copy->DropColumnFamily(cfh);
|
||||
db_copy->DestroyColumnFamilyHandle(cfh);
|
||||
delete db_copy;
|
||||
test::DestroyDir(env_, dbname_ + "/db_copy");
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -21,7 +21,8 @@ class LookupKey {
|
||||
public:
|
||||
// Initialize *this for looking up user_key at a snapshot with
|
||||
// the specified sequence number.
|
||||
LookupKey(const Slice& _user_key, SequenceNumber sequence);
|
||||
LookupKey(const Slice& _user_key, SequenceNumber sequence,
|
||||
const Slice* ts = nullptr);
|
||||
|
||||
~LookupKey();
|
||||
|
||||
|
||||
@@ -42,9 +42,6 @@ class ReadCallback {
|
||||
// Refresh to a more recent visible seq
|
||||
virtual void Refresh(SequenceNumber seq) { max_visible_seq_ = seq; }
|
||||
|
||||
// Refer to DBIter::CanReseekToSkip
|
||||
virtual bool CanReseekToSkip() { return true; }
|
||||
|
||||
protected:
|
||||
// The max visible seq, it is usually the snapshot but could be larger if
|
||||
// transaction has its own writes written to db.
|
||||
|
||||
+46
-3
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/snapshot_impl.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "file/filename.h"
|
||||
|
||||
@@ -24,6 +25,7 @@
|
||||
#include "table/table_builder.h"
|
||||
#include "table/table_reader.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/stop_watch.h"
|
||||
@@ -192,7 +194,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
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,
|
||||
!for_compaction /* record_read_stats */, file_read_hist,
|
||||
skip_filters, level);
|
||||
if (s.ok()) {
|
||||
table_reader = GetTableReaderFromHandle(handle);
|
||||
@@ -277,8 +279,23 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
// sequence key increases. However, to support caching snapshot
|
||||
// reads, we append the sequence number (incremented by 1 to
|
||||
// distinguish from 0) only in this case.
|
||||
uint64_t seq_no =
|
||||
options.snapshot == nullptr ? 0 : 1 + GetInternalKeySeqno(k);
|
||||
// If the snapshot is larger than the largest seqno in the file,
|
||||
// all data should be exposed to the snapshot, so we treat it
|
||||
// the same as there is no snapshot. The exception is that if
|
||||
// a seq-checking callback is registered, some internal keys
|
||||
// may still be filtered out.
|
||||
uint64_t seq_no = 0;
|
||||
// Maybe we can include the whole file ifsnapshot == fd.largest_seqno.
|
||||
if (options.snapshot != nullptr &&
|
||||
(get_context->has_callback() ||
|
||||
static_cast_with_check<const SnapshotImpl, const Snapshot>(
|
||||
options.snapshot)
|
||||
->GetSequenceNumber() <= fd.largest_seqno)) {
|
||||
// We should consider to use options.snapshot->GetSequenceNumber()
|
||||
// instead of GetInternalKeySeqno(k), which will make the code
|
||||
// easier to understand.
|
||||
seq_no = 1 + GetInternalKeySeqno(k);
|
||||
}
|
||||
|
||||
// Compute row cache key.
|
||||
row_cache_key.TrimAppend(row_cache_key.Size(), row_cache_id_.data(),
|
||||
@@ -488,4 +505,30 @@ void TableCache::Evict(Cache* cache, uint64_t file_number) {
|
||||
cache->Erase(GetSliceForFileNumber(&file_number));
|
||||
}
|
||||
|
||||
uint64_t TableCache::ApproximateOffsetOf(
|
||||
const Slice& key, const FileDescriptor& fd, TableReaderCaller caller,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const SliceTransform* prefix_extractor) {
|
||||
uint64_t result = 0;
|
||||
TableReader* table_reader = fd.table_reader;
|
||||
Cache::Handle* table_handle = nullptr;
|
||||
if (table_reader == nullptr) {
|
||||
const bool for_compaction = (caller == TableReaderCaller::kCompaction);
|
||||
Status s = FindTable(env_options_, internal_comparator, fd, &table_handle,
|
||||
prefix_extractor, false /* no_io */,
|
||||
!for_compaction /* record_read_stats */);
|
||||
if (s.ok()) {
|
||||
table_reader = GetTableReaderFromHandle(table_handle);
|
||||
}
|
||||
}
|
||||
|
||||
if (table_reader != nullptr) {
|
||||
result = table_reader->ApproximateOffsetOf(key, caller);
|
||||
}
|
||||
if (table_handle != nullptr) {
|
||||
ReleaseHandle(table_handle);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace rocksdb
|
||||
@@ -153,6 +153,12 @@ class TableCache {
|
||||
const FileDescriptor& fd,
|
||||
const SliceTransform* prefix_extractor = nullptr);
|
||||
|
||||
// Returns approximated offset of a key in a file represented by fd.
|
||||
uint64_t ApproximateOffsetOf(
|
||||
const Slice& key, const FileDescriptor& fd, TableReaderCaller caller,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const SliceTransform* prefix_extractor = nullptr);
|
||||
|
||||
// Release the handle from a cache
|
||||
void ReleaseHandle(Cache::Handle* handle);
|
||||
|
||||
|
||||
+12
-23
@@ -859,8 +859,7 @@ class LevelIterator final : public InternalIterator {
|
||||
bool skip_filters, int level, RangeDelAggregator* range_del_agg,
|
||||
const std::vector<AtomicCompactionUnitBoundary>*
|
||||
compaction_boundaries = nullptr)
|
||||
: InternalIterator(false),
|
||||
table_cache_(table_cache),
|
||||
: table_cache_(table_cache),
|
||||
read_options_(read_options),
|
||||
env_options_(env_options),
|
||||
icomparator_(icomparator),
|
||||
@@ -3420,16 +3419,10 @@ VersionSet::VersionSet(const std::string& dbname,
|
||||
env_options_(storage_options),
|
||||
block_cache_tracer_(block_cache_tracer) {}
|
||||
|
||||
void CloseTables(void* ptr, size_t) {
|
||||
TableReader* table_reader = reinterpret_cast<TableReader*>(ptr);
|
||||
table_reader->Close();
|
||||
}
|
||||
|
||||
VersionSet::~VersionSet() {
|
||||
// we need to delete column_family_set_ because its destructor depends on
|
||||
// VersionSet
|
||||
Cache* table_cache = column_family_set_->get_table_cache();
|
||||
table_cache->ApplyToAllCacheEntries(&CloseTables, false /* thread_safe */);
|
||||
column_family_set_.reset();
|
||||
for (auto& file : obsolete_files_) {
|
||||
if (file.metadata->table_reader_handle) {
|
||||
@@ -4267,7 +4260,8 @@ Status VersionSet::Recover(
|
||||
return s;
|
||||
}
|
||||
manifest_file_reader.reset(
|
||||
new SequentialFileReader(std::move(manifest_file), manifest_path));
|
||||
new SequentialFileReader(std::move(manifest_file), manifest_path,
|
||||
db_options_->log_readahead_size));
|
||||
}
|
||||
uint64_t current_manifest_file_size;
|
||||
s = env_->GetFileSize(manifest_path, ¤t_manifest_file_size);
|
||||
@@ -4597,7 +4591,8 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file), dscname));
|
||||
file_reader.reset(new SequentialFileReader(
|
||||
std::move(file), dscname, db_options_->log_readahead_size));
|
||||
}
|
||||
|
||||
bool have_prev_log_number = false;
|
||||
@@ -4972,19 +4967,12 @@ uint64_t VersionSet::ApproximateSize(Version* v, const FdWithKeyRange& f,
|
||||
} else {
|
||||
// "key" falls in the range for this table. Add the
|
||||
// approximate offset of "key" within the table.
|
||||
TableReader* table_reader_ptr;
|
||||
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,
|
||||
/*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, caller);
|
||||
TableCache* table_cache = v->cfd_->table_cache();
|
||||
if (table_cache != nullptr) {
|
||||
result = table_cache->ApproximateOffsetOf(
|
||||
key, f.file_metadata->fd, caller, v->cfd()->internal_comparator(),
|
||||
v->GetMutableCFOptions().prefix_extractor.get());
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -5721,7 +5709,8 @@ Status ReactiveVersionSet::MaybeSwitchManifest(
|
||||
std::unique_ptr<SequentialFileReader> manifest_file_reader;
|
||||
if (s.ok()) {
|
||||
manifest_file_reader.reset(
|
||||
new SequentialFileReader(std::move(manifest_file), manifest_path));
|
||||
new SequentialFileReader(std::move(manifest_file), manifest_path,
|
||||
db_options_->log_readahead_size));
|
||||
manifest_reader->reset(new log::FragmentBufferedReader(
|
||||
nullptr, std::move(manifest_file_reader), reporter,
|
||||
true /* checksum */, 0 /* log_number */));
|
||||
|
||||
+2
-2
@@ -655,7 +655,7 @@ class Version {
|
||||
|
||||
uint64_t GetSstFilesSize();
|
||||
|
||||
MutableCFOptions GetMutableCFOptions() { return mutable_cf_options_; }
|
||||
const MutableCFOptions& GetMutableCFOptions() { return mutable_cf_options_; }
|
||||
|
||||
private:
|
||||
Env* env_;
|
||||
@@ -981,7 +981,7 @@ class VersionSet {
|
||||
void AddLiveFiles(std::vector<FileDescriptor>* live_list);
|
||||
|
||||
// Return the approximate size of data to be scanned for range [start, end)
|
||||
// in levels [start_level, end_level). If end_level == 0 it will search
|
||||
// in levels [start_level, end_level). If end_level == -1 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,
|
||||
|
||||
+139
-7
@@ -135,6 +135,105 @@ struct BatchContentClassifier : public WriteBatch::Handler {
|
||||
}
|
||||
};
|
||||
|
||||
class TimestampAssigner : public WriteBatch::Handler {
|
||||
public:
|
||||
explicit TimestampAssigner(const Slice& ts)
|
||||
: timestamp_(ts), timestamps_(kEmptyTimestampList) {}
|
||||
explicit TimestampAssigner(const std::vector<Slice>& ts_list)
|
||||
: timestamps_(ts_list) {
|
||||
SanityCheck();
|
||||
}
|
||||
~TimestampAssigner() override {}
|
||||
|
||||
Status PutCF(uint32_t, const Slice& key, const Slice&) override {
|
||||
AssignTimestamp(key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DeleteCF(uint32_t, const Slice& key) override {
|
||||
AssignTimestamp(key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status SingleDeleteCF(uint32_t, const Slice& key) override {
|
||||
AssignTimestamp(key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DeleteRangeCF(uint32_t, const Slice& begin_key,
|
||||
const Slice& end_key) override {
|
||||
AssignTimestamp(begin_key);
|
||||
AssignTimestamp(end_key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MergeCF(uint32_t, const Slice& key, const Slice&) override {
|
||||
AssignTimestamp(key);
|
||||
++idx_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status PutBlobIndexCF(uint32_t, const Slice&, const Slice&) override {
|
||||
// TODO (yanqin): support blob db in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkBeginPrepare(bool) override {
|
||||
// TODO (yanqin): support in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkEndPrepare(const Slice&) override {
|
||||
// TODO (yanqin): support in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkCommit(const Slice&) override {
|
||||
// TODO (yanqin): support in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkRollback(const Slice&) override {
|
||||
// TODO (yanqin): support in the future.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
void SanityCheck() const {
|
||||
assert(!timestamps_.empty());
|
||||
#ifndef NDEBUG
|
||||
const size_t ts_sz = timestamps_[0].size();
|
||||
for (size_t i = 1; i != timestamps_.size(); ++i) {
|
||||
assert(ts_sz == timestamps_[i].size());
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
}
|
||||
|
||||
void AssignTimestamp(const Slice& key) {
|
||||
assert(timestamps_.empty() || idx_ < timestamps_.size());
|
||||
const Slice& ts = timestamps_.empty() ? timestamp_ : timestamps_[idx_];
|
||||
size_t ts_sz = ts.size();
|
||||
char* ptr = const_cast<char*>(key.data() + key.size() - ts_sz);
|
||||
memcpy(ptr, ts.data(), ts_sz);
|
||||
}
|
||||
|
||||
static const std::vector<Slice> kEmptyTimestampList;
|
||||
const Slice timestamp_;
|
||||
const std::vector<Slice>& timestamps_;
|
||||
size_t idx_ = 0;
|
||||
|
||||
// No copy or move.
|
||||
TimestampAssigner(const TimestampAssigner&) = delete;
|
||||
TimestampAssigner(TimestampAssigner&&) = delete;
|
||||
TimestampAssigner& operator=(const TimestampAssigner&) = delete;
|
||||
TimestampAssigner&& operator=(TimestampAssigner&&) = delete;
|
||||
};
|
||||
const std::vector<Slice> TimestampAssigner::kEmptyTimestampList;
|
||||
|
||||
} // anon namespace
|
||||
|
||||
struct SavePoints {
|
||||
@@ -142,7 +241,15 @@ struct SavePoints {
|
||||
};
|
||||
|
||||
WriteBatch::WriteBatch(size_t reserved_bytes, size_t max_bytes)
|
||||
: content_flags_(0), max_bytes_(max_bytes), rep_() {
|
||||
: content_flags_(0), max_bytes_(max_bytes), rep_(), timestamp_size_(0) {
|
||||
rep_.reserve((reserved_bytes > WriteBatchInternal::kHeader)
|
||||
? reserved_bytes
|
||||
: WriteBatchInternal::kHeader);
|
||||
rep_.resize(WriteBatchInternal::kHeader);
|
||||
}
|
||||
|
||||
WriteBatch::WriteBatch(size_t reserved_bytes, size_t max_bytes, size_t ts_sz)
|
||||
: content_flags_(0), max_bytes_(max_bytes), rep_(), timestamp_size_(ts_sz) {
|
||||
rep_.reserve((reserved_bytes > WriteBatchInternal::kHeader) ?
|
||||
reserved_bytes : WriteBatchInternal::kHeader);
|
||||
rep_.resize(WriteBatchInternal::kHeader);
|
||||
@@ -151,18 +258,21 @@ WriteBatch::WriteBatch(size_t reserved_bytes, size_t max_bytes)
|
||||
WriteBatch::WriteBatch(const std::string& rep)
|
||||
: content_flags_(ContentFlags::DEFERRED),
|
||||
max_bytes_(0),
|
||||
rep_(rep) {}
|
||||
rep_(rep),
|
||||
timestamp_size_(0) {}
|
||||
|
||||
WriteBatch::WriteBatch(std::string&& rep)
|
||||
: content_flags_(ContentFlags::DEFERRED),
|
||||
max_bytes_(0),
|
||||
rep_(std::move(rep)) {}
|
||||
rep_(std::move(rep)),
|
||||
timestamp_size_(0) {}
|
||||
|
||||
WriteBatch::WriteBatch(const WriteBatch& src)
|
||||
: wal_term_point_(src.wal_term_point_),
|
||||
content_flags_(src.content_flags_.load(std::memory_order_relaxed)),
|
||||
max_bytes_(src.max_bytes_),
|
||||
rep_(src.rep_) {
|
||||
rep_(src.rep_),
|
||||
timestamp_size_(src.timestamp_size_) {
|
||||
if (src.save_points_ != nullptr) {
|
||||
save_points_.reset(new SavePoints());
|
||||
save_points_->stack = src.save_points_->stack;
|
||||
@@ -174,7 +284,8 @@ WriteBatch::WriteBatch(WriteBatch&& src) noexcept
|
||||
wal_term_point_(std::move(src.wal_term_point_)),
|
||||
content_flags_(src.content_flags_.load(std::memory_order_relaxed)),
|
||||
max_bytes_(src.max_bytes_),
|
||||
rep_(std::move(src.rep_)) {}
|
||||
rep_(std::move(src.rep_)),
|
||||
timestamp_size_(src.timestamp_size_) {}
|
||||
|
||||
WriteBatch& WriteBatch::operator=(const WriteBatch& src) {
|
||||
if (&src != this) {
|
||||
@@ -643,7 +754,14 @@ Status WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
|
||||
b->rep_.push_back(static_cast<char>(kTypeColumnFamilyValue));
|
||||
PutVarint32(&b->rep_, column_family_id);
|
||||
}
|
||||
PutLengthPrefixedSlice(&b->rep_, key);
|
||||
if (0 == b->timestamp_size_) {
|
||||
PutLengthPrefixedSlice(&b->rep_, key);
|
||||
} else {
|
||||
PutVarint32(&b->rep_,
|
||||
static_cast<uint32_t>(key.size() + b->timestamp_size_));
|
||||
b->rep_.append(key.data(), key.size());
|
||||
b->rep_.append(b->timestamp_size_, '\0');
|
||||
}
|
||||
PutLengthPrefixedSlice(&b->rep_, value);
|
||||
b->content_flags_.store(
|
||||
b->content_flags_.load(std::memory_order_relaxed) | ContentFlags::HAS_PUT,
|
||||
@@ -692,7 +810,11 @@ Status WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
|
||||
b->rep_.push_back(static_cast<char>(kTypeColumnFamilyValue));
|
||||
PutVarint32(&b->rep_, column_family_id);
|
||||
}
|
||||
PutLengthPrefixedSliceParts(&b->rep_, key);
|
||||
if (0 == b->timestamp_size_) {
|
||||
PutLengthPrefixedSliceParts(&b->rep_, key);
|
||||
} else {
|
||||
PutLengthPrefixedSlicePartsWithPadding(&b->rep_, key, b->timestamp_size_);
|
||||
}
|
||||
PutLengthPrefixedSliceParts(&b->rep_, value);
|
||||
b->content_flags_.store(
|
||||
b->content_flags_.load(std::memory_order_relaxed) | ContentFlags::HAS_PUT,
|
||||
@@ -1038,6 +1160,16 @@ Status WriteBatch::PopSavePoint() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WriteBatch::AssignTimestamp(const Slice& ts) {
|
||||
TimestampAssigner ts_assigner(ts);
|
||||
return Iterate(&ts_assigner);
|
||||
}
|
||||
|
||||
Status WriteBatch::AssignTimestamps(const std::vector<Slice>& ts_list) {
|
||||
TimestampAssigner ts_assigner(ts_list);
|
||||
return Iterate(&ts_assigner);
|
||||
}
|
||||
|
||||
class MemTableInserter : public WriteBatch::Handler {
|
||||
|
||||
SequenceNumber sequence_;
|
||||
|
||||
@@ -8,9 +8,13 @@ def test_binary(
|
||||
test_cc,
|
||||
parallelism,
|
||||
rocksdb_arch_preprocessor_flags,
|
||||
rocksdb_os_preprocessor_flags,
|
||||
rocksdb_compiler_flags,
|
||||
rocksdb_preprocessor_flags,
|
||||
rocksdb_external_deps):
|
||||
rocksdb_external_deps,
|
||||
rocksdb_os_deps,
|
||||
extra_deps,
|
||||
extra_compiler_flags):
|
||||
TEST_RUNNER = native.package_name() + "/buckifier/rocks_test_runner.sh"
|
||||
|
||||
ttype = "gtest" if parallelism == "parallel" else "simple"
|
||||
@@ -20,9 +24,11 @@ def test_binary(
|
||||
name = test_bin,
|
||||
srcs = [test_cc],
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
os_preprocessor_flags = rocksdb_os_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags + extra_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
deps = [":rocksdb_test_lib"] + extra_deps,
|
||||
os_deps = rocksdb_os_deps,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
|
||||
|
||||
Vendored
+15
@@ -16,6 +16,7 @@
|
||||
#include "port/port.h"
|
||||
#include "port/sys_time.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -28,6 +29,20 @@ Status Env::NewLogger(const std::string& fname,
|
||||
return NewEnvLogger(fname, this, result);
|
||||
}
|
||||
|
||||
Status Env::LoadEnv(const std::string& value, Env** result) {
|
||||
Env* env = *result;
|
||||
Status s;
|
||||
#ifndef ROCKSDB_LITE
|
||||
s = ObjectRegistry::NewInstance()->NewStaticObject<Env>(value, &env);
|
||||
#else
|
||||
s = Status::NotSupported("Cannot load environment in LITE mode: ", value);
|
||||
#endif
|
||||
if (s.ok()) {
|
||||
*result = env;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string Env::PriorityToString(Env::Priority priority) {
|
||||
switch (priority) {
|
||||
case Env::Priority::BOTTOM:
|
||||
|
||||
Vendored
+1
-3
@@ -11,7 +11,6 @@
|
||||
|
||||
#include "env/mock_env.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -104,13 +103,12 @@ namespace {
|
||||
// ValuesIn() will skip running tests when given an empty collection.
|
||||
std::vector<Env*> GetCustomEnvs() {
|
||||
static Env* custom_env;
|
||||
static std::unique_ptr<Env> custom_env_guard;
|
||||
static bool init = false;
|
||||
if (!init) {
|
||||
init = true;
|
||||
const char* uri = getenv("TEST_ENV_URI");
|
||||
if (uri != nullptr) {
|
||||
custom_env = NewCustomObject<Env>(uri, &custom_env_guard);
|
||||
Env::LoadEnv(uri, &custom_env);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+8
-7
@@ -14,6 +14,7 @@
|
||||
#include <algorithm>
|
||||
#if defined(OS_LINUX)
|
||||
#include <linux/fs.h>
|
||||
#include <linux/falloc.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -803,8 +804,8 @@ Status PosixMmapFile::InvalidateCache(size_t offset, size_t length) {
|
||||
|
||||
#ifdef ROCKSDB_FALLOCATE_PRESENT
|
||||
Status PosixMmapFile::Allocate(uint64_t offset, uint64_t len) {
|
||||
assert(offset <= std::numeric_limits<off_t>::max());
|
||||
assert(len <= std::numeric_limits<off_t>::max());
|
||||
assert(offset <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
|
||||
assert(len <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
|
||||
TEST_KILL_RANDOM("PosixMmapFile::Allocate:0", rocksdb_kill_odds);
|
||||
int alloc_status = 0;
|
||||
if (allow_fallocate_) {
|
||||
@@ -873,7 +874,7 @@ Status PosixWritableFile::PositionedAppend(const Slice& data, uint64_t offset) {
|
||||
assert(IsSectorAligned(data.size(), GetRequiredBufferAlignment()));
|
||||
assert(IsSectorAligned(data.data(), GetRequiredBufferAlignment()));
|
||||
}
|
||||
assert(offset <= std::numeric_limits<off_t>::max());
|
||||
assert(offset <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
|
||||
const char* src = data.data();
|
||||
size_t nbytes = data.size();
|
||||
if (!PosixPositionedWrite(fd_, src, nbytes, static_cast<off_t>(offset))) {
|
||||
@@ -1009,8 +1010,8 @@ Status PosixWritableFile::InvalidateCache(size_t offset, size_t length) {
|
||||
|
||||
#ifdef ROCKSDB_FALLOCATE_PRESENT
|
||||
Status PosixWritableFile::Allocate(uint64_t offset, uint64_t len) {
|
||||
assert(offset <= std::numeric_limits<off_t>::max());
|
||||
assert(len <= std::numeric_limits<off_t>::max());
|
||||
assert(offset <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
|
||||
assert(len <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
|
||||
TEST_KILL_RANDOM("PosixWritableFile::Allocate:0", rocksdb_kill_odds);
|
||||
IOSTATS_TIMER_GUARD(allocate_nanos);
|
||||
int alloc_status = 0;
|
||||
@@ -1031,8 +1032,8 @@ Status PosixWritableFile::Allocate(uint64_t offset, uint64_t len) {
|
||||
|
||||
Status PosixWritableFile::RangeSync(uint64_t offset, uint64_t nbytes) {
|
||||
#ifdef ROCKSDB_RANGESYNC_PRESENT
|
||||
assert(offset <= std::numeric_limits<off_t>::max());
|
||||
assert(nbytes <= std::numeric_limits<off_t>::max());
|
||||
assert(offset <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
|
||||
assert(nbytes <= static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
|
||||
if (sync_file_range_supported_) {
|
||||
int ret;
|
||||
if (strict_bytes_per_sync_) {
|
||||
|
||||
@@ -250,11 +250,6 @@ class Cache {
|
||||
|
||||
virtual std::string GetPrintableOptions() const { return ""; }
|
||||
|
||||
// Mark the last inserted object as being a raw data block. This will be used
|
||||
// in tests. The default implementation does nothing.
|
||||
virtual void TEST_mark_as_data_block(const Slice& /*key*/,
|
||||
size_t /*charge*/) {}
|
||||
|
||||
MemoryAllocator* memory_allocator() const { return memory_allocator_.get(); }
|
||||
|
||||
private:
|
||||
|
||||
@@ -35,6 +35,7 @@ class Comparator {
|
||||
|
||||
virtual ~Comparator() {}
|
||||
|
||||
static const char* Type() { return "Comparator"; }
|
||||
// Three-way comparison. Returns value:
|
||||
// < 0 iff "a" < "b",
|
||||
// == 0 iff "a" == "b",
|
||||
|
||||
+17
-7
@@ -808,7 +808,7 @@ class DB {
|
||||
// stats should be included, or file stats approximation or both
|
||||
enum SizeApproximationFlags : uint8_t {
|
||||
NONE = 0,
|
||||
INCLUDE_MEMTABLES = 1,
|
||||
INCLUDE_MEMTABLES = 1 << 0,
|
||||
INCLUDE_FILES = 1 << 1
|
||||
};
|
||||
|
||||
@@ -818,14 +818,24 @@ class DB {
|
||||
// Note that the returned sizes measure file system space usage, so
|
||||
// if the user data compresses by a factor of ten, the returned
|
||||
// sizes will be one-tenth the size of the corresponding user data size.
|
||||
//
|
||||
// If include_flags defines whether the returned size should include
|
||||
// the recently written data in the mem-tables (if
|
||||
// the mem-table type supports it), data serialized to disk, or both.
|
||||
// include_flags should be of type DB::SizeApproximationFlags
|
||||
virtual Status GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n,
|
||||
uint64_t* sizes) = 0;
|
||||
|
||||
// Simpler versions of the GetApproximateSizes() method above.
|
||||
// The include_flags argumenbt must of type DB::SizeApproximationFlags
|
||||
// and can not be NONE.
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n, uint64_t* sizes,
|
||||
uint8_t include_flags = INCLUDE_FILES) = 0;
|
||||
uint8_t include_flags = INCLUDE_FILES) {
|
||||
SizeApproximationOptions options;
|
||||
options.include_memtabtles =
|
||||
(include_flags & SizeApproximationFlags::INCLUDE_MEMTABLES) != 0;
|
||||
options.include_files =
|
||||
(include_flags & SizeApproximationFlags::INCLUDE_FILES) != 0;
|
||||
GetApproximateSizes(options, column_family, range, n, sizes);
|
||||
}
|
||||
virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes,
|
||||
uint8_t include_flags = INCLUDE_FILES) {
|
||||
GetApproximateSizes(DefaultColumnFamily(), range, n, sizes, include_flags);
|
||||
|
||||
@@ -118,10 +118,10 @@ struct EnvOptions {
|
||||
bool fallocate_with_keep_size = true;
|
||||
|
||||
// See DBOptions doc
|
||||
size_t compaction_readahead_size;
|
||||
size_t compaction_readahead_size = 0;
|
||||
|
||||
// See DBOptions doc
|
||||
size_t random_access_max_buffer_size;
|
||||
size_t random_access_max_buffer_size = 0;
|
||||
|
||||
// See DBOptions doc
|
||||
size_t writable_file_max_buffer_size = 1024 * 1024;
|
||||
@@ -144,6 +144,11 @@ class Env {
|
||||
|
||||
virtual ~Env();
|
||||
|
||||
static const char* Type() { return "Environment"; }
|
||||
|
||||
// Loads the environment specified by the input value into the result
|
||||
static Status LoadEnv(const std::string& value, Env** result);
|
||||
|
||||
// Return a default environment suitable for the current operating
|
||||
// system. Sophisticated users may wish to provide their own Env
|
||||
// implementation instead of relying on this default environment.
|
||||
|
||||
@@ -46,6 +46,7 @@ class Logger;
|
||||
class MergeOperator {
|
||||
public:
|
||||
virtual ~MergeOperator() {}
|
||||
static const char* Type() { return "MergeOperator"; }
|
||||
|
||||
// Gives the client a way to express the read -> modify -> write semantics
|
||||
// key: (IN) The key that's associated with this merge operation.
|
||||
|
||||
@@ -275,10 +275,12 @@ 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.1s
|
||||
// NOTE: This feautre is currently incompatible with RangeDeletes.
|
||||
//
|
||||
// Default: 0
|
||||
//
|
||||
// Dynamically changeable through SetOptions() API
|
||||
uint64_t snap_refresh_nanos = 100 * 1000 * 1000; // 0.1s
|
||||
uint64_t snap_refresh_nanos = 0;
|
||||
|
||||
// Disable automatic compactions. Manual compactions can still
|
||||
// be issued on this column family
|
||||
@@ -1087,6 +1089,13 @@ struct DBOptions {
|
||||
// If set to true, takes precedence over
|
||||
// ReadOptions::background_purge_on_iterator_cleanup.
|
||||
bool avoid_unnecessary_blocking_io = false;
|
||||
|
||||
// The number of bytes to prefetch when reading the log. This is mostly useful
|
||||
// for reading a remotely located log, as it can save the number of
|
||||
// round-trips. If 0, then the prefetching is disabled.
|
||||
//
|
||||
// Default: 0
|
||||
size_t log_readahead_size = 0;
|
||||
};
|
||||
|
||||
// Options to control the behavior of a database (passed to DB::Open)
|
||||
@@ -1497,4 +1506,14 @@ struct ImportColumnFamilyOptions {
|
||||
bool move_files = false;
|
||||
};
|
||||
|
||||
// Options used with DB::GetApproximateSizes()
|
||||
struct SizeApproximationOptions {
|
||||
// Defines whether the returned size should include the recently written
|
||||
// data in the mem-tables. If set to false, include_files must be true.
|
||||
bool include_memtabtles = false;
|
||||
// Defines whether the returned size should include data serialized to disk.
|
||||
// If set to false, include_memtabtles must be true.
|
||||
bool include_files = true;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -480,7 +480,7 @@ enum StatsLevel : uint8_t {
|
||||
class Statistics {
|
||||
public:
|
||||
virtual ~Statistics() {}
|
||||
|
||||
static const char* Type() { return "Statistics"; }
|
||||
virtual uint64_t getTickerCount(uint32_t tickerType) const = 0;
|
||||
virtual void histogramData(uint32_t type,
|
||||
HistogramData* const data) const = 0;
|
||||
|
||||
@@ -11,80 +11,195 @@
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Creates a new T using the factory function that was registered with a pattern
|
||||
// that matches the provided "target" string according to std::regex_match.
|
||||
//
|
||||
// If no registered functions match, returns nullptr. If multiple functions
|
||||
// match, the factory function used is unspecified.
|
||||
//
|
||||
// Populates res_guard with result pointer if caller is granted ownership.
|
||||
template <typename T>
|
||||
T* NewCustomObject(const std::string& target, std::unique_ptr<T>* res_guard);
|
||||
|
||||
class Logger;
|
||||
// Returns a new T when called with a string. Populates the std::unique_ptr
|
||||
// argument if granting ownership to caller.
|
||||
template <typename T>
|
||||
using FactoryFunc = std::function<T*(const std::string&, std::unique_ptr<T>*)>;
|
||||
using FactoryFunc =
|
||||
std::function<T*(const std::string&, std::unique_ptr<T>*, std::string*)>;
|
||||
|
||||
// To register a factory function for a type T, initialize a Registrar<T> object
|
||||
// with static storage duration. For example:
|
||||
//
|
||||
// static Registrar<Env> hdfs_reg("hdfs://.*", &CreateHdfsEnv);
|
||||
//
|
||||
// Then, calling NewCustomObject<Env>("hdfs://some_path", ...) will match the
|
||||
// regex provided above, so it returns the result of invoking CreateHdfsEnv.
|
||||
template <typename T>
|
||||
class Registrar {
|
||||
class ObjectLibrary {
|
||||
public:
|
||||
explicit Registrar(std::string pattern, FactoryFunc<T> factory);
|
||||
};
|
||||
// Base class for an Entry in the Registry.
|
||||
class Entry {
|
||||
public:
|
||||
virtual ~Entry() {}
|
||||
Entry(const std::string& name) : name_(std::move(name)) {}
|
||||
|
||||
// Implementation details follow.
|
||||
// Checks to see if the target matches this entry
|
||||
virtual bool matches(const std::string& target) const {
|
||||
return name_ == target;
|
||||
}
|
||||
const std::string& Name() const { return name_; }
|
||||
|
||||
namespace internal {
|
||||
private:
|
||||
const std::string name_; // The name of the Entry
|
||||
}; // End class Entry
|
||||
|
||||
template <typename T>
|
||||
struct RegistryEntry {
|
||||
std::regex pattern;
|
||||
FactoryFunc<T> factory;
|
||||
};
|
||||
// An Entry containing a FactoryFunc for creating new Objects
|
||||
template <typename T>
|
||||
class FactoryEntry : public Entry {
|
||||
public:
|
||||
FactoryEntry(const std::string& name, FactoryFunc<T> f)
|
||||
: Entry(name), pattern_(std::move(name)), factory_(std::move(f)) {}
|
||||
~FactoryEntry() override {}
|
||||
bool matches(const std::string& target) const override {
|
||||
return std::regex_match(target, pattern_);
|
||||
}
|
||||
// Creates a new T object.
|
||||
T* NewFactoryObject(const std::string& target, std::unique_ptr<T>* guard,
|
||||
std::string* msg) const {
|
||||
return factory_(target, guard, msg);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct Registry {
|
||||
static Registry* Get() {
|
||||
static Registry<T> instance;
|
||||
return &instance;
|
||||
private:
|
||||
std::regex pattern_; // The pattern for this entry
|
||||
FactoryFunc<T> factory_;
|
||||
}; // End class FactoryEntry
|
||||
public:
|
||||
// Finds the entry matching the input name and type
|
||||
const Entry* FindEntry(const std::string& type,
|
||||
const std::string& name) const;
|
||||
void Dump(Logger* logger) const;
|
||||
|
||||
// Registers the factory with the library for the pattern.
|
||||
// If the pattern matches, the factory may be used to create a new object.
|
||||
template <typename T>
|
||||
const FactoryFunc<T>& Register(const std::string& pattern,
|
||||
const FactoryFunc<T>& factory) {
|
||||
std::unique_ptr<Entry> entry(new FactoryEntry<T>(pattern, factory));
|
||||
AddEntry(T::Type(), entry);
|
||||
return factory;
|
||||
}
|
||||
std::vector<RegistryEntry<T>> entries;
|
||||
// Returns the default ObjectLibrary
|
||||
static std::shared_ptr<ObjectLibrary>& Default();
|
||||
|
||||
private:
|
||||
Registry() = default;
|
||||
// Adds the input entry to the list for the given type
|
||||
void AddEntry(const std::string& type, std::unique_ptr<Entry>& entry);
|
||||
|
||||
// ** FactoryFunctions for this loader, organized by type
|
||||
std::unordered_map<std::string, std::vector<std::unique_ptr<Entry>>> entries_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
// The ObjectRegistry is used to register objects that can be created by a
|
||||
// name/pattern at run-time where the specific implementation of the object may
|
||||
// not be known in advance.
|
||||
class ObjectRegistry {
|
||||
public:
|
||||
static std::shared_ptr<ObjectRegistry> NewInstance();
|
||||
|
||||
template <typename T>
|
||||
T* NewCustomObject(const std::string& target, std::unique_ptr<T>* res_guard) {
|
||||
res_guard->reset();
|
||||
for (const auto& entry : internal::Registry<T>::Get()->entries) {
|
||||
if (std::regex_match(target, entry.pattern)) {
|
||||
return entry.factory(target, res_guard);
|
||||
ObjectRegistry();
|
||||
|
||||
void AddLibrary(const std::shared_ptr<ObjectLibrary>& library) {
|
||||
libraries_.emplace_back(library);
|
||||
}
|
||||
|
||||
// Creates a new T using the factory function that was registered with a
|
||||
// pattern that matches the provided "target" string according to
|
||||
// std::regex_match.
|
||||
//
|
||||
// If no registered functions match, returns nullptr. If multiple functions
|
||||
// match, the factory function used is unspecified.
|
||||
//
|
||||
// Populates res_guard with result pointer if caller is granted ownership.
|
||||
template <typename T>
|
||||
T* NewObject(const std::string& target, std::unique_ptr<T>* guard,
|
||||
std::string* errmsg) {
|
||||
guard->reset();
|
||||
const auto* basic = FindEntry(T::Type(), target);
|
||||
if (basic != nullptr) {
|
||||
const auto* factory =
|
||||
static_cast<const ObjectLibrary::FactoryEntry<T>*>(basic);
|
||||
return factory->NewFactoryObject(target, guard, errmsg);
|
||||
} else {
|
||||
*errmsg = std::string("Could not load ") + T::Type();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Registrar<T>::Registrar(std::string pattern, FactoryFunc<T> factory) {
|
||||
internal::Registry<T>::Get()->entries.emplace_back(internal::RegistryEntry<T>{
|
||||
std::regex(std::move(pattern)), std::move(factory)});
|
||||
}
|
||||
// Creates a new unique T using the input factory functions.
|
||||
// Returns OK if a new unique T was successfully created
|
||||
// Returns NotFound if the type/target could not be created
|
||||
// Returns InvalidArgument if the factory return an unguarded object
|
||||
// (meaning it cannot be managed by a unique ptr)
|
||||
template <typename T>
|
||||
Status NewUniqueObject(const std::string& target,
|
||||
std::unique_ptr<T>* result) {
|
||||
std::string errmsg;
|
||||
T* ptr = NewObject(target, result, &errmsg);
|
||||
if (ptr == nullptr) {
|
||||
return Status::NotFound(errmsg, target);
|
||||
} else if (*result) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
return Status::InvalidArgument(std::string("Cannot make a unique ") +
|
||||
T::Type() + " from unguarded one ",
|
||||
target);
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new shared T using the input factory functions.
|
||||
// Returns OK if a new shared T was successfully created
|
||||
// Returns NotFound if the type/target could not be created
|
||||
// Returns InvalidArgument if the factory return an unguarded object
|
||||
// (meaning it cannot be managed by a shared ptr)
|
||||
template <typename T>
|
||||
Status NewSharedObject(const std::string& target,
|
||||
std::shared_ptr<T>* result) {
|
||||
std::string errmsg;
|
||||
std::unique_ptr<T> guard;
|
||||
T* ptr = NewObject(target, &guard, &errmsg);
|
||||
if (ptr == nullptr) {
|
||||
return Status::NotFound(errmsg, target);
|
||||
} else if (guard) {
|
||||
result->reset(guard.release());
|
||||
return Status::OK();
|
||||
} else {
|
||||
return Status::InvalidArgument(std::string("Cannot make a shared ") +
|
||||
T::Type() + " from unguarded one ",
|
||||
target);
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new static T using the input factory functions.
|
||||
// Returns OK if a new static T was successfully created
|
||||
// Returns NotFound if the type/target could not be created
|
||||
// Returns InvalidArgument if the factory return a guarded object
|
||||
// (meaning it is managed by a unique ptr)
|
||||
template <typename T>
|
||||
Status NewStaticObject(const std::string& target, T** result) {
|
||||
std::string errmsg;
|
||||
std::unique_ptr<T> guard;
|
||||
T* ptr = NewObject(target, &guard, &errmsg);
|
||||
if (ptr == nullptr) {
|
||||
return Status::NotFound(errmsg, target);
|
||||
} else if (guard.get()) {
|
||||
return Status::InvalidArgument(std::string("Cannot make a static ") +
|
||||
T::Type() + " from a guarded one ",
|
||||
target);
|
||||
} else {
|
||||
*result = ptr;
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
// Dump the contents of the registry to the logger
|
||||
void Dump(Logger* logger) const;
|
||||
|
||||
private:
|
||||
const ObjectLibrary::Entry* FindEntry(const std::string& type,
|
||||
const std::string& name) const;
|
||||
|
||||
// The set of libraries to search for factories for this registry.
|
||||
// The libraries are searched in reverse order (back to front) when
|
||||
// searching for entries.
|
||||
std::vector<std::shared_ptr<ObjectLibrary>> libraries_;
|
||||
};
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -209,10 +209,11 @@ class StackableDB : public DB {
|
||||
}
|
||||
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(
|
||||
ColumnFamilyHandle* column_family, const Range* r, int n, uint64_t* sizes,
|
||||
uint8_t include_flags = INCLUDE_FILES) override {
|
||||
return db_->GetApproximateSizes(column_family, r, n, sizes, include_flags);
|
||||
virtual Status GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Range* r, int n,
|
||||
uint64_t* sizes) override {
|
||||
return db_->GetApproximateSizes(options, column_family, r, n, sizes);
|
||||
}
|
||||
|
||||
using DB::GetApproximateMemTableStats;
|
||||
|
||||
@@ -101,6 +101,11 @@ struct TransactionDBOptions {
|
||||
// ordering rather than concurrency control.
|
||||
bool skip_concurrency_control = false;
|
||||
|
||||
// This option is only valid for write unprepared. If a write batch exceeds
|
||||
// this threshold, then the transaction will implicitly flush the currently
|
||||
// pending writes into the database. A value of 0 or less means no limit.
|
||||
int64_t default_write_batch_flush_threshold = 0;
|
||||
|
||||
private:
|
||||
// 128 entries
|
||||
size_t wp_snapshot_cache_bits = static_cast<size_t>(7);
|
||||
@@ -162,6 +167,11 @@ struct TransactionOptions {
|
||||
// back/commit before new transactions start.
|
||||
// Default: false
|
||||
bool skip_concurrency_control = false;
|
||||
|
||||
// See TransactionDBOptions::default_write_batch_flush_threshold for
|
||||
// description. If a negative value is specified, then the default value from
|
||||
// TransactionDBOptions is used.
|
||||
int64_t write_batch_flush_threshold = -1;
|
||||
};
|
||||
|
||||
// The per-write optimizations that do not involve transactions. TransactionDB
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 6
|
||||
#define ROCKSDB_MINOR 2
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 4
|
||||
#define ROCKSDB_PATCH 6
|
||||
|
||||
// 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
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/write_batch_base.h"
|
||||
|
||||
@@ -60,6 +61,7 @@ struct SavePoint {
|
||||
class WriteBatch : public WriteBatchBase {
|
||||
public:
|
||||
explicit WriteBatch(size_t reserved_bytes = 0, size_t max_bytes = 0);
|
||||
explicit WriteBatch(size_t reserved_bytes, size_t max_bytes, size_t ts_sz);
|
||||
~WriteBatch() override;
|
||||
|
||||
using WriteBatchBase::Put;
|
||||
@@ -311,6 +313,12 @@ class WriteBatch : public WriteBatchBase {
|
||||
// Returns trie if MarkRollback will be called during Iterate
|
||||
bool HasRollback() const;
|
||||
|
||||
// Assign timestamp to write batch
|
||||
Status AssignTimestamp(const Slice& ts);
|
||||
|
||||
// Assign timestamps to write batch
|
||||
Status AssignTimestamps(const std::vector<Slice>& ts_list);
|
||||
|
||||
using WriteBatchBase::GetWriteBatch;
|
||||
WriteBatch* GetWriteBatch() override { return this; }
|
||||
|
||||
@@ -361,6 +369,7 @@ class WriteBatch : public WriteBatchBase {
|
||||
|
||||
protected:
|
||||
std::string rep_; // See comment in write_batch.cc for the format of rep_
|
||||
const size_t timestamp_size_;
|
||||
|
||||
// Intentionally copyable
|
||||
};
|
||||
|
||||
@@ -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 -j6 rocksdbjavastatic'
|
||||
scl enable devtoolset-7 'PORTABLE=1 make -j2 rocksdbjavastatic'
|
||||
elif scl --list | grep -q 'devtoolset-2'; then
|
||||
scl enable devtoolset-2 'make jclean clean'
|
||||
scl enable devtoolset-2 'PORTABLE=1 make -j6 rocksdbjavastatic'
|
||||
scl enable devtoolset-2 'PORTABLE=1 make -j2 rocksdbjavastatic'
|
||||
else
|
||||
echo "Could not find devtoolset"
|
||||
exit 1;
|
||||
fi
|
||||
else
|
||||
make jclean clean
|
||||
PORTABLE=1 make -j6 rocksdbjavastatic
|
||||
PORTABLE=1 make -j2 rocksdbjavastatic
|
||||
fi
|
||||
|
||||
cp java/target/librocksdbjni-linux*.so java/target/rocksdbjni-*-linux*.jar /rocksdb-host/java/target
|
||||
|
||||
@@ -46,9 +46,8 @@ AutoRollLogger::AutoRollLogger(Env* env, const std::string& dbname,
|
||||
}
|
||||
GetExistingFiles();
|
||||
ResetLogger();
|
||||
s = TrimOldLogFiles();
|
||||
if (!status_.ok()) {
|
||||
status_ = s;
|
||||
if (status_.ok()) {
|
||||
status_ = TrimOldLogFiles();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +155,11 @@ std::string AutoRollLogger::ValistToString(const char* format,
|
||||
|
||||
void AutoRollLogger::LogInternal(const char* format, ...) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
if (!logger_) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
logger_->Logv(format, args);
|
||||
@@ -164,7 +168,10 @@ void AutoRollLogger::LogInternal(const char* format, ...) {
|
||||
|
||||
void AutoRollLogger::Logv(const char* format, va_list ap) {
|
||||
assert(GetStatus().ok());
|
||||
|
||||
if (!logger_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<Logger> logger;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
@@ -208,6 +215,10 @@ void AutoRollLogger::WriteHeaderInfo() {
|
||||
}
|
||||
|
||||
void AutoRollLogger::LogHeader(const char* format, va_list args) {
|
||||
if (!logger_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// header message are to be retained in memory. Since we cannot make any
|
||||
// assumptions about the data contained in va_list, we will retain them as
|
||||
// strings
|
||||
|
||||
@@ -41,6 +41,10 @@ class AutoRollLogger : public Logger {
|
||||
}
|
||||
|
||||
size_t GetLogFileSize() const override {
|
||||
if (!logger_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<Logger> logger;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
@@ -635,6 +635,15 @@ 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) {
|
||||
|
||||
@@ -85,7 +85,8 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
manual_wal_flush(options.manual_wal_flush),
|
||||
atomic_flush(options.atomic_flush),
|
||||
avoid_unnecessary_blocking_io(options.avoid_unnecessary_blocking_io),
|
||||
persist_stats_to_disk(options.persist_stats_to_disk) {
|
||||
persist_stats_to_disk(options.persist_stats_to_disk),
|
||||
log_readahead_size(options.log_readahead_size) {
|
||||
}
|
||||
|
||||
void ImmutableDBOptions::Dump(Logger* log) const {
|
||||
@@ -225,6 +226,9 @@ void ImmutableDBOptions::Dump(Logger* log) const {
|
||||
avoid_unnecessary_blocking_io);
|
||||
ROCKS_LOG_HEADER(log, " Options.persist_stats_to_disk: %u",
|
||||
persist_stats_to_disk);
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " Options.log_readahead_size: %" ROCKSDB_PRIszt,
|
||||
log_readahead_size);
|
||||
}
|
||||
|
||||
MutableDBOptions::MutableDBOptions()
|
||||
|
||||
@@ -82,6 +82,7 @@ struct ImmutableDBOptions {
|
||||
bool atomic_flush;
|
||||
bool avoid_unnecessary_blocking_io;
|
||||
bool persist_stats_to_disk;
|
||||
size_t log_readahead_size;
|
||||
};
|
||||
|
||||
struct MutableDBOptions {
|
||||
|
||||
+17
-14
@@ -138,7 +138,7 @@ DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
|
||||
options.atomic_flush = immutable_db_options.atomic_flush;
|
||||
options.avoid_unnecessary_blocking_io =
|
||||
immutable_db_options.avoid_unnecessary_blocking_io;
|
||||
|
||||
options.log_readahead_size = immutable_db_options.log_readahead_size;
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -1045,21 +1045,21 @@ Status ParseColumnFamilyOption(const std::string& name,
|
||||
} else {
|
||||
if (name == kNameComparator) {
|
||||
// Try to get comparator from object registry first.
|
||||
std::unique_ptr<const Comparator> comp_guard;
|
||||
const Comparator* comp =
|
||||
NewCustomObject<const Comparator>(value, &comp_guard);
|
||||
// Only support static comparator for now.
|
||||
if (comp != nullptr && !comp_guard) {
|
||||
new_options->comparator = comp;
|
||||
Status status = ObjectRegistry::NewInstance()->NewStaticObject(
|
||||
value, &new_options->comparator);
|
||||
if (status.ok()) {
|
||||
return status;
|
||||
}
|
||||
} else if (name == kNameMergeOperator) {
|
||||
// Try to get merge operator from object registry first.
|
||||
std::unique_ptr<std::shared_ptr<MergeOperator>> mo_guard;
|
||||
std::shared_ptr<MergeOperator>* mo =
|
||||
NewCustomObject<std::shared_ptr<MergeOperator>>(value, &mo_guard);
|
||||
std::shared_ptr<MergeOperator> mo;
|
||||
Status status =
|
||||
ObjectRegistry::NewInstance()->NewSharedObject<MergeOperator>(
|
||||
value, &new_options->merge_operator);
|
||||
// Only support static comparator for now.
|
||||
if (mo != nullptr) {
|
||||
new_options->merge_operator = *mo;
|
||||
if (status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1191,10 +1191,10 @@ Status ParseDBOption(const std::string& name,
|
||||
NewGenericRateLimiter(static_cast<int64_t>(ParseUint64(value))));
|
||||
} else if (name == kNameEnv) {
|
||||
// Currently `Env` can be deserialized from object registry only.
|
||||
std::unique_ptr<Env> env_guard;
|
||||
Env* env = NewCustomObject<Env>(value, &env_guard);
|
||||
Env* env = new_options->env;
|
||||
Status status = Env::LoadEnv(value, &env);
|
||||
// Only support static env for now.
|
||||
if (env != nullptr && !env_guard) {
|
||||
if (status.ok()) {
|
||||
new_options->env = env;
|
||||
}
|
||||
} else {
|
||||
@@ -1664,6 +1664,9 @@ std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct DBOptions, avoid_unnecessary_blocking_io),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal, false,
|
||||
offsetof(struct ImmutableDBOptions, avoid_unnecessary_blocking_io)}},
|
||||
{"log_readahead_size",
|
||||
{offsetof(struct DBOptions, log_readahead_size), OptionType::kSizeT,
|
||||
OptionVerificationType::kNormal, false, 0}},
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, BlockBasedTableOptions::IndexType>
|
||||
|
||||
@@ -295,7 +295,8 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
|
||||
"manual_wal_flush=false;"
|
||||
"seq_per_batch=false;"
|
||||
"atomic_flush=false;"
|
||||
"avoid_unnecessary_blocking_io=false",
|
||||
"avoid_unnecessary_blocking_io=false;"
|
||||
"log_readahead_size=0",
|
||||
new_options));
|
||||
|
||||
ASSERT_EQ(unset_bytes_base, NumUnsetBytes(new_options_ptr, sizeof(DBOptions),
|
||||
|
||||
+17
-16
@@ -341,11 +341,11 @@ TEST_F(OptionsTest, GetColumnFamilyOptionsFromStringTest) {
|
||||
|
||||
// Comparator from object registry
|
||||
std::string kCompName = "reverse_comp";
|
||||
static Registrar<const Comparator> test_reg_a(
|
||||
kCompName, [](const std::string& /*name*/,
|
||||
std::unique_ptr<const Comparator>* /*comparator_guard*/) {
|
||||
return ReverseBytewiseComparator();
|
||||
});
|
||||
ObjectLibrary::Default()->Register<const Comparator>(
|
||||
kCompName,
|
||||
[](const std::string& /*name*/,
|
||||
std::unique_ptr<const Comparator>* /*guard*/,
|
||||
std::string* /* errmsg */) { return ReverseBytewiseComparator(); });
|
||||
|
||||
ASSERT_OK(GetColumnFamilyOptionsFromString(
|
||||
base_cf_opt, "comparator=" + kCompName + ";", &new_cf_opt));
|
||||
@@ -354,13 +354,12 @@ TEST_F(OptionsTest, GetColumnFamilyOptionsFromStringTest) {
|
||||
// MergeOperator from object registry
|
||||
std::unique_ptr<BytesXOROperator> bxo(new BytesXOROperator());
|
||||
std::string kMoName = bxo->Name();
|
||||
static Registrar<std::shared_ptr<MergeOperator>> test_reg_b(
|
||||
kMoName, [](const std::string& /*name*/,
|
||||
std::unique_ptr<std::shared_ptr<MergeOperator>>*
|
||||
merge_operator_guard) {
|
||||
merge_operator_guard->reset(
|
||||
new std::shared_ptr<MergeOperator>(new BytesXOROperator()));
|
||||
return merge_operator_guard->get();
|
||||
ObjectLibrary::Default()->Register<MergeOperator>(
|
||||
kMoName,
|
||||
[](const std::string& /*name*/, std::unique_ptr<MergeOperator>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
guard->reset(new BytesXOROperator());
|
||||
return guard->get();
|
||||
});
|
||||
|
||||
ASSERT_OK(GetColumnFamilyOptionsFromString(
|
||||
@@ -770,9 +769,10 @@ TEST_F(OptionsTest, GetOptionsFromStringTest) {
|
||||
explicit CustomEnv(Env* _target) : EnvWrapper(_target) {}
|
||||
};
|
||||
|
||||
static Registrar<Env> test_reg_env(
|
||||
ObjectLibrary::Default()->Register<Env>(
|
||||
kCustomEnvName,
|
||||
[](const std::string& /*name*/, std::unique_ptr<Env>* /*env_guard*/) {
|
||||
[](const std::string& /*name*/, std::unique_ptr<Env>* /*env_guard*/,
|
||||
std::string* /* errmsg */) {
|
||||
static CustomEnv env(Env::Default());
|
||||
return &env;
|
||||
});
|
||||
@@ -813,8 +813,9 @@ TEST_F(OptionsTest, GetOptionsFromStringTest) {
|
||||
ASSERT_EQ(new_options.create_if_missing, true);
|
||||
ASSERT_EQ(new_options.max_open_files, 1);
|
||||
ASSERT_TRUE(new_options.rate_limiter.get() != nullptr);
|
||||
std::unique_ptr<Env> env_guard;
|
||||
ASSERT_EQ(NewCustomObject<Env>(kCustomEnvName, &env_guard), new_options.env);
|
||||
Env* newEnv = new_options.env;
|
||||
ASSERT_OK(Env::LoadEnv(kCustomEnvName, &newEnv));
|
||||
ASSERT_EQ(newEnv, new_options.env);
|
||||
}
|
||||
|
||||
TEST_F(OptionsTest, DBOptionsSerialization) {
|
||||
|
||||
+2
-1
@@ -192,7 +192,8 @@ int GetMaxOpenFiles() {
|
||||
return -1;
|
||||
}
|
||||
// protect against overflow
|
||||
if (no_files_limit.rlim_cur >= std::numeric_limits<int>::max()) {
|
||||
if (static_cast<uintmax_t>(no_files_limit.rlim_cur) >=
|
||||
static_cast<uintmax_t>(std::numeric_limits<int>::max())) {
|
||||
return std::numeric_limits<int>::max();
|
||||
}
|
||||
return static_cast<int>(no_files_limit.rlim_cur);
|
||||
|
||||
@@ -121,6 +121,7 @@ LIB_SOURCES = \
|
||||
table/block_based/full_filter_block.cc \
|
||||
table/block_based/index_builder.cc \
|
||||
table/block_based/partitioned_filter_block.cc \
|
||||
table/block_based/uncompression_dict_reader.cc \
|
||||
table/block_fetcher.cc \
|
||||
table/bloom_block.cc \
|
||||
table/cuckoo/cuckoo_table_builder.cc \
|
||||
@@ -194,6 +195,7 @@ LIB_SOURCES = \
|
||||
utilities/merge_operators/string_append/stringappend2.cc \
|
||||
utilities/merge_operators/uint64add.cc \
|
||||
utilities/merge_operators/bytesxor.cc \
|
||||
utilities/object_registry.cc \
|
||||
utilities/option_change_migration/option_change_migration.cc \
|
||||
utilities/options/options_util.cc \
|
||||
utilities/persistent_cache/block_cache_tier.cc \
|
||||
@@ -244,7 +246,7 @@ TOOL_LIB_SOURCES = \
|
||||
utilities/blob_db/blob_dump_tool.cc \
|
||||
|
||||
ANALYZER_LIB_SOURCES = \
|
||||
tools/block_cache_trace_analyzer.cc \
|
||||
tools/block_cache_analyzer/block_cache_trace_analyzer.cc \
|
||||
tools/trace_analyzer_tool.cc \
|
||||
|
||||
MOCK_LIB_SOURCES = \
|
||||
@@ -372,8 +374,8 @@ MAIN_SOURCES = \
|
||||
table/table_reader_bench.cc \
|
||||
table/table_test.cc \
|
||||
third-party/gtest-1.7.0/fused-src/gtest/gtest-all.cc \
|
||||
tools/block_cache_trace_analyzer_test.cc \
|
||||
tools/block_cache_trace_analyzer_tool.cc \
|
||||
tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc \
|
||||
tools/block_cache_analyzer/block_cache_trace_analyzer_tool.cc \
|
||||
tools/db_bench.cc \
|
||||
tools/db_bench_tool_test.cc \
|
||||
tools/db_sanity_test.cc \
|
||||
|
||||
@@ -181,8 +181,8 @@ std::unique_ptr<FilterBlockReader> BlockBasedFilterBlockReader::Create(
|
||||
CachableEntry<BlockContents> filter_block;
|
||||
if (prefetch || !use_cache) {
|
||||
const Status s = ReadFilterBlock(table, prefetch_buffer, ReadOptions(),
|
||||
nullptr /* get_context */, lookup_context,
|
||||
&filter_block);
|
||||
use_cache, nullptr /* get_context */,
|
||||
lookup_context, &filter_block);
|
||||
if (!s.ok()) {
|
||||
return std::unique_ptr<FilterBlockReader>();
|
||||
}
|
||||
|
||||
@@ -45,10 +45,7 @@ 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;
|
||||
}
|
||||
: BlockBasedTable(rep, nullptr /* block_cache_tracer */) {}
|
||||
};
|
||||
|
||||
class FilterBlockTest : public testing::Test {
|
||||
@@ -64,7 +61,6 @@ class FilterBlockTest : public testing::Test {
|
||||
: 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;
|
||||
@@ -271,7 +267,6 @@ class BlockBasedFilterBlockTest : public testing::Test {
|
||||
: ioptions_(options_),
|
||||
env_options_(options_),
|
||||
icomp_(options_.comparator) {
|
||||
table_options_.no_block_cache = true;
|
||||
table_options_.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
|
||||
constexpr bool skip_filters = false;
|
||||
|
||||
@@ -63,12 +63,62 @@ extern const std::string kHashIndexPrefixesMetadataBlock;
|
||||
typedef BlockBasedTable::IndexReader IndexReader;
|
||||
|
||||
BlockBasedTable::~BlockBasedTable() {
|
||||
Close();
|
||||
delete rep_;
|
||||
}
|
||||
|
||||
std::atomic<uint64_t> BlockBasedTable::next_cache_key_id_(0);
|
||||
|
||||
template <typename TBlocklike>
|
||||
class BlocklikeTraits;
|
||||
|
||||
template <>
|
||||
class BlocklikeTraits<BlockContents> {
|
||||
public:
|
||||
static BlockContents* Create(BlockContents&& contents,
|
||||
SequenceNumber /* global_seqno */,
|
||||
size_t /* read_amp_bytes_per_bit */,
|
||||
Statistics* /* statistics */,
|
||||
bool /* using_zstd */) {
|
||||
return new BlockContents(std::move(contents));
|
||||
}
|
||||
|
||||
static uint32_t GetNumRestarts(const BlockContents& /* contents */) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class BlocklikeTraits<Block> {
|
||||
public:
|
||||
static Block* Create(BlockContents&& contents, SequenceNumber global_seqno,
|
||||
size_t read_amp_bytes_per_bit, Statistics* statistics,
|
||||
bool /* using_zstd */) {
|
||||
return new Block(std::move(contents), global_seqno, read_amp_bytes_per_bit,
|
||||
statistics);
|
||||
}
|
||||
|
||||
static uint32_t GetNumRestarts(const Block& block) {
|
||||
return block.NumRestarts();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class BlocklikeTraits<UncompressionDict> {
|
||||
public:
|
||||
static UncompressionDict* Create(BlockContents&& contents,
|
||||
SequenceNumber /* global_seqno */,
|
||||
size_t /* read_amp_bytes_per_bit */,
|
||||
Statistics* /* statistics */,
|
||||
bool using_zstd) {
|
||||
return new UncompressionDict(contents.data, std::move(contents.allocation),
|
||||
using_zstd);
|
||||
}
|
||||
|
||||
static uint32_t GetNumRestarts(const UncompressionDict& /* dict */) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
// Read the block identified by "handle" from "file".
|
||||
// The only relevant option is options.verify_checksums for now.
|
||||
@@ -76,15 +126,16 @@ namespace {
|
||||
// On success fill *result and return OK - caller owns *result
|
||||
// @param uncompression_dict Data for presetting the compression library's
|
||||
// dictionary.
|
||||
template <typename TBlocklike>
|
||||
Status ReadBlockFromFile(
|
||||
RandomAccessFileReader* file, FilePrefetchBuffer* prefetch_buffer,
|
||||
const Footer& footer, const ReadOptions& options, const BlockHandle& handle,
|
||||
std::unique_ptr<Block>* result, const ImmutableCFOptions& ioptions,
|
||||
std::unique_ptr<TBlocklike>* result, const ImmutableCFOptions& ioptions,
|
||||
bool do_uncompress, bool maybe_compressed, BlockType block_type,
|
||||
const UncompressionDict& uncompression_dict,
|
||||
const PersistentCacheOptions& cache_options, SequenceNumber global_seqno,
|
||||
size_t read_amp_bytes_per_bit, MemoryAllocator* memory_allocator,
|
||||
bool for_compaction = false) {
|
||||
bool for_compaction, bool using_zstd) {
|
||||
assert(result);
|
||||
|
||||
BlockContents contents;
|
||||
@@ -94,34 +145,9 @@ Status ReadBlockFromFile(
|
||||
cache_options, memory_allocator, nullptr, for_compaction);
|
||||
Status s = block_fetcher.ReadBlockContents();
|
||||
if (s.ok()) {
|
||||
result->reset(new Block(std::move(contents), global_seqno,
|
||||
read_amp_bytes_per_bit, ioptions.statistics));
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status ReadBlockFromFile(
|
||||
RandomAccessFileReader* file, FilePrefetchBuffer* prefetch_buffer,
|
||||
const Footer& footer, const ReadOptions& options, const BlockHandle& handle,
|
||||
std::unique_ptr<BlockContents>* result, const ImmutableCFOptions& ioptions,
|
||||
bool do_uncompress, bool maybe_compressed, BlockType block_type,
|
||||
const UncompressionDict& uncompression_dict,
|
||||
const PersistentCacheOptions& cache_options,
|
||||
SequenceNumber /* global_seqno */, size_t /* read_amp_bytes_per_bit */,
|
||||
MemoryAllocator* memory_allocator, bool for_compaction = false) {
|
||||
assert(result);
|
||||
|
||||
result->reset(new BlockContents);
|
||||
|
||||
BlockFetcher block_fetcher(
|
||||
file, prefetch_buffer, footer, options, handle, result->get(), ioptions,
|
||||
do_uncompress, maybe_compressed, block_type, uncompression_dict,
|
||||
cache_options, memory_allocator, nullptr, for_compaction);
|
||||
|
||||
const Status s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
result->reset();
|
||||
result->reset(BlocklikeTraits<TBlocklike>::Create(
|
||||
std::move(contents), global_seqno, read_amp_bytes_per_bit,
|
||||
ioptions.statistics, using_zstd));
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -148,8 +174,6 @@ void DeleteCachedEntry(const Slice& /*key*/, void* value) {
|
||||
delete entry;
|
||||
}
|
||||
|
||||
void DeleteCachedUncompressionDictEntry(const Slice& key, void* value);
|
||||
|
||||
// Release the cached entry and decrement its ref count.
|
||||
void ForceReleaseCachedEntry(void* arg, void* h) {
|
||||
Cache* cache = reinterpret_cast<Cache*>(arg);
|
||||
@@ -211,7 +235,7 @@ class BlockBasedTable::IndexReaderCommon : public BlockBasedTable::IndexReader {
|
||||
protected:
|
||||
static Status ReadIndexBlock(const BlockBasedTable* table,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
const ReadOptions& read_options,
|
||||
const ReadOptions& read_options, bool use_cache,
|
||||
GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
CachableEntry<Block>* index_block);
|
||||
@@ -243,6 +267,12 @@ class BlockBasedTable::IndexReaderCommon : public BlockBasedTable::IndexReader {
|
||||
return table_->get_rep()->index_value_is_full;
|
||||
}
|
||||
|
||||
bool cache_index_blocks() const {
|
||||
assert(table_ != nullptr);
|
||||
assert(table_->get_rep() != nullptr);
|
||||
return table_->get_rep()->table_options.cache_index_and_filter_blocks;
|
||||
}
|
||||
|
||||
Status GetOrReadIndexBlock(bool no_io, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
CachableEntry<Block>* index_block) const;
|
||||
@@ -261,7 +291,7 @@ class BlockBasedTable::IndexReaderCommon : public BlockBasedTable::IndexReader {
|
||||
|
||||
Status BlockBasedTable::IndexReaderCommon::ReadIndexBlock(
|
||||
const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
|
||||
const ReadOptions& read_options, GetContext* get_context,
|
||||
const ReadOptions& read_options, bool use_cache, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
CachableEntry<Block>* index_block) {
|
||||
PERF_TIMER_GUARD(read_index_block_nanos);
|
||||
@@ -276,7 +306,7 @@ Status BlockBasedTable::IndexReaderCommon::ReadIndexBlock(
|
||||
const Status s = table->RetrieveBlock(
|
||||
prefetch_buffer, read_options, rep->footer.index_handle(),
|
||||
UncompressionDict::GetEmptyDict(), index_block, BlockType::kIndex,
|
||||
get_context, lookup_context);
|
||||
get_context, lookup_context, /* for_compaction */ false, use_cache);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -298,7 +328,8 @@ Status BlockBasedTable::IndexReaderCommon::GetOrReadIndexBlock(
|
||||
}
|
||||
|
||||
return ReadIndexBlock(table_, /*prefetch_buffer=*/nullptr, read_options,
|
||||
get_context, lookup_context, index_block);
|
||||
cache_index_blocks(), get_context, lookup_context,
|
||||
index_block);
|
||||
}
|
||||
|
||||
// Index that allows binary search lookup in a two-level index structure.
|
||||
@@ -321,7 +352,7 @@ class PartitionIndexReader : public BlockBasedTable::IndexReaderCommon {
|
||||
CachableEntry<Block> index_block;
|
||||
if (prefetch || !use_cache) {
|
||||
const Status s =
|
||||
ReadIndexBlock(table, prefetch_buffer, ReadOptions(),
|
||||
ReadIndexBlock(table, prefetch_buffer, ReadOptions(), use_cache,
|
||||
/*get_context=*/nullptr, lookup_context, &index_block);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -512,7 +543,7 @@ class BinarySearchIndexReader : public BlockBasedTable::IndexReaderCommon {
|
||||
CachableEntry<Block> index_block;
|
||||
if (prefetch || !use_cache) {
|
||||
const Status s =
|
||||
ReadIndexBlock(table, prefetch_buffer, ReadOptions(),
|
||||
ReadIndexBlock(table, prefetch_buffer, ReadOptions(), use_cache,
|
||||
/*get_context=*/nullptr, lookup_context, &index_block);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -596,7 +627,7 @@ class HashIndexReader : public BlockBasedTable::IndexReaderCommon {
|
||||
CachableEntry<Block> index_block;
|
||||
if (prefetch || !use_cache) {
|
||||
const Status s =
|
||||
ReadIndexBlock(table, prefetch_buffer, ReadOptions(),
|
||||
ReadIndexBlock(table, prefetch_buffer, ReadOptions(), use_cache,
|
||||
/*get_context=*/nullptr, lookup_context, &index_block);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -1419,37 +1450,6 @@ Status BlockBasedTable::ReadRangeDelBlock(
|
||||
return s;
|
||||
}
|
||||
|
||||
Status BlockBasedTable::ReadCompressionDictBlock(
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
std::unique_ptr<const BlockContents>* compression_dict_block) const {
|
||||
assert(compression_dict_block != nullptr);
|
||||
Status s;
|
||||
if (!rep_->compression_dict_handle.IsNull()) {
|
||||
std::unique_ptr<BlockContents> compression_dict_cont{new BlockContents()};
|
||||
PersistentCacheOptions cache_options;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
BlockFetcher compression_block_fetcher(
|
||||
rep_->file.get(), prefetch_buffer, rep_->footer, read_options,
|
||||
rep_->compression_dict_handle, compression_dict_cont.get(),
|
||||
rep_->ioptions, false /* decompress */, false /*maybe_compressed*/,
|
||||
BlockType::kCompressionDictionary, UncompressionDict::GetEmptyDict(),
|
||||
cache_options);
|
||||
s = compression_block_fetcher.ReadBlockContents();
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
rep_->ioptions.info_log,
|
||||
"Encountered error while reading data from compression dictionary "
|
||||
"block %s",
|
||||
s.ToString().c_str());
|
||||
} else {
|
||||
*compression_dict_block = std::move(compression_dict_cont);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status BlockBasedTable::PrefetchIndexAndFilterBlocks(
|
||||
FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter,
|
||||
BlockBasedTable* new_table, bool prefetch_all,
|
||||
@@ -1555,23 +1555,16 @@ Status BlockBasedTable::PrefetchIndexAndFilterBlocks(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(ajkr): also prefetch compression dictionary block
|
||||
// TODO(ajkr): also pin compression dictionary block when
|
||||
// `pin_l0_filter_and_index_blocks_in_cache == true`.
|
||||
if (!table_options.cache_index_and_filter_blocks) {
|
||||
std::unique_ptr<const BlockContents> compression_dict_block;
|
||||
s = ReadCompressionDictBlock(prefetch_buffer, &compression_dict_block);
|
||||
if (!rep_->compression_dict_handle.IsNull()) {
|
||||
std::unique_ptr<UncompressionDictReader> uncompression_dict_reader;
|
||||
s = UncompressionDictReader::Create(this, prefetch_buffer, use_cache,
|
||||
prefetch_all, pin_all, lookup_context,
|
||||
&uncompression_dict_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (!rep_->compression_dict_handle.IsNull()) {
|
||||
assert(compression_dict_block != nullptr);
|
||||
// TODO(ajkr): find a way to avoid the `compression_dict_block` data copy
|
||||
rep_->uncompression_dict.reset(new UncompressionDict(
|
||||
compression_dict_block->data.ToString(),
|
||||
rep_->blocks_definitely_zstd_compressed, rep_->ioptions.statistics));
|
||||
}
|
||||
rep_->uncompression_dict_reader = std::move(uncompression_dict_reader);
|
||||
}
|
||||
|
||||
assert(s.ok());
|
||||
@@ -1609,8 +1602,8 @@ size_t BlockBasedTable::ApproximateMemoryUsage() const {
|
||||
if (rep_->index_reader) {
|
||||
usage += rep_->index_reader->ApproximateMemoryUsage();
|
||||
}
|
||||
if (rep_->uncompression_dict) {
|
||||
usage += rep_->uncompression_dict->ApproximateMemoryUsage();
|
||||
if (rep_->uncompression_dict_reader) {
|
||||
usage += rep_->uncompression_dict_reader->ApproximateMemoryUsage();
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
@@ -1629,7 +1622,8 @@ Status BlockBasedTable::ReadMetaBlock(FilePrefetchBuffer* prefetch_buffer,
|
||||
true /* decompress */, true /*maybe_compressed*/, BlockType::kMetaIndex,
|
||||
UncompressionDict::GetEmptyDict(), rep_->persistent_cache_options,
|
||||
kDisableGlobalSequenceNumber, 0 /* read_amp_bytes_per_bit */,
|
||||
GetMemoryAllocator(rep_->table_options));
|
||||
GetMemoryAllocator(rep_->table_options), false /* for_compaction */,
|
||||
rep_->blocks_definitely_zstd_compressed);
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(rep_->ioptions.info_log,
|
||||
@@ -1646,38 +1640,6 @@ Status BlockBasedTable::ReadMetaBlock(FilePrefetchBuffer* prefetch_buffer,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
template <typename TBlocklike>
|
||||
class BlocklikeTraits;
|
||||
|
||||
template <>
|
||||
class BlocklikeTraits<BlockContents> {
|
||||
public:
|
||||
static BlockContents* Create(BlockContents&& contents,
|
||||
SequenceNumber /* global_seqno */,
|
||||
size_t /* read_amp_bytes_per_bit */,
|
||||
Statistics* /* statistics */) {
|
||||
return new BlockContents(std::move(contents));
|
||||
}
|
||||
|
||||
static uint32_t GetNumRestarts(const BlockContents& /* contents */) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class BlocklikeTraits<Block> {
|
||||
public:
|
||||
static Block* Create(BlockContents&& contents, SequenceNumber global_seqno,
|
||||
size_t read_amp_bytes_per_bit, Statistics* statistics) {
|
||||
return new Block(std::move(contents), global_seqno, read_amp_bytes_per_bit,
|
||||
statistics);
|
||||
}
|
||||
|
||||
static uint32_t GetNumRestarts(const Block& block) {
|
||||
return block.NumRestarts();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TBlocklike>
|
||||
Status BlockBasedTable::GetDataBlockFromCache(
|
||||
const Slice& block_cache_key, const Slice& compressed_block_cache_key,
|
||||
@@ -1749,7 +1711,8 @@ Status BlockBasedTable::GetDataBlockFromCache(
|
||||
std::unique_ptr<TBlocklike> block_holder(
|
||||
BlocklikeTraits<TBlocklike>::Create(
|
||||
std::move(contents), rep_->get_global_seqno(block_type),
|
||||
read_amp_bytes_per_bit, statistics)); // uncompressed block
|
||||
read_amp_bytes_per_bit, statistics,
|
||||
rep_->blocks_definitely_zstd_compressed)); // uncompressed block
|
||||
|
||||
if (block_cache != nullptr && block_holder->own_bytes() &&
|
||||
read_options.fill_cache) {
|
||||
@@ -1757,9 +1720,6 @@ Status BlockBasedTable::GetDataBlockFromCache(
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
s = block_cache->Insert(block_cache_key, block_holder.get(), charge,
|
||||
&DeleteCachedEntry<TBlocklike>, &cache_handle);
|
||||
#ifndef NDEBUG
|
||||
block_cache->TEST_mark_as_data_block(block_cache_key, charge);
|
||||
#endif // NDEBUG
|
||||
if (s.ok()) {
|
||||
assert(cache_handle != nullptr);
|
||||
block->SetCachedValue(block_holder.release(), block_cache,
|
||||
@@ -1823,11 +1783,11 @@ Status BlockBasedTable::PutDataBlockToCache(
|
||||
|
||||
block_holder.reset(BlocklikeTraits<TBlocklike>::Create(
|
||||
std::move(uncompressed_block_contents), seq_no, read_amp_bytes_per_bit,
|
||||
statistics));
|
||||
statistics, rep_->blocks_definitely_zstd_compressed));
|
||||
} else {
|
||||
block_holder.reset(BlocklikeTraits<TBlocklike>::Create(
|
||||
std::move(*raw_block_contents), seq_no, read_amp_bytes_per_bit,
|
||||
statistics));
|
||||
statistics, rep_->blocks_definitely_zstd_compressed));
|
||||
}
|
||||
|
||||
// Insert compressed block into compressed block cache.
|
||||
@@ -1863,9 +1823,6 @@ Status BlockBasedTable::PutDataBlockToCache(
|
||||
s = block_cache->Insert(block_cache_key, block_holder.get(), charge,
|
||||
&DeleteCachedEntry<TBlocklike>, &cache_handle,
|
||||
priority);
|
||||
#ifndef NDEBUG
|
||||
block_cache->TEST_mark_as_data_block(block_cache_key, charge);
|
||||
#endif // NDEBUG
|
||||
if (s.ok()) {
|
||||
assert(cache_handle != nullptr);
|
||||
cached_block->SetCachedValue(block_holder.release(), block_cache,
|
||||
@@ -1914,86 +1871,6 @@ std::unique_ptr<FilterBlockReader> BlockBasedTable::CreateFilterBlockReader(
|
||||
}
|
||||
}
|
||||
|
||||
CachableEntry<UncompressionDict> BlockBasedTable::GetUncompressionDict(
|
||||
FilePrefetchBuffer* prefetch_buffer, bool no_io, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) const {
|
||||
if (!rep_->table_options.cache_index_and_filter_blocks) {
|
||||
// block cache is either disabled or not used for meta-blocks. In either
|
||||
// case, BlockBasedTableReader is the owner of the uncompression dictionary.
|
||||
return {rep_->uncompression_dict.get(), nullptr /* cache */,
|
||||
nullptr /* cache_handle */, false /* own_value */};
|
||||
}
|
||||
if (rep_->compression_dict_handle.IsNull()) {
|
||||
return CachableEntry<UncompressionDict>();
|
||||
}
|
||||
char cache_key_buf[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
auto cache_key =
|
||||
GetCacheKey(rep_->cache_key_prefix, rep_->cache_key_prefix_size,
|
||||
rep_->compression_dict_handle, cache_key_buf);
|
||||
auto cache_handle =
|
||||
GetEntryFromCache(rep_->table_options.block_cache.get(), cache_key,
|
||||
BlockType::kCompressionDictionary, get_context);
|
||||
UncompressionDict* dict = nullptr;
|
||||
bool is_cache_hit = false;
|
||||
size_t usage = 0;
|
||||
if (cache_handle != nullptr) {
|
||||
dict = reinterpret_cast<UncompressionDict*>(
|
||||
rep_->table_options.block_cache->Value(cache_handle));
|
||||
is_cache_hit = true;
|
||||
usage = dict->ApproximateMemoryUsage();
|
||||
} else if (no_io) {
|
||||
// Do not invoke any io.
|
||||
} else {
|
||||
std::unique_ptr<const BlockContents> compression_dict_block;
|
||||
Status s =
|
||||
ReadCompressionDictBlock(prefetch_buffer, &compression_dict_block);
|
||||
if (s.ok()) {
|
||||
assert(compression_dict_block != nullptr);
|
||||
// TODO(ajkr): find a way to avoid the `compression_dict_block` data copy
|
||||
std::unique_ptr<UncompressionDict> uncompression_dict(
|
||||
new UncompressionDict(compression_dict_block->data.ToString(),
|
||||
rep_->blocks_definitely_zstd_compressed,
|
||||
rep_->ioptions.statistics));
|
||||
usage = uncompression_dict->ApproximateMemoryUsage();
|
||||
s = rep_->table_options.block_cache->Insert(
|
||||
cache_key, uncompression_dict.get(), usage,
|
||||
&DeleteCachedUncompressionDictEntry, &cache_handle,
|
||||
rep_->table_options.cache_index_and_filter_blocks_with_high_priority
|
||||
? Cache::Priority::HIGH
|
||||
: Cache::Priority::LOW);
|
||||
|
||||
if (s.ok()) {
|
||||
UpdateCacheInsertionMetrics(BlockType::kCompressionDictionary,
|
||||
get_context, usage);
|
||||
dict = uncompression_dict.release();
|
||||
} else {
|
||||
RecordTick(rep_->ioptions.statistics, BLOCK_CACHE_ADD_FAILURES);
|
||||
assert(dict == nullptr);
|
||||
assert(cache_handle == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (block_cache_tracer_ && block_cache_tracer_->is_tracing_enabled() &&
|
||||
lookup_context) {
|
||||
// Avoid making copy of block_key and cf_name when constructing the access
|
||||
// record.
|
||||
BlockCacheTraceRecord access_record(
|
||||
rep_->ioptions.env->NowMicros(),
|
||||
/*block_key=*/"", TraceType::kBlockTraceUncompressionDictBlock,
|
||||
/*block_size=*/usage, rep_->cf_id_for_tracing(),
|
||||
/*cf_name=*/"", rep_->level_for_tracing(),
|
||||
rep_->sst_number_for_tracing(), lookup_context->caller, is_cache_hit,
|
||||
/*no_insert=*/no_io, lookup_context->get_id,
|
||||
lookup_context->get_from_user_specified_snapshot,
|
||||
/*referenced_key=*/"");
|
||||
block_cache_tracer_->WriteBlockAccess(access_record, cache_key,
|
||||
rep_->cf_name_for_tracing(),
|
||||
lookup_context->referenced_key);
|
||||
}
|
||||
return {dict, cache_handle ? rep_->table_options.block_cache.get() : nullptr,
|
||||
cache_handle, false /* own_value */};
|
||||
}
|
||||
|
||||
// disable_prefix_seek should be set to true when prefix_extractor found in SST
|
||||
// differs from the one in mutable_cf_options and index type is HashBasedIndex
|
||||
InternalIteratorBase<IndexValue>* BlockBasedTable::NewIndexIterator(
|
||||
@@ -2028,17 +1905,26 @@ TBlockIter* BlockBasedTable::NewDataBlockIterator(
|
||||
return iter;
|
||||
}
|
||||
|
||||
const bool no_io = (ro.read_tier == kBlockCacheTier);
|
||||
auto uncompression_dict_storage =
|
||||
GetUncompressionDict(prefetch_buffer, no_io, get_context, lookup_context);
|
||||
const UncompressionDict& uncompression_dict =
|
||||
uncompression_dict_storage.GetValue() == nullptr
|
||||
? UncompressionDict::GetEmptyDict()
|
||||
: *uncompression_dict_storage.GetValue();
|
||||
CachableEntry<UncompressionDict> uncompression_dict;
|
||||
if (rep_->uncompression_dict_reader) {
|
||||
const bool no_io = (ro.read_tier == kBlockCacheTier);
|
||||
s = rep_->uncompression_dict_reader->GetOrReadUncompressionDictionary(
|
||||
prefetch_buffer, no_io, get_context, lookup_context,
|
||||
&uncompression_dict);
|
||||
if (!s.ok()) {
|
||||
iter->Invalidate(s);
|
||||
return iter;
|
||||
}
|
||||
}
|
||||
|
||||
const UncompressionDict& dict = uncompression_dict.GetValue()
|
||||
? *uncompression_dict.GetValue()
|
||||
: UncompressionDict::GetEmptyDict();
|
||||
|
||||
CachableEntry<Block> block;
|
||||
s = RetrieveBlock(prefetch_buffer, ro, handle, uncompression_dict, &block,
|
||||
block_type, get_context, lookup_context, for_compaction);
|
||||
s = RetrieveBlock(prefetch_buffer, ro, handle, dict, &block, block_type,
|
||||
get_context, lookup_context, for_compaction,
|
||||
/* use_cache */ true);
|
||||
|
||||
if (!s.ok()) {
|
||||
assert(block.IsEmpty());
|
||||
@@ -2201,8 +2087,10 @@ Status BlockBasedTable::GetDataBlockFromCache(
|
||||
GetContext* get_context) const {
|
||||
BlockCacheLookupContext lookup_data_block_context(
|
||||
TableReaderCaller::kUserMultiGet);
|
||||
assert(block_type == BlockType::kData);
|
||||
Status s = RetrieveBlock(nullptr, ro, handle, uncompression_dict, block,
|
||||
block_type, get_context, &lookup_data_block_context);
|
||||
block_type, get_context, &lookup_data_block_context,
|
||||
/* for_compaction */ false, /* use_cache */ true);
|
||||
if (s.IsIncomplete()) {
|
||||
s = Status::OK();
|
||||
}
|
||||
@@ -2268,7 +2156,9 @@ Status BlockBasedTable::MaybeReadBlockAndLoadToCache(
|
||||
if (block_entry->GetValue() == nullptr && !no_io && ro.fill_cache) {
|
||||
Statistics* statistics = rep_->ioptions.statistics;
|
||||
const bool maybe_compressed =
|
||||
block_type != BlockType::kFilter && rep_->blocks_maybe_compressed;
|
||||
block_type != BlockType::kFilter &&
|
||||
block_type != BlockType::kCompressionDictionary &&
|
||||
rep_->blocks_maybe_compressed;
|
||||
const bool do_uncompress = maybe_compressed && !block_cache_compressed;
|
||||
CompressionType raw_block_comp_type;
|
||||
BlockContents raw_block_contents;
|
||||
@@ -2321,6 +2211,9 @@ Status BlockBasedTable::MaybeReadBlockAndLoadToCache(
|
||||
case BlockType::kFilter:
|
||||
trace_block_type = TraceType::kBlockTraceFilterBlock;
|
||||
break;
|
||||
case BlockType::kCompressionDictionary:
|
||||
trace_block_type = TraceType::kBlockTraceUncompressionDictBlock;
|
||||
break;
|
||||
case BlockType::kRangeDeletion:
|
||||
trace_block_type = TraceType::kBlockTraceRangeDeletionBlock;
|
||||
break;
|
||||
@@ -2380,15 +2273,11 @@ Status BlockBasedTable::MaybeReadBlockAndLoadToCache(
|
||||
// handles - A vector of block handles. Some of them me be NULL handles
|
||||
// scratch - An optional contiguous buffer to read compressed blocks into
|
||||
void BlockBasedTable::MaybeLoadBlocksToCache(
|
||||
const ReadOptions& options,
|
||||
const MultiGetRange* batch,
|
||||
const autovector<BlockHandle, MultiGetContext::MAX_BATCH_SIZE>* handles,
|
||||
const ReadOptions& options, const MultiGetRange* batch,
|
||||
const autovector<BlockHandle, MultiGetContext::MAX_BATCH_SIZE>* handles,
|
||||
autovector<Status, MultiGetContext::MAX_BATCH_SIZE>* statuses,
|
||||
autovector<
|
||||
CachableEntry<Block>, MultiGetContext::MAX_BATCH_SIZE>* results,
|
||||
char* scratch,
|
||||
const UncompressionDict& uncompression_dict) const {
|
||||
|
||||
autovector<CachableEntry<Block>, MultiGetContext::MAX_BATCH_SIZE>* results,
|
||||
char* scratch, const UncompressionDict& uncompression_dict) const {
|
||||
RandomAccessFileReader* file = rep_->file.get();
|
||||
const Footer& footer = rep_->footer;
|
||||
const ImmutableCFOptions& ioptions = rep_->ioptions;
|
||||
@@ -2407,9 +2296,11 @@ void BlockBasedTable::MaybeLoadBlocksToCache(
|
||||
continue;
|
||||
}
|
||||
|
||||
(*statuses)[idx_in_batch] = RetrieveBlock(nullptr, options, handle,
|
||||
uncompression_dict, &(*results)[idx_in_batch], BlockType::kData,
|
||||
mget_iter->get_context, &lookup_data_block_context);
|
||||
(*statuses)[idx_in_batch] =
|
||||
RetrieveBlock(nullptr, options, handle, uncompression_dict,
|
||||
&(*results)[idx_in_batch], BlockType::kData,
|
||||
mget_iter->get_context, &lookup_data_block_context,
|
||||
/* for_compaction */ false, /* use_cache */ true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -2536,15 +2427,12 @@ Status BlockBasedTable::RetrieveBlock(
|
||||
const BlockHandle& handle, const UncompressionDict& uncompression_dict,
|
||||
CachableEntry<TBlocklike>* block_entry, BlockType block_type,
|
||||
GetContext* get_context, BlockCacheLookupContext* lookup_context,
|
||||
bool for_compaction) const {
|
||||
bool for_compaction, bool use_cache) const {
|
||||
assert(block_entry);
|
||||
assert(block_entry->IsEmpty());
|
||||
|
||||
Status s;
|
||||
if (rep_->table_options.cache_index_and_filter_blocks ||
|
||||
(block_type != BlockType::kFilter &&
|
||||
block_type != BlockType::kCompressionDictionary &&
|
||||
block_type != BlockType::kIndex)) {
|
||||
if (use_cache) {
|
||||
s = MaybeReadBlockAndLoadToCache(prefetch_buffer, ro, handle,
|
||||
uncompression_dict, block_entry,
|
||||
block_type, get_context, lookup_context,
|
||||
@@ -2568,7 +2456,9 @@ Status BlockBasedTable::RetrieveBlock(
|
||||
}
|
||||
|
||||
const bool maybe_compressed =
|
||||
block_type != BlockType::kFilter && rep_->blocks_maybe_compressed;
|
||||
block_type != BlockType::kFilter &&
|
||||
block_type != BlockType::kCompressionDictionary &&
|
||||
rep_->blocks_maybe_compressed;
|
||||
const bool do_uncompress = maybe_compressed;
|
||||
std::unique_ptr<TBlocklike> block;
|
||||
|
||||
@@ -2583,7 +2473,8 @@ Status BlockBasedTable::RetrieveBlock(
|
||||
block_type == BlockType::kData
|
||||
? rep_->table_options.read_amp_bytes_per_bit
|
||||
: 0,
|
||||
GetMemoryAllocator(rep_->table_options), for_compaction);
|
||||
GetMemoryAllocator(rep_->table_options), for_compaction,
|
||||
rep_->blocks_definitely_zstd_compressed);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -2603,14 +2494,21 @@ template Status BlockBasedTable::RetrieveBlock<BlockContents>(
|
||||
const BlockHandle& handle, const UncompressionDict& uncompression_dict,
|
||||
CachableEntry<BlockContents>* block_entry, BlockType block_type,
|
||||
GetContext* get_context, BlockCacheLookupContext* lookup_context,
|
||||
bool for_compaction) const;
|
||||
bool for_compaction, bool use_cache) const;
|
||||
|
||||
template Status BlockBasedTable::RetrieveBlock<Block>(
|
||||
FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
|
||||
const BlockHandle& handle, const UncompressionDict& uncompression_dict,
|
||||
CachableEntry<Block>* block_entry, BlockType block_type,
|
||||
GetContext* get_context, BlockCacheLookupContext* lookup_context,
|
||||
bool for_compaction) const;
|
||||
bool for_compaction, bool use_cache) const;
|
||||
|
||||
template Status BlockBasedTable::RetrieveBlock<UncompressionDict>(
|
||||
FilePrefetchBuffer* prefetch_buffer, const ReadOptions& ro,
|
||||
const BlockHandle& handle, const UncompressionDict& uncompression_dict,
|
||||
CachableEntry<UncompressionDict>* block_entry, BlockType block_type,
|
||||
GetContext* get_context, BlockCacheLookupContext* lookup_context,
|
||||
bool for_compaction, bool use_cache) const;
|
||||
|
||||
BlockBasedTable::PartitionedIndexIteratorState::PartitionedIndexIteratorState(
|
||||
const BlockBasedTable* table,
|
||||
@@ -2825,11 +2723,21 @@ void BlockBasedTableIterator<TBlockIter, TValue>::SeekImpl(
|
||||
// Index contains the first key of the block, and it's >= target.
|
||||
// We can defer reading the block.
|
||||
is_at_first_key_from_index_ = true;
|
||||
// ResetDataIter() will invalidate block_iter_. Thus, there is no need to
|
||||
// call CheckDataBlockWithinUpperBound() to check for iterate_upper_bound
|
||||
// as that will be done later when the data block is actually read.
|
||||
ResetDataIter();
|
||||
} else {
|
||||
// Need to use the data block.
|
||||
if (!same_block) {
|
||||
InitDataBlock();
|
||||
} else {
|
||||
// When the user does a reseek, the iterate_upper_bound might have
|
||||
// changed. CheckDataBlockWithinUpperBound() needs to be called
|
||||
// explicitly if the reseek ends up in the same data block.
|
||||
// If the reseek ends up in a different block, InitDataBlock() will do
|
||||
// the iterator upper bound check.
|
||||
CheckDataBlockWithinUpperBound();
|
||||
}
|
||||
|
||||
if (target) {
|
||||
@@ -2840,7 +2748,6 @@ void BlockBasedTableIterator<TBlockIter, TValue>::SeekImpl(
|
||||
FindKeyForward();
|
||||
}
|
||||
|
||||
CheckDataBlockWithinUpperBound();
|
||||
CheckOutOfBound();
|
||||
|
||||
if (target) {
|
||||
@@ -3504,12 +3411,21 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
|
||||
{
|
||||
MultiGetRange data_block_range(sst_file_range, sst_file_range.begin(),
|
||||
sst_file_range.end());
|
||||
auto uncompression_dict_storage = GetUncompressionDict(
|
||||
nullptr, no_io, sst_file_range.begin()->get_context, &lookup_context);
|
||||
const UncompressionDict& uncompression_dict =
|
||||
uncompression_dict_storage.GetValue() == nullptr
|
||||
? UncompressionDict::GetEmptyDict()
|
||||
: *uncompression_dict_storage.GetValue();
|
||||
|
||||
CachableEntry<UncompressionDict> uncompression_dict;
|
||||
Status uncompression_dict_status;
|
||||
if (rep_->uncompression_dict_reader) {
|
||||
uncompression_dict_status =
|
||||
rep_->uncompression_dict_reader->GetOrReadUncompressionDictionary(
|
||||
nullptr /* prefetch_buffer */, no_io,
|
||||
sst_file_range.begin()->get_context, &lookup_context,
|
||||
&uncompression_dict);
|
||||
}
|
||||
|
||||
const UncompressionDict& dict = uncompression_dict.GetValue()
|
||||
? *uncompression_dict.GetValue()
|
||||
: UncompressionDict::GetEmptyDict();
|
||||
|
||||
size_t total_len = 0;
|
||||
ReadOptions ro = read_options;
|
||||
ro.read_tier = kBlockCacheTier;
|
||||
@@ -3535,6 +3451,14 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
|
||||
sst_file_range.SkipKey(miter);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!uncompression_dict_status.ok()) {
|
||||
*(miter->s) = uncompression_dict_status;
|
||||
data_block_range.SkipKey(miter);
|
||||
sst_file_range.SkipKey(miter);
|
||||
continue;
|
||||
}
|
||||
|
||||
statuses.emplace_back();
|
||||
results.emplace_back();
|
||||
if (v.handle.offset() == offset) {
|
||||
@@ -3545,8 +3469,8 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
|
||||
}
|
||||
offset = v.handle.offset();
|
||||
BlockHandle handle = v.handle;
|
||||
Status s = GetDataBlockFromCache(ro, handle, uncompression_dict,
|
||||
&(results.back()), BlockType::kData, miter->get_context);
|
||||
Status s = GetDataBlockFromCache(ro, handle, dict, &(results.back()),
|
||||
BlockType::kData, miter->get_context);
|
||||
if (s.ok() && !results.back().IsEmpty()) {
|
||||
// Found it in the cache. Add NULL handle to indicate there is
|
||||
// nothing to read from disk
|
||||
@@ -3577,9 +3501,8 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
|
||||
block_buf.reset(scratch);
|
||||
}
|
||||
}
|
||||
MaybeLoadBlocksToCache(read_options,
|
||||
&data_block_range, &block_handles, &statuses, &results,
|
||||
scratch, uncompression_dict);
|
||||
MaybeLoadBlocksToCache(read_options, &data_block_range, &block_handles,
|
||||
&statuses, &results, scratch, dict);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4018,10 +3941,11 @@ Status BlockBasedTable::CreateIndexReader(
|
||||
uint64_t BlockBasedTable::ApproximateOffsetOf(const Slice& key,
|
||||
TableReaderCaller caller) {
|
||||
BlockCacheLookupContext context(caller);
|
||||
std::unique_ptr<InternalIteratorBase<IndexValue>> index_iter(
|
||||
IndexBlockIter iiter_on_stack;
|
||||
auto index_iter =
|
||||
NewIndexIterator(ReadOptions(), /*need_upper_bound_check=*/false,
|
||||
/*input_iter=*/nullptr, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/&context));
|
||||
/*input_iter=*/&iiter_on_stack, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/&context);
|
||||
|
||||
index_iter->Seek(key);
|
||||
uint64_t result;
|
||||
@@ -4041,6 +3965,11 @@ uint64_t BlockBasedTable::ApproximateOffsetOf(const Slice& key,
|
||||
result = rep_->footer.metaindex_handle().offset();
|
||||
}
|
||||
}
|
||||
|
||||
if (index_iter != &iiter_on_stack) {
|
||||
delete index_iter;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4185,23 +4114,27 @@ Status BlockBasedTable::DumpTable(WritableFile* out_file) {
|
||||
}
|
||||
|
||||
// Output compression dictionary
|
||||
if (!rep_->compression_dict_handle.IsNull()) {
|
||||
std::unique_ptr<const BlockContents> compression_dict_block;
|
||||
s = ReadCompressionDictBlock(nullptr /* prefetch_buffer */,
|
||||
&compression_dict_block);
|
||||
if (rep_->uncompression_dict_reader) {
|
||||
CachableEntry<UncompressionDict> uncompression_dict;
|
||||
s = rep_->uncompression_dict_reader->GetOrReadUncompressionDictionary(
|
||||
nullptr /* prefetch_buffer */, false /* no_io */,
|
||||
nullptr /* get_context */, nullptr /* lookup_context */,
|
||||
&uncompression_dict);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
assert(compression_dict_block != nullptr);
|
||||
auto compression_dict = compression_dict_block->data;
|
||||
|
||||
assert(uncompression_dict.GetValue());
|
||||
|
||||
const Slice& raw_dict = uncompression_dict.GetValue()->GetRawDict();
|
||||
out_file->Append(
|
||||
"Compression Dictionary:\n"
|
||||
"--------------------------------------\n");
|
||||
out_file->Append(" size (bytes): ");
|
||||
out_file->Append(rocksdb::ToString(compression_dict.size()));
|
||||
out_file->Append(rocksdb::ToString(raw_dict.size()));
|
||||
out_file->Append("\n\n");
|
||||
out_file->Append(" HEX ");
|
||||
out_file->Append(compression_dict.ToString(true).c_str());
|
||||
out_file->Append(raw_dict.ToString(true).c_str());
|
||||
out_file->Append("\n\n");
|
||||
}
|
||||
|
||||
@@ -4227,29 +4160,6 @@ Status BlockBasedTable::DumpTable(WritableFile* out_file) {
|
||||
return s;
|
||||
}
|
||||
|
||||
void BlockBasedTable::Close() {
|
||||
if (rep_->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// cleanup index, filter, and compression dictionary blocks
|
||||
// to avoid accessing dangling pointers
|
||||
if (!rep_->table_options.no_block_cache) {
|
||||
if (!rep_->compression_dict_handle.IsNull()) {
|
||||
// Get the compression dictionary block key
|
||||
char cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
auto key =
|
||||
GetCacheKey(rep_->cache_key_prefix, rep_->cache_key_prefix_size,
|
||||
rep_->compression_dict_handle, cache_key);
|
||||
|
||||
Cache* const cache = rep_->table_options.block_cache.get();
|
||||
cache->Erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
rep_->closed = true;
|
||||
}
|
||||
|
||||
Status BlockBasedTable::DumpIndexBlock(WritableFile* out_file) {
|
||||
out_file->Append(
|
||||
"Index Details:\n"
|
||||
@@ -4425,15 +4335,4 @@ void BlockBasedTable::DumpKeyValue(const Slice& key, const Slice& value,
|
||||
out_file->Append("\n ------\n");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void DeleteCachedUncompressionDictEntry(const Slice& /*key*/, void* value) {
|
||||
UncompressionDict* dict = reinterpret_cast<UncompressionDict*>(value);
|
||||
RecordTick(dict->statistics(), BLOCK_CACHE_COMPRESSION_DICT_BYTES_EVICT,
|
||||
dict->ApproximateMemoryUsage());
|
||||
delete dict;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "table/block_based/block_type.h"
|
||||
#include "table/block_based/cachable_entry.h"
|
||||
#include "table/block_based/filter_block.h"
|
||||
#include "table/block_based/uncompression_dict_reader.h"
|
||||
#include "table/format.h"
|
||||
#include "table/get_context.h"
|
||||
#include "table/multiget_context.h"
|
||||
@@ -176,8 +177,6 @@ class BlockBasedTable : public TableReader {
|
||||
|
||||
Status VerifyChecksum(TableReaderCaller caller) override;
|
||||
|
||||
void Close() override;
|
||||
|
||||
~BlockBasedTable();
|
||||
|
||||
bool TEST_FilterBlockInCache() const;
|
||||
@@ -242,8 +241,11 @@ class BlockBasedTable : public TableReader {
|
||||
|
||||
template <typename TBlocklike>
|
||||
friend class FilterBlockReaderCommon;
|
||||
|
||||
friend class PartitionIndexReader;
|
||||
|
||||
friend class UncompressionDictReader;
|
||||
|
||||
protected:
|
||||
Rep* rep_;
|
||||
explicit BlockBasedTable(Rep* rep, BlockCacheTracer* const block_cache_tracer)
|
||||
@@ -297,7 +299,7 @@ class BlockBasedTable : public TableReader {
|
||||
CachableEntry<TBlocklike>* block_entry,
|
||||
BlockType block_type, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
bool for_compaction = false) const;
|
||||
bool for_compaction, bool use_cache) const;
|
||||
|
||||
Status GetDataBlockFromCache(
|
||||
const ReadOptions& ro, const BlockHandle& handle,
|
||||
@@ -307,19 +309,18 @@ class BlockBasedTable : public TableReader {
|
||||
|
||||
void MaybeLoadBlocksToCache(
|
||||
const ReadOptions& options, const MultiGetRange* batch,
|
||||
const autovector<BlockHandle, MultiGetContext::MAX_BATCH_SIZE>* handles,
|
||||
const autovector<BlockHandle, MultiGetContext::MAX_BATCH_SIZE>* handles,
|
||||
autovector<Status, MultiGetContext::MAX_BATCH_SIZE>* statuses,
|
||||
autovector<
|
||||
CachableEntry<Block>, MultiGetContext::MAX_BATCH_SIZE>* results,
|
||||
autovector<CachableEntry<Block>, MultiGetContext::MAX_BATCH_SIZE>*
|
||||
results,
|
||||
char* scratch, const UncompressionDict& uncompression_dict) const;
|
||||
|
||||
CachableEntry<UncompressionDict> GetUncompressionDict(
|
||||
FilePrefetchBuffer* prefetch_buffer, bool no_io, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context) const;
|
||||
|
||||
// Get the iterator from the index reader.
|
||||
// If input_iter is not set, return new Iterator
|
||||
// If input_iter is set, update it and return it as Iterator
|
||||
//
|
||||
// If input_iter is not set, return a new Iterator.
|
||||
// If input_iter is set, try to update it and return it as Iterator.
|
||||
// However note that in some cases the returned iterator may be different
|
||||
// from input_iter. In such case the returned iterator should be freed.
|
||||
//
|
||||
// Note: ErrorIterator with Status::Incomplete shall be returned if all the
|
||||
// following conditions are met:
|
||||
@@ -413,9 +414,6 @@ class BlockBasedTable : public TableReader {
|
||||
InternalIterator* meta_iter,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
BlockCacheLookupContext* lookup_context);
|
||||
Status ReadCompressionDictBlock(
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
std::unique_ptr<const BlockContents>* compression_dict_block) const;
|
||||
Status PrefetchIndexAndFilterBlocks(
|
||||
FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter,
|
||||
BlockBasedTable* new_table, bool prefetch_all,
|
||||
@@ -511,7 +509,7 @@ struct BlockBasedTable::Rep {
|
||||
|
||||
std::unique_ptr<IndexReader> index_reader;
|
||||
std::unique_ptr<FilterBlockReader> filter;
|
||||
std::unique_ptr<UncompressionDict> uncompression_dict;
|
||||
std::unique_ptr<UncompressionDictReader> uncompression_dict_reader;
|
||||
|
||||
enum class FilterType {
|
||||
kNoFilter,
|
||||
@@ -563,7 +561,6 @@ struct BlockBasedTable::Rep {
|
||||
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 {
|
||||
@@ -605,8 +602,7 @@ class BlockBasedTableIterator : public InternalIteratorBase<TValue> {
|
||||
const SliceTransform* prefix_extractor,
|
||||
BlockType block_type, TableReaderCaller caller,
|
||||
size_t compaction_readahead_size = 0)
|
||||
: InternalIteratorBase<TValue>(false),
|
||||
table_(table),
|
||||
: table_(table),
|
||||
read_options_(read_options),
|
||||
icomp_(icomp),
|
||||
user_comparator_(icomp.user_comparator()),
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace rocksdb {
|
||||
template <typename TBlocklike>
|
||||
Status FilterBlockReaderCommon<TBlocklike>::ReadFilterBlock(
|
||||
const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
|
||||
const ReadOptions& read_options, GetContext* get_context,
|
||||
const ReadOptions& read_options, bool use_cache, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
CachableEntry<TBlocklike>* filter_block) {
|
||||
PERF_TIMER_GUARD(read_filter_block_nanos);
|
||||
@@ -28,7 +28,8 @@ Status FilterBlockReaderCommon<TBlocklike>::ReadFilterBlock(
|
||||
const Status s =
|
||||
table->RetrieveBlock(prefetch_buffer, read_options, rep->filter_handle,
|
||||
UncompressionDict::GetEmptyDict(), filter_block,
|
||||
BlockType::kFilter, get_context, lookup_context);
|
||||
BlockType::kFilter, get_context, lookup_context,
|
||||
/* for_compaction */ false, use_cache);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -52,6 +53,14 @@ bool FilterBlockReaderCommon<TBlocklike>::whole_key_filtering() const {
|
||||
return table_->get_rep()->whole_key_filtering;
|
||||
}
|
||||
|
||||
template <typename TBlocklike>
|
||||
bool FilterBlockReaderCommon<TBlocklike>::cache_filter_blocks() const {
|
||||
assert(table_);
|
||||
assert(table_->get_rep());
|
||||
|
||||
return table_->get_rep()->table_options.cache_index_and_filter_blocks;
|
||||
}
|
||||
|
||||
template <typename TBlocklike>
|
||||
Status FilterBlockReaderCommon<TBlocklike>::GetOrReadFilterBlock(
|
||||
bool no_io, GetContext* get_context,
|
||||
@@ -70,7 +79,8 @@ Status FilterBlockReaderCommon<TBlocklike>::GetOrReadFilterBlock(
|
||||
}
|
||||
|
||||
return ReadFilterBlock(table_, nullptr /* prefetch_buffer */, read_options,
|
||||
get_context, lookup_context, filter_block);
|
||||
cache_filter_blocks(), get_context, lookup_context,
|
||||
filter_block);
|
||||
}
|
||||
|
||||
template <typename TBlocklike>
|
||||
|
||||
@@ -31,7 +31,7 @@ class FilterBlockReaderCommon : public FilterBlockReader {
|
||||
protected:
|
||||
static Status ReadFilterBlock(const BlockBasedTable* table,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
const ReadOptions& read_options,
|
||||
const ReadOptions& read_options, bool use_cache,
|
||||
GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
CachableEntry<TBlocklike>* filter_block);
|
||||
@@ -39,6 +39,7 @@ class FilterBlockReaderCommon : public FilterBlockReader {
|
||||
const BlockBasedTable* table() const { return table_; }
|
||||
const SliceTransform* table_prefix_extractor() const;
|
||||
bool whole_key_filtering() const;
|
||||
bool cache_filter_blocks() const;
|
||||
|
||||
Status GetOrReadFilterBlock(bool no_io, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
|
||||
@@ -134,8 +134,8 @@ std::unique_ptr<FilterBlockReader> FullFilterBlockReader::Create(
|
||||
CachableEntry<BlockContents> filter_block;
|
||||
if (prefetch || !use_cache) {
|
||||
const Status s = ReadFilterBlock(table, prefetch_buffer, ReadOptions(),
|
||||
nullptr /* get_context */, lookup_context,
|
||||
&filter_block);
|
||||
use_cache, nullptr /* get_context */,
|
||||
lookup_context, &filter_block);
|
||||
if (!s.ok()) {
|
||||
return std::unique_ptr<FilterBlockReader>();
|
||||
}
|
||||
|
||||
@@ -44,10 +44,7 @@ class TestFilterBitsBuilder : public FilterBitsBuilder {
|
||||
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;
|
||||
}
|
||||
: BlockBasedTable(rep, nullptr /* block_cache_tracer */) {}
|
||||
};
|
||||
|
||||
class TestFilterBitsReader : public FilterBitsReader {
|
||||
@@ -116,7 +113,6 @@ class PluginFullFilterBlockTest : public testing::Test {
|
||||
: 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;
|
||||
@@ -210,7 +206,6 @@ class FullFilterBlockTest : public testing::Test {
|
||||
: ioptions_(options_),
|
||||
env_options_(options_),
|
||||
icomp_(options_.comparator) {
|
||||
table_options_.no_block_cache = true;
|
||||
table_options_.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
|
||||
constexpr bool skip_filters = false;
|
||||
|
||||
@@ -133,8 +133,8 @@ std::unique_ptr<FilterBlockReader> PartitionedFilterBlockReader::Create(
|
||||
CachableEntry<Block> filter_block;
|
||||
if (prefetch || !use_cache) {
|
||||
const Status s = ReadFilterBlock(table, prefetch_buffer, ReadOptions(),
|
||||
nullptr /* get_context */, lookup_context,
|
||||
&filter_block);
|
||||
use_cache, nullptr /* get_context */,
|
||||
lookup_context, &filter_block);
|
||||
if (!s.ok()) {
|
||||
return std::unique_ptr<FilterBlockReader>();
|
||||
}
|
||||
@@ -192,7 +192,12 @@ BlockHandle PartitionedFilterBlockReader::GetFilterPartitionHandle(
|
||||
index_key_includes_seq(), index_value_is_full());
|
||||
iter.Seek(entry);
|
||||
if (UNLIKELY(!iter.Valid())) {
|
||||
return BlockHandle(0, 0);
|
||||
// entry is larger than all the keys. However its prefix might still be
|
||||
// present in the last partition. If this is called by PrefixMayMatch this
|
||||
// is necessary for correct behavior. Otherwise it is unnecessary but safe.
|
||||
// Assuming this is an unlikely case for full key search, the performance
|
||||
// overhead should be negligible.
|
||||
iter.SeekToLast();
|
||||
}
|
||||
assert(iter.Valid());
|
||||
BlockHandle fltr_blk_handle = iter.value().handle;
|
||||
@@ -226,7 +231,8 @@ Status PartitionedFilterBlockReader::GetFilterPartitionBlock(
|
||||
const Status s =
|
||||
table()->RetrieveBlock(prefetch_buffer, read_options, fltr_blk_handle,
|
||||
UncompressionDict::GetEmptyDict(), filter_block,
|
||||
BlockType::kFilter, get_context, lookup_context);
|
||||
BlockType::kFilter, get_context, lookup_context,
|
||||
/* for_compaction */ false, /* use_cache */ true);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -324,7 +330,7 @@ void PartitionedFilterBlockReader::CacheDependencies(bool pin) {
|
||||
|
||||
prefetch_buffer.reset(new FilePrefetchBuffer());
|
||||
s = prefetch_buffer->Prefetch(rep->file.get(), prefetch_off,
|
||||
static_cast<size_t>(prefetch_len));
|
||||
static_cast<size_t>(prefetch_len));
|
||||
|
||||
// After prefetch, read the partitions one by one
|
||||
ReadOptions read_options;
|
||||
|
||||
@@ -27,7 +27,6 @@ class MockedBlockBasedTable : public BlockBasedTable {
|
||||
MockedBlockBasedTable(Rep* rep, PartitionedIndexBuilder* pib)
|
||||
: BlockBasedTable(rep, /*block_cache_tracer=*/nullptr) {
|
||||
// Initialize what Open normally does as much as necessary for the test
|
||||
rep->cache_key_prefix_size = 10;
|
||||
rep->index_key_includes_seq = pib->seperator_is_key_plus_seq();
|
||||
rep->index_value_is_full = !pib->get_use_value_delta_encoding();
|
||||
}
|
||||
@@ -67,9 +66,6 @@ class PartitionedFilterBlockTest
|
||||
env_options_(options_),
|
||||
icomp_(options_.comparator) {
|
||||
table_options_.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
table_options_.no_block_cache = true; // Otherwise BlockBasedTable::Close
|
||||
// will access variable that are not
|
||||
// initialized in our mocked version
|
||||
table_options_.format_version = GetParam();
|
||||
table_options_.index_block_restart_interval = 3;
|
||||
}
|
||||
@@ -331,7 +327,7 @@ TEST_P(PartitionedFilterBlockTest, SamePrefixInMultipleBlocks) {
|
||||
std::unique_ptr<PartitionedIndexBuilder> pib(NewIndexBuilder());
|
||||
std::unique_ptr<PartitionedFilterBlockBuilder> builder(
|
||||
NewBuilder(pib.get(), prefix_extractor.get()));
|
||||
const std::string pkeys[3] = {"p-key1", "p-key2", "p-key3"};
|
||||
const std::string pkeys[3] = {"p-key10", "p-key20", "p-key30"};
|
||||
builder->Add(pkeys[0]);
|
||||
CutABlock(pib.get(), pkeys[0], pkeys[1]);
|
||||
builder->Add(pkeys[1]);
|
||||
@@ -348,6 +344,16 @@ TEST_P(PartitionedFilterBlockTest, SamePrefixInMultipleBlocks) {
|
||||
/*no_io=*/false, &ikey_slice, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
}
|
||||
// Non-existent keys but with the same prefix
|
||||
const std::string pnonkeys[4] = {"p-key9", "p-key11", "p-key21", "p-key31"};
|
||||
for (auto key : pnonkeys) {
|
||||
auto ikey = InternalKey(key, 0, ValueType::kTypeValue);
|
||||
const Slice ikey_slice = Slice(*ikey.rep());
|
||||
ASSERT_TRUE(reader->PrefixMayMatch(
|
||||
prefix_extractor->Transform(key), prefix_extractor.get(), kNotValid,
|
||||
/*no_io=*/false, &ikey_slice, /*get_context=*/nullptr,
|
||||
/*lookup_context=*/nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PartitionedFilterBlockTest, OneBlockPerKey) {
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// 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 "table/block_based/uncompression_dict_reader.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "util/compression.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
Status UncompressionDictReader::Create(
|
||||
const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
|
||||
bool use_cache, bool prefetch, bool pin,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
std::unique_ptr<UncompressionDictReader>* uncompression_dict_reader) {
|
||||
assert(table);
|
||||
assert(table->get_rep());
|
||||
assert(!pin || prefetch);
|
||||
assert(uncompression_dict_reader);
|
||||
|
||||
CachableEntry<UncompressionDict> uncompression_dict;
|
||||
if (prefetch || !use_cache) {
|
||||
const Status s = ReadUncompressionDictionary(
|
||||
table, prefetch_buffer, ReadOptions(), use_cache,
|
||||
nullptr /* get_context */, lookup_context, &uncompression_dict);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (use_cache && !pin) {
|
||||
uncompression_dict.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
uncompression_dict_reader->reset(
|
||||
new UncompressionDictReader(table, std::move(uncompression_dict)));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status UncompressionDictReader::ReadUncompressionDictionary(
|
||||
const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
|
||||
const ReadOptions& read_options, bool use_cache, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
CachableEntry<UncompressionDict>* uncompression_dict) {
|
||||
// TODO: add perf counter for compression dictionary read time
|
||||
|
||||
assert(table);
|
||||
assert(uncompression_dict);
|
||||
assert(uncompression_dict->IsEmpty());
|
||||
|
||||
const BlockBasedTable::Rep* const rep = table->get_rep();
|
||||
assert(rep);
|
||||
assert(!rep->compression_dict_handle.IsNull());
|
||||
|
||||
const Status s = table->RetrieveBlock(
|
||||
prefetch_buffer, read_options, rep->compression_dict_handle,
|
||||
UncompressionDict::GetEmptyDict(), uncompression_dict,
|
||||
BlockType::kCompressionDictionary, get_context, lookup_context,
|
||||
/* for_compaction */ false, use_cache);
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
rep->ioptions.info_log,
|
||||
"Encountered error while reading data from compression dictionary "
|
||||
"block %s",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status UncompressionDictReader::GetOrReadUncompressionDictionary(
|
||||
FilePrefetchBuffer* prefetch_buffer, bool no_io, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
CachableEntry<UncompressionDict>* uncompression_dict) const {
|
||||
assert(uncompression_dict);
|
||||
|
||||
if (!uncompression_dict_.IsEmpty()) {
|
||||
uncompression_dict->SetUnownedValue(uncompression_dict_.GetValue());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
ReadOptions read_options;
|
||||
if (no_io) {
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
}
|
||||
|
||||
return ReadUncompressionDictionary(table_, prefetch_buffer, read_options,
|
||||
cache_dictionary_blocks(), get_context,
|
||||
lookup_context, uncompression_dict);
|
||||
}
|
||||
|
||||
size_t UncompressionDictReader::ApproximateMemoryUsage() const {
|
||||
assert(!uncompression_dict_.GetOwnValue() ||
|
||||
uncompression_dict_.GetValue() != nullptr);
|
||||
size_t usage = uncompression_dict_.GetOwnValue()
|
||||
? uncompression_dict_.GetValue()->ApproximateMemoryUsage()
|
||||
: 0;
|
||||
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
usage += malloc_usable_size(const_cast<UncompressionDictReader*>(this));
|
||||
#else
|
||||
usage += sizeof(*this);
|
||||
#endif // ROCKSDB_MALLOC_USABLE_SIZE
|
||||
|
||||
return usage;
|
||||
}
|
||||
|
||||
bool UncompressionDictReader::cache_dictionary_blocks() const {
|
||||
assert(table_);
|
||||
assert(table_->get_rep());
|
||||
|
||||
return table_->get_rep()->table_options.cache_index_and_filter_blocks;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include "table/block_based/cachable_entry.h"
|
||||
#include "table/format.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class BlockBasedTable;
|
||||
struct BlockCacheLookupContext;
|
||||
class FilePrefetchBuffer;
|
||||
class GetContext;
|
||||
struct ReadOptions;
|
||||
struct UncompressionDict;
|
||||
|
||||
// Provides access to the uncompression dictionary regardless of whether
|
||||
// it is owned by the reader or stored in the cache, or whether it is pinned
|
||||
// in the cache or not.
|
||||
class UncompressionDictReader {
|
||||
public:
|
||||
static Status Create(
|
||||
const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
|
||||
bool use_cache, bool prefetch, bool pin,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
std::unique_ptr<UncompressionDictReader>* uncompression_dict_reader);
|
||||
|
||||
Status GetOrReadUncompressionDictionary(
|
||||
FilePrefetchBuffer* prefetch_buffer, bool no_io, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
CachableEntry<UncompressionDict>* uncompression_dict) const;
|
||||
|
||||
size_t ApproximateMemoryUsage() const;
|
||||
|
||||
private:
|
||||
UncompressionDictReader(const BlockBasedTable* t,
|
||||
CachableEntry<UncompressionDict>&& uncompression_dict)
|
||||
: table_(t), uncompression_dict_(std::move(uncompression_dict)) {
|
||||
assert(table_);
|
||||
}
|
||||
|
||||
bool cache_dictionary_blocks() const;
|
||||
|
||||
static Status ReadUncompressionDictionary(
|
||||
const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
|
||||
const ReadOptions& read_options, bool use_cache, GetContext* get_context,
|
||||
BlockCacheLookupContext* lookup_context,
|
||||
CachableEntry<UncompressionDict>* uncompression_dict);
|
||||
|
||||
const BlockBasedTable* table_;
|
||||
CachableEntry<UncompressionDict> uncompression_dict_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -136,6 +136,8 @@ class GetContext {
|
||||
|
||||
void ReportCounters();
|
||||
|
||||
bool has_callback() const { return callback_ != nullptr; }
|
||||
|
||||
uint64_t get_tracing_get_id() const { return tracing_get_id_; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -25,8 +25,8 @@ struct IterateResult {
|
||||
template <class TValue>
|
||||
class InternalIteratorBase : public Cleanable {
|
||||
public:
|
||||
InternalIteratorBase() : is_mutable_(true) {}
|
||||
InternalIteratorBase(bool _is_mutable) : is_mutable_(_is_mutable) {}
|
||||
InternalIteratorBase() {}
|
||||
|
||||
virtual ~InternalIteratorBase() {}
|
||||
|
||||
// An iterator is either positioned at a key/value pair, or
|
||||
@@ -144,7 +144,6 @@ class InternalIteratorBase : public Cleanable {
|
||||
virtual Status GetProperty(std::string /*prop_name*/, std::string* /*prop*/) {
|
||||
return Status::NotSupported("");
|
||||
}
|
||||
bool is_mutable() const { return is_mutable_; }
|
||||
|
||||
protected:
|
||||
void SeekForPrevImpl(const Slice& target, const Comparator* cmp) {
|
||||
|
||||
@@ -73,7 +73,6 @@ class IteratorWrapperBase {
|
||||
}
|
||||
void Prev() { assert(iter_); iter_->Prev(); Update(); }
|
||||
void Seek(const Slice& k) {
|
||||
TEST_SYNC_POINT("IteratorWrapper::Seek:0");
|
||||
assert(iter_);
|
||||
iter_->Seek(k);
|
||||
Update();
|
||||
|
||||
@@ -127,29 +127,14 @@ class MergingIterator : public InternalIterator {
|
||||
}
|
||||
|
||||
void Seek(const Slice& target) override {
|
||||
bool is_increasing_reseek = false;
|
||||
if (current_ != nullptr && direction_ == kForward && status_.ok() &&
|
||||
comparator_->Compare(target, key()) >= 0) {
|
||||
is_increasing_reseek = true;
|
||||
}
|
||||
ClearHeaps();
|
||||
status_ = Status::OK();
|
||||
for (auto& child : children_) {
|
||||
// If upper bound never changes, we can skip Seek() for
|
||||
// the !Valid() case too, but people do hack the code to change
|
||||
// upper bound between Seek(), so it's not a good idea to break
|
||||
// the API.
|
||||
// If DBIter is used on top of merging iterator, we probably
|
||||
// can skip mutable child iterators if they are invalid too,
|
||||
// but it's a less clean API. We can optimize for it later if
|
||||
// needed.
|
||||
if (!is_increasing_reseek || !child.Valid() ||
|
||||
comparator_->Compare(target, child.key()) > 0 ||
|
||||
child.iter()->is_mutable()) {
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_child_seek_time);
|
||||
child.Seek(target);
|
||||
PERF_COUNTER_ADD(seek_child_seek_count, 1);
|
||||
}
|
||||
PERF_COUNTER_ADD(seek_child_seek_count, 1);
|
||||
|
||||
if (child.Valid()) {
|
||||
assert(child.status().ok());
|
||||
|
||||
@@ -124,8 +124,6 @@ class TableReader {
|
||||
virtual Status VerifyChecksum(TableReaderCaller /*caller*/) {
|
||||
return Status::NotSupported("VerifyChecksum() not supported");
|
||||
}
|
||||
|
||||
virtual void Close() {}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -2889,176 +2889,6 @@ TEST_P(BlockBasedTableTest, BlockReadCountTest) {
|
||||
}
|
||||
}
|
||||
|
||||
// A wrapper around LRICache that also keeps track of data blocks (in contrast
|
||||
// with the objects) in the cache. The class is very simple and can be used only
|
||||
// for trivial tests.
|
||||
class MockCache : public LRUCache {
|
||||
public:
|
||||
MockCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio)
|
||||
: LRUCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio) {}
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override {
|
||||
// Replace the deleter with our own so that we keep track of data blocks
|
||||
// erased from the cache
|
||||
deleters_[key.ToString()] = deleter;
|
||||
return ShardedCache::Insert(key, value, charge, &MockDeleter, handle,
|
||||
priority);
|
||||
}
|
||||
// This is called by the application right after inserting a data block
|
||||
void TEST_mark_as_data_block(const Slice& key, size_t charge) override {
|
||||
marked_data_in_cache_[key.ToString()] = charge;
|
||||
marked_size_ += charge;
|
||||
}
|
||||
using DeleterFunc = void (*)(const Slice& key, void* value);
|
||||
static std::map<std::string, DeleterFunc> deleters_;
|
||||
static std::map<std::string, size_t> marked_data_in_cache_;
|
||||
static size_t marked_size_;
|
||||
static void MockDeleter(const Slice& key, void* value) {
|
||||
// If the item was marked for being data block, decrease its usage from the
|
||||
// total data block usage of the cache
|
||||
if (marked_data_in_cache_.find(key.ToString()) !=
|
||||
marked_data_in_cache_.end()) {
|
||||
marked_size_ -= marked_data_in_cache_[key.ToString()];
|
||||
}
|
||||
// Then call the origianl deleter
|
||||
assert(deleters_.find(key.ToString()) != deleters_.end());
|
||||
auto deleter = deleters_[key.ToString()];
|
||||
deleter(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
size_t MockCache::marked_size_ = 0;
|
||||
std::map<std::string, MockCache::DeleterFunc> MockCache::deleters_;
|
||||
std::map<std::string, size_t> MockCache::marked_data_in_cache_;
|
||||
|
||||
// Block cache can contain raw data blocks as well as general objects. If an
|
||||
// object depends on the table to be live, it then must be destructed before the
|
||||
// table is closed. This test makes sure that the only items remains in the
|
||||
// cache after the table is closed are raw data blocks.
|
||||
TEST_P(BlockBasedTableTest, NoObjectInCacheAfterTableClose) {
|
||||
std::vector<CompressionType> compression_types{kNoCompression};
|
||||
|
||||
// The following are the compression library versions supporting compression
|
||||
// dictionaries. See the test case CacheCompressionDict in the
|
||||
// DBBlockCacheTest suite.
|
||||
#ifdef ZLIB
|
||||
compression_types.push_back(kZlibCompression);
|
||||
#endif // ZLIB
|
||||
#if LZ4_VERSION_NUMBER >= 10400
|
||||
compression_types.push_back(kLZ4Compression);
|
||||
compression_types.push_back(kLZ4HCCompression);
|
||||
#endif // LZ4_VERSION_NUMBER >= 10400
|
||||
#if ZSTD_VERSION_NUMBER >= 500
|
||||
compression_types.push_back(kZSTD);
|
||||
#endif // ZSTD_VERSION_NUMBER >= 500
|
||||
|
||||
for (int level: {-1, 0, 1, 10}) {
|
||||
for (auto index_type :
|
||||
{BlockBasedTableOptions::IndexType::kBinarySearch,
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch}) {
|
||||
for (bool block_based_filter : {true, false}) {
|
||||
for (bool partition_filter : {true, false}) {
|
||||
if (partition_filter &&
|
||||
(block_based_filter ||
|
||||
index_type !=
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch)) {
|
||||
continue;
|
||||
}
|
||||
for (bool index_and_filter_in_cache : {true, false}) {
|
||||
for (bool pin_l0 : {true, false}) {
|
||||
for (bool pin_top_level : {true, false}) {
|
||||
if (pin_l0 && !index_and_filter_in_cache) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto compression_type : compression_types) {
|
||||
for (uint32_t max_dict_bytes : {0, 1 << 14}) {
|
||||
if (compression_type == kNoCompression && max_dict_bytes)
|
||||
continue;
|
||||
|
||||
// Create a table
|
||||
Options opt;
|
||||
std::unique_ptr<InternalKeyComparator> ikc;
|
||||
ikc.reset(new test::PlainInternalKeyComparator(
|
||||
opt.comparator));
|
||||
opt.compression = compression_type;
|
||||
opt.compression_opts.max_dict_bytes = max_dict_bytes;
|
||||
BlockBasedTableOptions table_options =
|
||||
GetBlockBasedTableOptions();
|
||||
table_options.block_size = 1024;
|
||||
table_options.index_type = index_type;
|
||||
table_options.pin_l0_filter_and_index_blocks_in_cache =
|
||||
pin_l0;
|
||||
table_options.pin_top_level_index_and_filter =
|
||||
pin_top_level;
|
||||
table_options.partition_filters = partition_filter;
|
||||
table_options.cache_index_and_filter_blocks =
|
||||
index_and_filter_in_cache;
|
||||
// big enough so we don't ever lose cached values.
|
||||
table_options.block_cache = std::make_shared<MockCache>(
|
||||
16 * 1024 * 1024, 4, false, 0.0);
|
||||
table_options.filter_policy.reset(
|
||||
rocksdb::NewBloomFilterPolicy(10, block_based_filter));
|
||||
opt.table_factory.reset(NewBlockBasedTableFactory(
|
||||
table_options));
|
||||
|
||||
bool convert_to_internal_key = false;
|
||||
TableConstructor c(BytewiseComparator(),
|
||||
convert_to_internal_key, level);
|
||||
std::string user_key = "k01";
|
||||
std::string key =
|
||||
InternalKey(user_key, 0, kTypeValue).Encode().ToString();
|
||||
c.Add(key, "hello");
|
||||
std::vector<std::string> keys;
|
||||
stl_wrappers::KVMap kvmap;
|
||||
const ImmutableCFOptions ioptions(opt);
|
||||
const MutableCFOptions moptions(opt);
|
||||
c.Finish(opt, ioptions, moptions, table_options, *ikc,
|
||||
&keys, &kvmap);
|
||||
|
||||
// Doing a read to make index/filter loaded into the cache
|
||||
auto table_reader =
|
||||
dynamic_cast<BlockBasedTable*>(c.GetTableReader());
|
||||
PinnableSlice value;
|
||||
GetContext get_context(opt.comparator, nullptr, nullptr,
|
||||
nullptr, GetContext::kNotFound, user_key, &value,
|
||||
nullptr, nullptr, nullptr, nullptr);
|
||||
InternalKey ikey(user_key, 0, kTypeValue);
|
||||
auto s = table_reader->Get(ReadOptions(), key, &get_context,
|
||||
moptions.prefix_extractor.get());
|
||||
ASSERT_EQ(get_context.State(), GetContext::kFound);
|
||||
ASSERT_STREQ(value.data(), "hello");
|
||||
|
||||
// Close the table
|
||||
c.ResetTableReader();
|
||||
|
||||
auto usage = table_options.block_cache->GetUsage();
|
||||
auto pinned_usage =
|
||||
table_options.block_cache->GetPinnedUsage();
|
||||
// The only usage must be for marked data blocks
|
||||
ASSERT_EQ(usage, MockCache::marked_size_);
|
||||
// There must be some pinned data since PinnableSlice has
|
||||
// not released them yet
|
||||
ASSERT_GT(pinned_usage, 0);
|
||||
// Release pinnable slice reousrces
|
||||
value.Reset();
|
||||
pinned_usage = table_options.block_cache->GetPinnedUsage();
|
||||
ASSERT_EQ(pinned_usage, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // level
|
||||
}
|
||||
|
||||
TEST_P(BlockBasedTableTest, BlockCacheLeak) {
|
||||
// Check that when we reopen a table we don't lose access to blocks already
|
||||
// in the cache. This test checks whether the Table actually makes use of the
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
@@ -0,0 +1,864 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
import gc
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
from os import path
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
kSampleSize = 16 # The sample size used when performing eviction.
|
||||
kMicrosInSecond = 1000000
|
||||
kSecondsInMinute = 60
|
||||
kSecondsInHour = 3600
|
||||
|
||||
|
||||
class TraceRecord:
|
||||
"""
|
||||
A trace record represents a block access.
|
||||
It holds the same struct as BlockCacheTraceRecord in
|
||||
trace_replay/block_cache_tracer.h
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
access_time,
|
||||
block_id,
|
||||
block_type,
|
||||
block_size,
|
||||
cf_id,
|
||||
cf_name,
|
||||
level,
|
||||
fd,
|
||||
caller,
|
||||
no_insert,
|
||||
get_id,
|
||||
key_id,
|
||||
kv_size,
|
||||
is_hit,
|
||||
):
|
||||
self.access_time = access_time
|
||||
self.block_id = block_id
|
||||
self.block_type = block_type
|
||||
self.block_size = block_size
|
||||
self.cf_id = cf_id
|
||||
self.cf_name = cf_name
|
||||
self.level = level
|
||||
self.fd = fd
|
||||
self.caller = caller
|
||||
if no_insert == 1:
|
||||
self.no_insert = True
|
||||
else:
|
||||
self.no_insert = False
|
||||
self.get_id = get_id
|
||||
self.key_id = key_id
|
||||
self.kv_size = kv_size
|
||||
if is_hit == 1:
|
||||
self.is_hit = True
|
||||
else:
|
||||
self.is_hit = False
|
||||
|
||||
|
||||
class CacheEntry:
|
||||
"""A cache entry stored in the cache."""
|
||||
|
||||
def __init__(self, value_size, cf_id, level, block_type, access_number):
|
||||
self.value_size = value_size
|
||||
self.last_access_number = access_number
|
||||
self.num_hits = 0
|
||||
self.cf_id = 0
|
||||
self.level = level
|
||||
self.block_type = block_type
|
||||
|
||||
def __repr__(self):
|
||||
"""Debug string."""
|
||||
return "s={},last={},hits={},cf={},l={},bt={}".format(
|
||||
self.value_size,
|
||||
self.last_access_number,
|
||||
self.num_hits,
|
||||
self.cf_id,
|
||||
self.level,
|
||||
self.block_type,
|
||||
)
|
||||
|
||||
|
||||
class HashEntry:
|
||||
"""A hash entry stored in a hash table."""
|
||||
|
||||
def __init__(self, key, hash, value):
|
||||
self.key = key
|
||||
self.hash = hash
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return "k={},h={},v=[{}]".format(self.key, self.hash, self.value)
|
||||
|
||||
|
||||
class HashTable:
|
||||
"""
|
||||
A custom implementation of hash table to support fast random sampling.
|
||||
It is closed hashing and uses chaining to resolve hash conflicts.
|
||||
It grows/shrinks the hash table upon insertion/deletion to support
|
||||
fast lookups and random samplings.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.table = [None] * 32
|
||||
self.elements = 0
|
||||
|
||||
def random_sample(self, sample_size):
|
||||
"""Randomly sample 'sample_size' hash entries from the table."""
|
||||
samples = []
|
||||
index = random.randint(0, len(self.table))
|
||||
pos = (index + 1) % len(self.table)
|
||||
searches = 0
|
||||
# Starting from index, adding hash entries to the sample list until
|
||||
# sample_size is met or we ran out of entries.
|
||||
while pos != index and len(samples) < sample_size:
|
||||
if self.table[pos] is not None:
|
||||
for i in range(len(self.table[pos])):
|
||||
if self.table[pos][i] is None:
|
||||
continue
|
||||
samples.append(self.table[pos][i])
|
||||
if len(samples) > sample_size:
|
||||
break
|
||||
pos += 1
|
||||
pos = pos % len(self.table)
|
||||
searches += 1
|
||||
return samples
|
||||
|
||||
def insert(self, key, hash, value):
|
||||
"""
|
||||
Insert a hash entry in the table. Replace the old entry if it already
|
||||
exists.
|
||||
"""
|
||||
self.grow()
|
||||
inserted = False
|
||||
index = hash % len(self.table)
|
||||
if self.table[index] is None:
|
||||
self.table[index] = []
|
||||
for i in range(len(self.table[index])):
|
||||
if self.table[index][i] is not None:
|
||||
if (
|
||||
self.table[index][i].hash == hash
|
||||
and self.table[index][i].key == key
|
||||
):
|
||||
# The entry already exists in the table.
|
||||
self.table[index][i] = HashEntry(key, hash, value)
|
||||
return
|
||||
continue
|
||||
self.table[index][i] = HashEntry(key, hash, value)
|
||||
inserted = True
|
||||
break
|
||||
if not inserted:
|
||||
self.table[index].append(HashEntry(key, hash, value))
|
||||
self.elements += 1
|
||||
|
||||
def resize(self, new_size):
|
||||
if new_size == len(self.table):
|
||||
return
|
||||
if new_size == 0:
|
||||
return
|
||||
if self.elements < 100:
|
||||
return
|
||||
new_table = [None] * new_size
|
||||
# Copy 'self.table' to new_table.
|
||||
for i in range(len(self.table)):
|
||||
entries = self.table[i]
|
||||
if entries is None:
|
||||
continue
|
||||
for j in range(len(entries)):
|
||||
if entries[j] is None:
|
||||
continue
|
||||
index = entries[j].hash % new_size
|
||||
if new_table[index] is None:
|
||||
new_table[index] = []
|
||||
new_table[index].append(entries[j])
|
||||
self.table = new_table
|
||||
del new_table
|
||||
# Manually call python gc here to free the memory as 'self.table'
|
||||
# might be very large.
|
||||
gc.collect()
|
||||
|
||||
def grow(self):
|
||||
if self.elements < len(self.table):
|
||||
return
|
||||
new_size = int(len(self.table) * 1.2)
|
||||
self.resize(new_size)
|
||||
|
||||
def delete(self, key, hash):
|
||||
index = hash % len(self.table)
|
||||
entries = self.table[index]
|
||||
deleted = False
|
||||
if entries is None:
|
||||
return
|
||||
for i in range(len(entries)):
|
||||
if (
|
||||
entries[i] is not None
|
||||
and entries[i].hash == hash
|
||||
and entries[i].key == key
|
||||
):
|
||||
entries[i] = None
|
||||
self.elements -= 1
|
||||
deleted = True
|
||||
break
|
||||
if deleted:
|
||||
self.shrink()
|
||||
|
||||
def shrink(self):
|
||||
if self.elements * 2 >= len(self.table):
|
||||
return
|
||||
new_size = int(len(self.table) * 0.7)
|
||||
self.resize(new_size)
|
||||
|
||||
def lookup(self, key, hash):
|
||||
index = hash % len(self.table)
|
||||
entries = self.table[index]
|
||||
if entries is None:
|
||||
return None
|
||||
for entry in entries:
|
||||
if entry is not None and entry.hash == hash and entry.key == key:
|
||||
return entry.value
|
||||
return None
|
||||
|
||||
|
||||
class MissRatioStats:
|
||||
def __init__(self, time_unit):
|
||||
self.num_misses = 0
|
||||
self.num_accesses = 0
|
||||
self.time_unit = time_unit
|
||||
self.time_misses = {}
|
||||
self.time_accesses = {}
|
||||
|
||||
def update_metrics(self, access_time, is_hit):
|
||||
access_time /= kMicrosInSecond * self.time_unit
|
||||
self.num_accesses += 1
|
||||
if access_time not in self.time_accesses:
|
||||
self.time_accesses[access_time] = 0
|
||||
self.time_accesses[access_time] += 1
|
||||
if not is_hit:
|
||||
self.num_misses += 1
|
||||
if access_time not in self.time_misses:
|
||||
self.time_misses[access_time] = 0
|
||||
self.time_misses[access_time] += 1
|
||||
|
||||
def reset_counter(self):
|
||||
self.num_misses = 0
|
||||
self.num_accesses = 0
|
||||
|
||||
def miss_ratio(self):
|
||||
return float(self.num_misses) * 100.0 / float(self.num_accesses)
|
||||
|
||||
def write_miss_timeline(self, cache_type, cache_size, result_dir, start, end):
|
||||
start /= kMicrosInSecond * self.time_unit
|
||||
end /= kMicrosInSecond * self.time_unit
|
||||
header_file_path = "{}/header-ml-miss-timeline-{}-{}-{}".format(
|
||||
result_dir, self.time_unit, cache_type, cache_size
|
||||
)
|
||||
if not path.exists(header_file_path):
|
||||
with open(header_file_path, "w+") as header_file:
|
||||
header = "time"
|
||||
for trace_time in range(start, end):
|
||||
header += ",{}".format(trace_time)
|
||||
header_file.write(header + "\n")
|
||||
file_path = "{}/data-ml-miss-timeline-{}-{}-{}".format(
|
||||
result_dir, self.time_unit, cache_type, cache_size
|
||||
)
|
||||
with open(file_path, "w+") as file:
|
||||
row = "{}".format(cache_type)
|
||||
for trace_time in range(start, end):
|
||||
row += ",{}".format(self.time_misses.get(trace_time, 0))
|
||||
file.write(row + "\n")
|
||||
|
||||
def write_miss_ratio_timeline(self, cache_type, cache_size, result_dir, start, end):
|
||||
start /= kMicrosInSecond * self.time_unit
|
||||
end /= kMicrosInSecond * self.time_unit
|
||||
header_file_path = "{}/header-ml-miss-ratio-timeline-{}-{}-{}".format(
|
||||
result_dir, self.time_unit, cache_type, cache_size
|
||||
)
|
||||
if not path.exists(header_file_path):
|
||||
with open(header_file_path, "w+") as header_file:
|
||||
header = "time"
|
||||
for trace_time in range(start, end):
|
||||
header += ",{}".format(trace_time)
|
||||
header_file.write(header + "\n")
|
||||
file_path = "{}/data-ml-miss-ratio-timeline-{}-{}-{}".format(
|
||||
result_dir, self.time_unit, cache_type, cache_size
|
||||
)
|
||||
with open(file_path, "w+") as file:
|
||||
row = "{}".format(cache_type)
|
||||
for trace_time in range(start, end):
|
||||
naccesses = self.time_accesses.get(trace_time, 0)
|
||||
miss_ratio = 0
|
||||
if naccesses > 0:
|
||||
miss_ratio = float(
|
||||
self.time_misses.get(trace_time, 0) * 100.0
|
||||
) / float(naccesses)
|
||||
row += ",{0:.2f}".format(miss_ratio)
|
||||
file.write(row + "\n")
|
||||
|
||||
|
||||
class PolicyStats:
|
||||
def __init__(self, time_unit, policies):
|
||||
self.time_selected_polices = {}
|
||||
self.time_accesses = {}
|
||||
self.policy_names = {}
|
||||
self.time_unit = time_unit
|
||||
for i in range(len(policies)):
|
||||
self.policy_names[i] = policies[i].policy_name()
|
||||
|
||||
def update_metrics(self, access_time, selected_policy):
|
||||
access_time /= kMicrosInSecond * self.time_unit
|
||||
if access_time not in self.time_accesses:
|
||||
self.time_accesses[access_time] = 0
|
||||
self.time_accesses[access_time] += 1
|
||||
if access_time not in self.time_selected_polices:
|
||||
self.time_selected_polices[access_time] = {}
|
||||
policy_name = self.policy_names[selected_policy]
|
||||
if policy_name not in self.time_selected_polices[access_time]:
|
||||
self.time_selected_polices[access_time][policy_name] = 0
|
||||
self.time_selected_polices[access_time][policy_name] += 1
|
||||
|
||||
def write_policy_timeline(self, cache_type, cache_size, result_dir, start, end):
|
||||
start /= kMicrosInSecond * self.time_unit
|
||||
end /= kMicrosInSecond * self.time_unit
|
||||
header_file_path = "{}/header-ml-policy-timeline-{}-{}-{}".format(
|
||||
result_dir, self.time_unit, cache_type, cache_size
|
||||
)
|
||||
if not path.exists(header_file_path):
|
||||
with open(header_file_path, "w+") as header_file:
|
||||
header = "time"
|
||||
for trace_time in range(start, end):
|
||||
header += ",{}".format(trace_time)
|
||||
header_file.write(header + "\n")
|
||||
file_path = "{}/data-ml-policy-timeline-{}-{}-{}".format(
|
||||
result_dir, self.time_unit, cache_type, cache_size
|
||||
)
|
||||
with open(file_path, "w+") as file:
|
||||
for policy in self.policy_names:
|
||||
policy_name = self.policy_names[policy]
|
||||
row = "{}-{}".format(cache_type, policy_name)
|
||||
for trace_time in range(start, end):
|
||||
row += ",{}".format(
|
||||
self.time_selected_polices.get(trace_time, {}).get(
|
||||
policy_name, 0
|
||||
)
|
||||
)
|
||||
file.write(row + "\n")
|
||||
|
||||
def write_policy_ratio_timeline(
|
||||
self, cache_type, cache_size, file_path, start, end
|
||||
):
|
||||
start /= kMicrosInSecond * self.time_unit
|
||||
end /= kMicrosInSecond * self.time_unit
|
||||
header_file_path = "{}/header-ml-policy-ratio-timeline-{}-{}-{}".format(
|
||||
result_dir, self.time_unit, cache_type, cache_size
|
||||
)
|
||||
if not path.exists(header_file_path):
|
||||
with open(header_file_path, "w+") as header_file:
|
||||
header = "time"
|
||||
for trace_time in range(start, end):
|
||||
header += ",{}".format(trace_time)
|
||||
header_file.write(header + "\n")
|
||||
file_path = "{}/data-ml-policy-ratio-timeline-{}-{}-{}".format(
|
||||
result_dir, self.time_unit, cache_type, cache_size
|
||||
)
|
||||
with open(file_path, "w+") as file:
|
||||
for policy in self.policy_names:
|
||||
policy_name = self.policy_names[policy]
|
||||
row = "{}-{}".format(cache_type, policy_name)
|
||||
for trace_time in range(start, end):
|
||||
naccesses = self.time_accesses.get(trace_time, 0)
|
||||
ratio = 0
|
||||
if naccesses > 0:
|
||||
ratio = float(
|
||||
self.time_selected_polices.get(trace_time, {}).get(
|
||||
policy_name, 0
|
||||
)
|
||||
* 100.0
|
||||
) / float(naccesses)
|
||||
row += ",{0:.2f}".format(ratio)
|
||||
file.write(row + "\n")
|
||||
|
||||
|
||||
class Policy(object):
|
||||
"""
|
||||
A policy maintains a set of evicted keys. It returns a reward of one to
|
||||
itself if it has not evicted a missing key. Otherwise, it gives itself 0
|
||||
reward.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.evicted_keys = {}
|
||||
|
||||
def evict(self, key, max_size):
|
||||
self.evicted_keys[key] = 0
|
||||
|
||||
def delete(self, key):
|
||||
self.evicted_keys.pop(key, None)
|
||||
|
||||
def prioritize_samples(self, samples):
|
||||
raise NotImplementedError
|
||||
|
||||
def policy_name(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def generate_reward(self, key):
|
||||
if key in self.evicted_keys:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
class LRUPolicy(Policy):
|
||||
def prioritize_samples(self, samples):
|
||||
return sorted(
|
||||
samples,
|
||||
cmp=lambda e1, e2: e1.value.last_access_number
|
||||
- e2.value.last_access_number,
|
||||
)
|
||||
|
||||
def policy_name(self):
|
||||
return "lru"
|
||||
|
||||
|
||||
class MRUPolicy(Policy):
|
||||
def prioritize_samples(self, samples):
|
||||
return sorted(
|
||||
samples,
|
||||
cmp=lambda e1, e2: e2.value.last_access_number
|
||||
- e1.value.last_access_number,
|
||||
)
|
||||
|
||||
def policy_name(self):
|
||||
return "mru"
|
||||
|
||||
|
||||
class LFUPolicy(Policy):
|
||||
def prioritize_samples(self, samples):
|
||||
return sorted(samples, cmp=lambda e1, e2: e1.value.num_hits - e2.value.num_hits)
|
||||
|
||||
def policy_name(self):
|
||||
return "lfu"
|
||||
|
||||
|
||||
class MLCache(object):
|
||||
def __init__(self, cache_size, enable_cache_row_key, policies):
|
||||
self.cache_size = cache_size
|
||||
self.used_size = 0
|
||||
self.miss_ratio_stats = MissRatioStats(kSecondsInMinute)
|
||||
self.policy_stats = PolicyStats(kSecondsInMinute, policies)
|
||||
self.per_hour_miss_ratio_stats = MissRatioStats(kSecondsInHour)
|
||||
self.per_hour_policy_stats = PolicyStats(kSecondsInHour, policies)
|
||||
self.table = HashTable()
|
||||
self.enable_cache_row_key = enable_cache_row_key
|
||||
self.get_id_row_key_map = {}
|
||||
self.policies = policies
|
||||
|
||||
def _lookup(self, key, hash):
|
||||
value = self.table.lookup(key, hash)
|
||||
if value is not None:
|
||||
value.last_access_number = self.miss_ratio_stats.num_accesses
|
||||
value.num_hits += 1
|
||||
return True
|
||||
return False
|
||||
|
||||
def _select_policy(self, trace_record, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def cache_name(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _evict(self, policy_index, value_size):
|
||||
# Randomly sample n entries.
|
||||
samples = self.table.random_sample(kSampleSize)
|
||||
samples = self.policies[policy_index].prioritize_samples(samples)
|
||||
for hash_entry in samples:
|
||||
self.used_size -= hash_entry.value.value_size
|
||||
self.table.delete(hash_entry.key, hash_entry.hash)
|
||||
self.policies[policy_index].evict(
|
||||
key=hash_entry.key, max_size=self.table.elements
|
||||
)
|
||||
if self.used_size + value_size <= self.cache_size:
|
||||
break
|
||||
|
||||
def _insert(self, trace_record, key, hash, value_size):
|
||||
if value_size > self.cache_size:
|
||||
return
|
||||
policy_index = self._select_policy(trace_record, key)
|
||||
self.policies[policy_index].delete(key)
|
||||
self.policy_stats.update_metrics(trace_record.access_time, policy_index)
|
||||
self.per_hour_policy_stats.update_metrics(
|
||||
trace_record.access_time, policy_index
|
||||
)
|
||||
while self.used_size + value_size > self.cache_size:
|
||||
self._evict(policy_index, value_size)
|
||||
self.table.insert(
|
||||
key,
|
||||
hash,
|
||||
CacheEntry(
|
||||
value_size,
|
||||
trace_record.cf_id,
|
||||
trace_record.level,
|
||||
trace_record.block_type,
|
||||
self.miss_ratio_stats.num_accesses,
|
||||
),
|
||||
)
|
||||
self.used_size += value_size
|
||||
|
||||
def _access_kv(self, trace_record, key, hash, value_size, no_insert):
|
||||
if self._lookup(key, hash):
|
||||
return True
|
||||
if not no_insert and value_size > 0:
|
||||
self._insert(trace_record, key, hash, value_size)
|
||||
return False
|
||||
|
||||
def _update_stats(self, access_time, is_hit):
|
||||
self.miss_ratio_stats.update_metrics(access_time, is_hit)
|
||||
self.per_hour_miss_ratio_stats.update_metrics(access_time, is_hit)
|
||||
|
||||
def access(self, trace_record):
|
||||
assert self.used_size <= self.cache_size
|
||||
if (
|
||||
self.enable_cache_row_key
|
||||
and trace_record.caller == 1
|
||||
and trace_record.key_id != 0
|
||||
and trace_record.get_id != 0
|
||||
):
|
||||
# This is a get request.
|
||||
if trace_record.get_id not in self.get_id_row_key_map:
|
||||
self.get_id_row_key_map[trace_record.get_id] = {}
|
||||
self.get_id_row_key_map[trace_record.get_id]["h"] = False
|
||||
if self.get_id_row_key_map[trace_record.get_id]["h"]:
|
||||
# We treat future accesses as hits since this get request
|
||||
# completes.
|
||||
self._update_stats(trace_record.access_time, is_hit=True)
|
||||
return
|
||||
if trace_record.key_id not in self.get_id_row_key_map[trace_record.get_id]:
|
||||
# First time seen this key.
|
||||
is_hit = self._access_kv(
|
||||
trace_record,
|
||||
key="g{}".format(trace_record.key_id),
|
||||
hash=trace_record.key_id,
|
||||
value_size=trace_record.kv_size,
|
||||
no_insert=False,
|
||||
)
|
||||
inserted = False
|
||||
if trace_record.kv_size > 0:
|
||||
inserted = True
|
||||
self.get_id_row_key_map[trace_record.get_id][
|
||||
trace_record.key_id
|
||||
] = inserted
|
||||
self.get_id_row_key_map[trace_record.get_id]["h"] = is_hit
|
||||
if self.get_id_row_key_map[trace_record.get_id]["h"]:
|
||||
# We treat future accesses as hits since this get request
|
||||
# completes.
|
||||
self._update_stats(trace_record.access_time, is_hit=True)
|
||||
return
|
||||
# Access its blocks.
|
||||
is_hit = self._access_kv(
|
||||
trace_record,
|
||||
key="b{}".format(trace_record.block_id),
|
||||
hash=trace_record.block_id,
|
||||
value_size=trace_record.block_size,
|
||||
no_insert=trace_record.no_insert,
|
||||
)
|
||||
self._update_stats(trace_record.access_time, is_hit)
|
||||
if (
|
||||
trace_record.kv_size > 0
|
||||
and not self.get_id_row_key_map[trace_record.get_id][
|
||||
trace_record.key_id
|
||||
]
|
||||
):
|
||||
# Insert the row key-value pair.
|
||||
self._access_kv(
|
||||
trace_record,
|
||||
key="g{}".format(trace_record.key_id),
|
||||
hash=trace_record.key_id,
|
||||
value_size=trace_record.kv_size,
|
||||
no_insert=False,
|
||||
)
|
||||
# Mark as inserted.
|
||||
self.get_id_row_key_map[trace_record.get_id][trace_record.key_id] = True
|
||||
return
|
||||
# Access the block.
|
||||
is_hit = self._access_kv(
|
||||
trace_record,
|
||||
key="b{}".format(trace_record.block_id),
|
||||
hash=trace_record.block_id,
|
||||
value_size=trace_record.block_size,
|
||||
no_insert=trace_record.no_insert,
|
||||
)
|
||||
self._update_stats(trace_record.access_time, is_hit)
|
||||
|
||||
|
||||
class ThompsonSamplingCache(MLCache):
|
||||
"""
|
||||
An implementation of Thompson Sampling for the Bernoulli Bandit [1].
|
||||
[1] Daniel J. Russo, Benjamin Van Roy, Abbas Kazerouni, Ian Osband,
|
||||
and Zheng Wen. 2018. A Tutorial on Thompson Sampling. Found.
|
||||
Trends Mach. Learn. 11, 1 (July 2018), 1-96.
|
||||
DOI: https://doi.org/10.1561/2200000070
|
||||
"""
|
||||
|
||||
def __init__(self, cache_size, enable_cache_row_key, policies, init_a=1, init_b=1):
|
||||
super(ThompsonSamplingCache, self).__init__(
|
||||
cache_size, enable_cache_row_key, policies
|
||||
)
|
||||
self._as = {}
|
||||
self._bs = {}
|
||||
for _i in range(len(policies)):
|
||||
self._as = [init_a] * len(self.policies)
|
||||
self._bs = [init_b] * len(self.policies)
|
||||
|
||||
def _select_policy(self, trace_record, key):
|
||||
samples = [
|
||||
np.random.beta(self._as[x], self._bs[x]) for x in range(len(self.policies))
|
||||
]
|
||||
selected_policy = max(range(len(self.policies)), key=lambda x: samples[x])
|
||||
reward = self.policies[selected_policy].generate_reward(key)
|
||||
assert reward <= 1 and reward >= 0
|
||||
self._as[selected_policy] += reward
|
||||
self._bs[selected_policy] += 1 - reward
|
||||
return selected_policy
|
||||
|
||||
def cache_name(self):
|
||||
if self.enable_cache_row_key:
|
||||
return "Hybrid ThompsonSampling (ts_hybrid)"
|
||||
return "ThompsonSampling (ts)"
|
||||
|
||||
|
||||
class LinUCBCache(MLCache):
|
||||
"""
|
||||
An implementation of LinUCB with disjoint linear models [2].
|
||||
[2] Lihong Li, Wei Chu, John Langford, and Robert E. Schapire. 2010.
|
||||
A contextual-bandit approach to personalized news article recommendation.
|
||||
In Proceedings of the 19th international conference on World wide web
|
||||
(WWW '10). ACM, New York, NY, USA, 661-670.
|
||||
DOI=http://dx.doi.org/10.1145/1772690.1772758
|
||||
"""
|
||||
|
||||
def __init__(self, cache_size, enable_cache_row_key, policies):
|
||||
super(LinUCBCache, self).__init__(cache_size, enable_cache_row_key, policies)
|
||||
self.nfeatures = 4 # Block type, caller, level, cf.
|
||||
self.th = np.zeros((len(self.policies), self.nfeatures))
|
||||
self.eps = 0.2
|
||||
self.b = np.zeros_like(self.th)
|
||||
self.A = np.zeros((len(self.policies), self.nfeatures, self.nfeatures))
|
||||
self.A_inv = np.zeros((len(self.policies), self.nfeatures, self.nfeatures))
|
||||
for i in range(len(self.policies)):
|
||||
self.A[i] = np.identity(self.nfeatures)
|
||||
self.th_hat = np.zeros_like(self.th)
|
||||
self.p = np.zeros(len(self.policies))
|
||||
self.alph = 0.2
|
||||
|
||||
def _select_policy(self, trace_record, key):
|
||||
x_i = np.zeros(self.nfeatures) # The current context vector
|
||||
x_i[0] = trace_record.block_type
|
||||
x_i[1] = trace_record.caller
|
||||
x_i[2] = trace_record.level
|
||||
x_i[3] = trace_record.cf_id
|
||||
p = np.zeros(len(self.policies))
|
||||
for a in range(len(self.policies)):
|
||||
self.th_hat[a] = self.A_inv[a].dot(self.b[a])
|
||||
ta = x_i.dot(self.A_inv[a]).dot(x_i)
|
||||
a_upper_ci = self.alph * np.sqrt(ta)
|
||||
a_mean = self.th_hat[a].dot(x_i)
|
||||
p[a] = a_mean + a_upper_ci
|
||||
p = p + (np.random.random(len(p)) * 0.000001)
|
||||
selected_policy = p.argmax()
|
||||
reward = self.policies[selected_policy].generate_reward(key)
|
||||
assert reward <= 1 and reward >= 0
|
||||
self.A[selected_policy] += np.outer(x_i, x_i)
|
||||
self.b[selected_policy] += reward * x_i
|
||||
self.A_inv[selected_policy] = np.linalg.inv(self.A[selected_policy])
|
||||
del x_i
|
||||
return selected_policy
|
||||
|
||||
def cache_name(self):
|
||||
if self.enable_cache_row_key:
|
||||
return "Hybrid LinUCB (linucb_hybrid)"
|
||||
return "LinUCB (linucb)"
|
||||
|
||||
|
||||
def parse_cache_size(cs):
|
||||
cs = cs.replace("\n", "")
|
||||
if cs[-1] == "M":
|
||||
return int(cs[: len(cs) - 1]) * 1024 * 1024
|
||||
if cs[-1] == "G":
|
||||
return int(cs[: len(cs) - 1]) * 1024 * 1024 * 1024
|
||||
if cs[-1] == "T":
|
||||
return int(cs[: len(cs) - 1]) * 1024 * 1024 * 1024 * 1024
|
||||
return int(cs)
|
||||
|
||||
|
||||
def create_cache(cache_type, cache_size, downsample_size):
|
||||
policies = []
|
||||
policies.append(LRUPolicy())
|
||||
policies.append(MRUPolicy())
|
||||
policies.append(LFUPolicy())
|
||||
cache_size = cache_size / downsample_size
|
||||
enable_cache_row_key = False
|
||||
if "hybrid" in cache_type:
|
||||
enable_cache_row_key = True
|
||||
cache_type = cache_type[:-7]
|
||||
if cache_type == "ts":
|
||||
return ThompsonSamplingCache(cache_size, enable_cache_row_key, policies)
|
||||
elif cache_type == "linucb":
|
||||
return LinUCBCache(cache_size, enable_cache_row_key, policies)
|
||||
else:
|
||||
print("Unknown cache type {}".format(cache_type))
|
||||
assert False
|
||||
return None
|
||||
|
||||
|
||||
def run(trace_file_path, cache_type, cache, warmup_seconds):
|
||||
warmup_complete = False
|
||||
num = 0
|
||||
trace_start_time = 0
|
||||
trace_duration = 0
|
||||
start_time = time.time()
|
||||
time_interval = 1
|
||||
trace_miss_ratio_stats = MissRatioStats(kSecondsInMinute)
|
||||
with open(trace_file_path, "r") as trace_file:
|
||||
for line in trace_file:
|
||||
num += 1
|
||||
if num % 1000000 == 0:
|
||||
# Force a python gc periodically to reduce memory usage.
|
||||
gc.collect()
|
||||
ts = line.split(",")
|
||||
timestamp = int(ts[0])
|
||||
if trace_start_time == 0:
|
||||
trace_start_time = timestamp
|
||||
trace_duration = timestamp - trace_start_time
|
||||
if not warmup_complete and trace_duration > warmup_seconds * 1000000:
|
||||
cache.miss_ratio_stats.reset_counter()
|
||||
warmup_complete = True
|
||||
record = TraceRecord(
|
||||
access_time=int(ts[0]),
|
||||
block_id=int(ts[1]),
|
||||
block_type=int(ts[2]),
|
||||
block_size=int(ts[3]),
|
||||
cf_id=int(ts[4]),
|
||||
cf_name=ts[5],
|
||||
level=int(ts[6]),
|
||||
fd=int(ts[7]),
|
||||
caller=int(ts[8]),
|
||||
no_insert=int(ts[9]),
|
||||
get_id=int(ts[10]),
|
||||
key_id=int(ts[11]),
|
||||
kv_size=int(ts[12]),
|
||||
is_hit=int(ts[13]),
|
||||
)
|
||||
trace_miss_ratio_stats.update_metrics(
|
||||
record.access_time, is_hit=record.is_hit
|
||||
)
|
||||
cache.access(record)
|
||||
del record
|
||||
if num % 100 != 0:
|
||||
continue
|
||||
# Report progress every 10 seconds.
|
||||
now = time.time()
|
||||
if now - start_time > time_interval * 10:
|
||||
print(
|
||||
"Take {} seconds to process {} trace records with trace "
|
||||
"duration of {} seconds. Throughput: {} records/second. "
|
||||
"Trace miss ratio {}".format(
|
||||
now - start_time,
|
||||
num,
|
||||
trace_duration / 1000000,
|
||||
num / (now - start_time),
|
||||
trace_miss_ratio_stats.miss_ratio(),
|
||||
)
|
||||
)
|
||||
time_interval += 1
|
||||
print(
|
||||
"{},0,0,{},{},{}".format(
|
||||
cache_type,
|
||||
cache.cache_size,
|
||||
cache.miss_ratio_stats.miss_ratio(),
|
||||
cache.miss_ratio_stats.num_accesses,
|
||||
)
|
||||
)
|
||||
now = time.time()
|
||||
print(
|
||||
"Take {} seconds to process {} trace records with trace duration of {} "
|
||||
"seconds. Throughput: {} records/second. Trace miss ratio {}".format(
|
||||
now - start_time,
|
||||
num,
|
||||
trace_duration / 1000000,
|
||||
num / (now - start_time),
|
||||
trace_miss_ratio_stats.miss_ratio(),
|
||||
)
|
||||
)
|
||||
return trace_start_time, trace_duration
|
||||
|
||||
|
||||
def report_stats(
|
||||
cache, cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
):
|
||||
cache_label = "{}-{}".format(cache_type, cache_size)
|
||||
with open("{}/data-ml-mrc-{}".format(result_dir, cache_label), "w+") as mrc_file:
|
||||
mrc_file.write(
|
||||
"{},0,0,{},{},{}\n".format(
|
||||
cache_type,
|
||||
cache_size,
|
||||
cache.miss_ratio_stats.miss_ratio(),
|
||||
cache.miss_ratio_stats.num_accesses,
|
||||
)
|
||||
)
|
||||
cache.policy_stats.write_policy_timeline(
|
||||
cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
)
|
||||
cache.policy_stats.write_policy_ratio_timeline(
|
||||
cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
)
|
||||
cache.miss_ratio_stats.write_miss_timeline(
|
||||
cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
)
|
||||
cache.miss_ratio_stats.write_miss_ratio_timeline(
|
||||
cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
)
|
||||
cache.per_hour_policy_stats.write_policy_timeline(
|
||||
cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
)
|
||||
cache.per_hour_policy_stats.write_policy_ratio_timeline(
|
||||
cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
)
|
||||
cache.per_hour_miss_ratio_stats.write_miss_timeline(
|
||||
cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
)
|
||||
cache.per_hour_miss_ratio_stats.write_miss_ratio_timeline(
|
||||
cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) <= 6:
|
||||
print(
|
||||
"Must provide 6 arguments. "
|
||||
"1) cache_type (ts, ts_hybrid, linucb, linucb_hybrid). "
|
||||
"2) cache size (xM, xG, xT). "
|
||||
"3) The sampling frequency used to collect the trace. (The "
|
||||
"simulation scales down the cache size by the sampling frequency). "
|
||||
"4) Warmup seconds (The number of seconds used for warmup). "
|
||||
"5) Trace file path. "
|
||||
"6) Result directory (A directory that saves generated results)"
|
||||
)
|
||||
exit(1)
|
||||
cache_type = sys.argv[1]
|
||||
cache_size = parse_cache_size(sys.argv[2])
|
||||
downsample_size = int(sys.argv[3])
|
||||
warmup_seconds = int(sys.argv[4])
|
||||
trace_file_path = sys.argv[5]
|
||||
result_dir = sys.argv[6]
|
||||
cache = create_cache(cache_type, cache_size, downsample_size)
|
||||
trace_start_time, trace_duration = run(
|
||||
trace_file_path, cache_type, cache, warmup_seconds
|
||||
)
|
||||
trace_end_time = trace_start_time + trace_duration
|
||||
report_stats(
|
||||
cache, cache_type, cache_size, result_dir, trace_start_time, trace_end_time
|
||||
)
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# A shell script to run a batch of pysims and combine individual pysim output files.
|
||||
#
|
||||
# Usage: bash block_cache_pysim.sh trace_file_path result_dir downsample_size warmup_seconds max_jobs
|
||||
# trace_file_path: The file path that stores the traces.
|
||||
# result_dir: The directory to store pysim results. The output files from a pysim is stores in result_dir/ml
|
||||
# downsample_size: The downsample size used to collect the trace.
|
||||
# warmup_seconds: The number of seconds used for warmup.
|
||||
# max_jobs: The max number of concurrent pysims to run.
|
||||
|
||||
if [ $# -ne 5 ]; then
|
||||
echo "Usage: ./block_cache_pysim.sh trace_file_path result_dir downsample_size warmup_seconds max_jobs"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
trace_file="$1"
|
||||
result_dir="$2"
|
||||
downsample_size="$3"
|
||||
warmup_seconds="$4"
|
||||
max_jobs="$5"
|
||||
current_jobs=0
|
||||
|
||||
ml_tmp_result_dir="$result_dir/ml"
|
||||
rm -rf "$ml_tmp_result_dir"
|
||||
mkdir -p "$result_dir"
|
||||
mkdir -p "$ml_tmp_result_dir"
|
||||
|
||||
for cache_type in "ts" "linucb" "ts_hybrid" "linucb_hybrid"
|
||||
do
|
||||
for cache_size in "16M" "256M" "1G" "2G" "4G" "8G" "12G" "16G"
|
||||
do
|
||||
while [ "$current_jobs" -ge "$max_jobs" ]
|
||||
do
|
||||
sleep 10
|
||||
echo "Waiting jobs to complete. Number of running jobs: $current_jobs"
|
||||
current_jobs=$(ps aux | grep pysim | grep python | grep -cv grep)
|
||||
echo "Waiting jobs to complete. Number of running jobs: $current_jobs"
|
||||
done
|
||||
output="log-ml-$cache_type-$cache_size"
|
||||
echo "Running simulation for $cache_type and cache size $cache_size. Number of running jobs: $current_jobs. "
|
||||
nohup python block_cache_pysim.py "$cache_type" "$cache_size" "$downsample_size" "$warmup_seconds" "$trace_file" "$ml_tmp_result_dir" >& $ml_tmp_result_dir/$output &
|
||||
current_jobs=$((current_jobs+1))
|
||||
done
|
||||
done
|
||||
|
||||
# Wait for all jobs to complete.
|
||||
while [ $current_jobs -gt 0 ]
|
||||
do
|
||||
sleep 10
|
||||
echo "Waiting jobs to complete. Number of running jobs: $current_jobs"
|
||||
current_jobs=$(ps aux | grep pysim | grep python | grep -cv grep)
|
||||
echo "Waiting jobs to complete. Number of running jobs: $current_jobs"
|
||||
done
|
||||
|
||||
echo "Combine individual pysim output files"
|
||||
|
||||
rm -rf "$result_dir/ml_*"
|
||||
mrc_file="$result_dir/ml_mrc"
|
||||
for header in "header-" "data-"
|
||||
do
|
||||
for fn in $ml_tmp_result_dir/*
|
||||
do
|
||||
sum_file=""
|
||||
time_unit=""
|
||||
capacity=""
|
||||
if [[ $fn == *"timeline"* ]]; then
|
||||
tmpfn="$fn"
|
||||
IFS='-' read -ra elements <<< "$tmpfn"
|
||||
time_unit_index=0
|
||||
capacity_index=0
|
||||
for i in "${elements[@]}"
|
||||
do
|
||||
if [[ $i == "timeline" ]]; then
|
||||
break
|
||||
fi
|
||||
time_unit_index=$((time_unit_index+1))
|
||||
done
|
||||
time_unit_index=$((time_unit_index+1))
|
||||
capacity_index=$((time_unit_index+2))
|
||||
time_unit="${elements[$time_unit_index]}_"
|
||||
capacity="${elements[$capacity_index]}_"
|
||||
fi
|
||||
|
||||
if [[ $fn == "${header}ml-policy-timeline"* ]]; then
|
||||
sum_file="$result_dir/ml_${capacity}${time_unit}policy_timeline"
|
||||
fi
|
||||
if [[ $fn == "${header}ml-policy-ratio-timeline"* ]]; then
|
||||
sum_file="$result_dir/ml_${capacity}${time_unit}policy_ratio_timeline"
|
||||
fi
|
||||
if [[ $fn == "${header}ml-miss-timeline"* ]]; then
|
||||
sum_file="$result_dir/ml_${capacity}${time_unit}miss_timeline"
|
||||
fi
|
||||
if [[ $fn == "${header}ml-miss-ratio-timeline"* ]]; then
|
||||
sum_file="$result_dir/ml_${capacity}${time_unit}miss_ratio_timeline"
|
||||
fi
|
||||
if [[ $fn == "${header}ml-mrc"* ]]; then
|
||||
sum_file="$mrc_file"
|
||||
fi
|
||||
if [[ $sum_file == "" ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ $header == "header-" ]]; then
|
||||
if [ -e "$sum_file" ]; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
cat "$ml_tmp_result_dir/$fn" >> "$sum_file"
|
||||
done
|
||||
done
|
||||
|
||||
echo "Done"
|
||||
# Sort MRC file by cache_type and cache_size.
|
||||
tmp_file="$result_dir/tmp_mrc"
|
||||
cat "$mrc_file" | sort -t ',' -k1,1 -k4,4n > "$tmp_file"
|
||||
cat "$tmp_file" > "$mrc_file"
|
||||
rm -rf "$tmp_file"
|
||||
@@ -0,0 +1,340 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
import random
|
||||
|
||||
from block_cache_pysim import (
|
||||
HashTable,
|
||||
LFUPolicy,
|
||||
LinUCBCache,
|
||||
LRUPolicy,
|
||||
MRUPolicy,
|
||||
ThompsonSamplingCache,
|
||||
TraceRecord,
|
||||
kSampleSize,
|
||||
)
|
||||
|
||||
|
||||
def test_hash_table():
|
||||
print("Test hash table")
|
||||
table = HashTable()
|
||||
data_size = 10000
|
||||
for i in range(data_size):
|
||||
table.insert("k{}".format(i), i, "v{}".format(i))
|
||||
for i in range(data_size):
|
||||
assert table.lookup("k{}".format(i), i) is not None
|
||||
for i in range(data_size):
|
||||
table.delete("k{}".format(i), i)
|
||||
for i in range(data_size):
|
||||
assert table.lookup("k{}".format(i), i) is None
|
||||
|
||||
truth_map = {}
|
||||
n = 1000000
|
||||
records = 100
|
||||
for i in range(n):
|
||||
key_id = random.randint(0, records)
|
||||
key = "k{}".format(key_id)
|
||||
value = "v{}".format(key_id)
|
||||
action = random.randint(0, 2)
|
||||
# print "{}:{}:{}".format(action, key, value)
|
||||
assert len(truth_map) == table.elements, "{} {} {}".format(
|
||||
len(truth_map), table.elements, i
|
||||
)
|
||||
if action == 0:
|
||||
table.insert(key, key_id, value)
|
||||
truth_map[key] = value
|
||||
elif action == 1:
|
||||
if key in truth_map:
|
||||
assert table.lookup(key, key_id) is not None
|
||||
assert truth_map[key] == table.lookup(key, key_id)
|
||||
else:
|
||||
assert table.lookup(key, key_id) is None
|
||||
else:
|
||||
table.delete(key, key_id)
|
||||
if key in truth_map:
|
||||
del truth_map[key]
|
||||
print("Test hash table: Success")
|
||||
|
||||
|
||||
def assert_metrics(cache, expected_value):
|
||||
assert cache.used_size == expected_value[0], "Expected {}, Actual {}".format(
|
||||
expected_value[0], cache.used_size
|
||||
)
|
||||
assert (
|
||||
cache.miss_ratio_stats.num_accesses == expected_value[1]
|
||||
), "Expected {}, Actual {}".format(
|
||||
expected_value[1], cache.miss_ratio_stats.num_accesses
|
||||
)
|
||||
assert (
|
||||
cache.miss_ratio_stats.num_misses == expected_value[2]
|
||||
), "Expected {}, Actual {}".format(
|
||||
expected_value[2], cache.miss_ratio_stats.num_misses
|
||||
)
|
||||
assert cache.table.elements == len(expected_value[3]) + len(
|
||||
expected_value[4]
|
||||
), "Expected {}, Actual {}".format(
|
||||
len(expected_value[3]) + len(expected_value[4]), cache.table.elements
|
||||
)
|
||||
for expeceted_k in expected_value[3]:
|
||||
val = cache.table.lookup("b{}".format(expeceted_k), expeceted_k)
|
||||
assert val is not None
|
||||
assert val.value_size == 1
|
||||
for expeceted_k in expected_value[4]:
|
||||
val = cache.table.lookup("g{}".format(expeceted_k), expeceted_k)
|
||||
assert val is not None
|
||||
assert val.value_size == 1
|
||||
|
||||
|
||||
# Access k1, k1, k2, k3, k3, k3, k4
|
||||
def test_cache(policies, expected_value):
|
||||
cache = ThompsonSamplingCache(3, False, policies)
|
||||
k1 = TraceRecord(
|
||||
access_time=0,
|
||||
block_id=1,
|
||||
block_type=1,
|
||||
block_size=1,
|
||||
cf_id=0,
|
||||
cf_name="",
|
||||
level=0,
|
||||
fd=0,
|
||||
caller=1,
|
||||
no_insert=0,
|
||||
get_id=1,
|
||||
key_id=1,
|
||||
kv_size=5,
|
||||
is_hit=1,
|
||||
)
|
||||
k2 = TraceRecord(
|
||||
access_time=1,
|
||||
block_id=2,
|
||||
block_type=1,
|
||||
block_size=1,
|
||||
cf_id=0,
|
||||
cf_name="",
|
||||
level=0,
|
||||
fd=0,
|
||||
caller=1,
|
||||
no_insert=0,
|
||||
get_id=1,
|
||||
key_id=1,
|
||||
kv_size=5,
|
||||
is_hit=1,
|
||||
)
|
||||
k3 = TraceRecord(
|
||||
access_time=2,
|
||||
block_id=3,
|
||||
block_type=1,
|
||||
block_size=1,
|
||||
cf_id=0,
|
||||
cf_name="",
|
||||
level=0,
|
||||
fd=0,
|
||||
caller=1,
|
||||
no_insert=0,
|
||||
get_id=1,
|
||||
key_id=1,
|
||||
kv_size=5,
|
||||
is_hit=1,
|
||||
)
|
||||
k4 = TraceRecord(
|
||||
access_time=3,
|
||||
block_id=4,
|
||||
block_type=1,
|
||||
block_size=1,
|
||||
cf_id=0,
|
||||
cf_name="",
|
||||
level=0,
|
||||
fd=0,
|
||||
caller=1,
|
||||
no_insert=0,
|
||||
get_id=1,
|
||||
key_id=1,
|
||||
kv_size=5,
|
||||
is_hit=1,
|
||||
)
|
||||
sequence = [k1, k1, k2, k3, k3, k3]
|
||||
index = 0
|
||||
expected_values = []
|
||||
# Access k1, miss.
|
||||
expected_values.append([1, 1, 1, [1], []])
|
||||
# Access k1, hit.
|
||||
expected_values.append([1, 2, 1, [1], []])
|
||||
# Access k2, miss.
|
||||
expected_values.append([2, 3, 2, [1, 2], []])
|
||||
# Access k3, miss.
|
||||
expected_values.append([3, 4, 3, [1, 2, 3], []])
|
||||
# Access k3, hit.
|
||||
expected_values.append([3, 5, 3, [1, 2, 3], []])
|
||||
# Access k3, hit.
|
||||
expected_values.append([3, 6, 3, [1, 2, 3], []])
|
||||
for access in sequence:
|
||||
cache.access(access)
|
||||
assert_metrics(cache, expected_values[index])
|
||||
index += 1
|
||||
cache.access(k4)
|
||||
assert_metrics(cache, expected_value)
|
||||
|
||||
|
||||
def test_lru_cache():
|
||||
print("Test LRU cache")
|
||||
policies = []
|
||||
policies.append(LRUPolicy())
|
||||
# Access k4, miss. evict k1
|
||||
test_cache(policies, [3, 7, 4, [2, 3, 4], []])
|
||||
print("Test LRU cache: Success")
|
||||
|
||||
|
||||
def test_mru_cache():
|
||||
print("Test MRU cache")
|
||||
policies = []
|
||||
policies.append(MRUPolicy())
|
||||
# Access k4, miss. evict k3
|
||||
test_cache(policies, [3, 7, 4, [1, 2, 4], []])
|
||||
print("Test MRU cache: Success")
|
||||
|
||||
|
||||
def test_lfu_cache():
|
||||
print("Test LFU cache")
|
||||
policies = []
|
||||
policies.append(LFUPolicy())
|
||||
# Access k4, miss. evict k2
|
||||
test_cache(policies, [3, 7, 4, [1, 3, 4], []])
|
||||
print("Test LFU cache: Success")
|
||||
|
||||
|
||||
def test_mix(cache):
|
||||
print("Test Mix {} cache".format(cache.cache_name()))
|
||||
n = 100000
|
||||
records = 199
|
||||
for i in range(n):
|
||||
key_id = random.randint(0, records)
|
||||
vs = random.randint(0, 10)
|
||||
k = TraceRecord(
|
||||
access_time=i,
|
||||
block_id=key_id,
|
||||
block_type=1,
|
||||
block_size=vs,
|
||||
cf_id=0,
|
||||
cf_name="",
|
||||
level=0,
|
||||
fd=0,
|
||||
caller=1,
|
||||
no_insert=0,
|
||||
get_id=key_id,
|
||||
key_id=key_id,
|
||||
kv_size=5,
|
||||
is_hit=1,
|
||||
)
|
||||
cache.access(k)
|
||||
assert cache.miss_ratio_stats.miss_ratio() > 0
|
||||
print("Test Mix {} cache: Success".format(cache.cache_name()))
|
||||
|
||||
|
||||
def test_hybrid(cache):
|
||||
print("Test {} cache".format(cache.cache_name()))
|
||||
k = TraceRecord(
|
||||
access_time=0,
|
||||
block_id=1,
|
||||
block_type=1,
|
||||
block_size=1,
|
||||
cf_id=0,
|
||||
cf_name="",
|
||||
level=0,
|
||||
fd=0,
|
||||
caller=1,
|
||||
no_insert=0,
|
||||
get_id=1, # the first get request.
|
||||
key_id=1,
|
||||
kv_size=0, # no size.
|
||||
is_hit=1,
|
||||
)
|
||||
cache.access(k) # Expect a miss.
|
||||
# used size, num accesses, num misses, hash table size, blocks, get keys.
|
||||
assert_metrics(cache, [1, 1, 1, [1], []])
|
||||
k.access_time += 1
|
||||
k.kv_size = 1
|
||||
k.block_id = 2
|
||||
cache.access(k) # k should be inserted.
|
||||
assert_metrics(cache, [3, 2, 2, [1, 2], [1]])
|
||||
k.access_time += 1
|
||||
k.block_id = 3
|
||||
cache.access(k) # k should not be inserted again.
|
||||
assert_metrics(cache, [4, 3, 3, [1, 2, 3], [1]])
|
||||
# A second get request referencing the same key.
|
||||
k.access_time += 1
|
||||
k.get_id = 2
|
||||
k.block_id = 4
|
||||
k.kv_size = 0
|
||||
cache.access(k) # k should observe a hit. No block access.
|
||||
assert_metrics(cache, [4, 4, 3, [1, 2, 3], [1]])
|
||||
|
||||
# A third get request searches three files, three different keys.
|
||||
# And the second key observes a hit.
|
||||
k.access_time += 1
|
||||
k.kv_size = 1
|
||||
k.get_id = 3
|
||||
k.block_id = 3
|
||||
k.key_id = 2
|
||||
cache.access(k) # k should observe a miss. block 3 observes a hit.
|
||||
assert_metrics(cache, [5, 5, 3, [1, 2, 3], [1, 2]])
|
||||
|
||||
k.access_time += 1
|
||||
k.kv_size = 1
|
||||
k.get_id = 3
|
||||
k.block_id = 4
|
||||
k.kv_size = 1
|
||||
k.key_id = 1
|
||||
cache.access(k) # k1 should observe a hit.
|
||||
assert_metrics(cache, [5, 6, 3, [1, 2, 3], [1, 2]])
|
||||
|
||||
k.access_time += 1
|
||||
k.kv_size = 1
|
||||
k.get_id = 3
|
||||
k.block_id = 4
|
||||
k.kv_size = 1
|
||||
k.key_id = 3
|
||||
# k3 should observe a miss.
|
||||
# However, as the get already complete, we should not access k3 any more.
|
||||
cache.access(k)
|
||||
assert_metrics(cache, [5, 7, 3, [1, 2, 3], [1, 2]])
|
||||
|
||||
# A fourth get request searches one file and two blocks. One row key.
|
||||
k.access_time += 1
|
||||
k.get_id = 4
|
||||
k.block_id = 5
|
||||
k.key_id = 4
|
||||
k.kv_size = 1
|
||||
cache.access(k)
|
||||
assert_metrics(cache, [7, 8, 4, [1, 2, 3, 5], [1, 2, 4]])
|
||||
|
||||
# A bunch of insertions which evict cached row keys.
|
||||
for i in range(6, 100):
|
||||
k.access_time += 1
|
||||
k.get_id = 0
|
||||
k.block_id = i
|
||||
cache.access(k)
|
||||
|
||||
k.get_id = 4
|
||||
k.block_id = 100 # A different block.
|
||||
k.key_id = 4 # Same row key and should not be inserted again.
|
||||
k.kv_size = 1
|
||||
cache.access(k)
|
||||
assert_metrics(cache, [16, 103, 99, [i for i in range(101 - kSampleSize, 101)], []])
|
||||
print("Test {} cache: Success".format(cache.cache_name()))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
policies = []
|
||||
policies.append(MRUPolicy())
|
||||
policies.append(LRUPolicy())
|
||||
policies.append(LFUPolicy())
|
||||
test_hash_table()
|
||||
test_lru_cache()
|
||||
test_mru_cache()
|
||||
test_lfu_cache()
|
||||
test_mix(ThompsonSamplingCache(100, False, policies))
|
||||
test_mix(ThompsonSamplingCache(100, True, policies))
|
||||
test_mix(LinUCBCache(100, False, policies))
|
||||
test_mix(LinUCBCache(100, True, policies))
|
||||
test_hybrid(ThompsonSamplingCache(kSampleSize, True, [LRUPolicy()]))
|
||||
test_hybrid(LinUCBCache(kSampleSize, True, [LRUPolicy()]))
|
||||
+442
-37
@@ -5,13 +5,18 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#ifdef GFLAGS
|
||||
#include "tools/block_cache_trace_analyzer.h"
|
||||
#include "tools/block_cache_analyzer/block_cache_trace_analyzer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
#include "monitoring/histogram.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -122,6 +127,20 @@ DEFINE_string(analyze_get_spatial_locality_labels, "",
|
||||
"Group data blocks using these labels.");
|
||||
DEFINE_string(analyze_get_spatial_locality_buckets, "",
|
||||
"Group data blocks by their statistics using these buckets.");
|
||||
DEFINE_bool(mrc_only, false,
|
||||
"Evaluate alternative cache policies only. When this flag is true, "
|
||||
"the analyzer does NOT maintain states of each block in memory for "
|
||||
"analysis. It only feeds the accesses into the cache simulators.");
|
||||
DEFINE_string(
|
||||
analyze_correlation_coefficients_labels, "",
|
||||
"Analyze the correlation coefficients of features such as number of past "
|
||||
"accesses with regard to the number of accesses till the next access.");
|
||||
DEFINE_int32(analyze_correlation_coefficients_max_number_of_values, 1000000,
|
||||
"The maximum number of values for a feature. If the number of "
|
||||
"values for a feature is larger than this max, it randomly "
|
||||
"selects 'max' number of values.");
|
||||
DEFINE_string(human_readable_trace_file_path, "",
|
||||
"The filt path that saves human readable access records.");
|
||||
|
||||
namespace rocksdb {
|
||||
namespace {
|
||||
@@ -143,7 +162,10 @@ const std::string kSupportedCacheNames =
|
||||
"ghost_lru_hybrid_no_insert_on_row_miss ";
|
||||
|
||||
// The suffix for the generated csv files.
|
||||
const std::string kFileNameSuffixMissRatioTimeline = "miss_ratio_timeline";
|
||||
const std::string kFileNameSuffixMissTimeline = "miss_timeline";
|
||||
const std::string kFileNameSuffixAccessTimeline = "access_timeline";
|
||||
const std::string kFileNameSuffixCorrelation = "correlation_input";
|
||||
const std::string kFileNameSuffixAvgReuseIntervalNaccesses =
|
||||
"avg_reuse_interval_naccesses";
|
||||
const std::string kFileNameSuffixAvgReuseInterval = "avg_reuse_interval";
|
||||
@@ -279,6 +301,18 @@ double percent(uint64_t numerator, uint64_t denomenator) {
|
||||
return static_cast<double>(numerator * 100.0 / denomenator);
|
||||
}
|
||||
|
||||
std::map<uint64_t, uint64_t> adjust_time_unit(
|
||||
const std::map<uint64_t, uint64_t>& time_stats, uint64_t time_unit) {
|
||||
if (time_unit == 1) {
|
||||
return time_stats;
|
||||
}
|
||||
std::map<uint64_t, uint64_t> adjusted_time_stats;
|
||||
for (auto const& time : time_stats) {
|
||||
adjusted_time_stats[static_cast<uint64_t>(time.first / time_unit)] +=
|
||||
time.second;
|
||||
}
|
||||
return adjusted_time_stats;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void BlockCacheTraceAnalyzer::WriteMissRatioCurves() const {
|
||||
@@ -288,8 +322,12 @@ void BlockCacheTraceAnalyzer::WriteMissRatioCurves() const {
|
||||
if (output_dir_.empty()) {
|
||||
return;
|
||||
}
|
||||
uint64_t trace_duration =
|
||||
trace_end_timestamp_in_seconds_ - trace_start_timestamp_in_seconds_;
|
||||
uint64_t total_accesses = access_sequence_number_;
|
||||
const std::string output_miss_ratio_curve_path =
|
||||
output_dir_ + "/" + kMissRatioCurveFileName;
|
||||
output_dir_ + "/" + std::to_string(trace_duration) + "_" +
|
||||
std::to_string(total_accesses) + "_" + kMissRatioCurveFileName;
|
||||
std::ofstream out(output_miss_ratio_curve_path);
|
||||
if (!out.is_open()) {
|
||||
return;
|
||||
@@ -302,7 +340,8 @@ void BlockCacheTraceAnalyzer::WriteMissRatioCurves() const {
|
||||
for (auto const& config_caches : cache_simulator_->sim_caches()) {
|
||||
const CacheConfiguration& config = config_caches.first;
|
||||
for (uint32_t i = 0; i < config.cache_capacities.size(); i++) {
|
||||
double miss_ratio = config_caches.second[i]->miss_ratio();
|
||||
double miss_ratio =
|
||||
config_caches.second[i]->miss_ratio_stats().miss_ratio();
|
||||
// Write the body.
|
||||
out << config.cache_name;
|
||||
out << ",";
|
||||
@@ -314,13 +353,287 @@ void BlockCacheTraceAnalyzer::WriteMissRatioCurves() const {
|
||||
out << ",";
|
||||
out << std::fixed << std::setprecision(4) << miss_ratio;
|
||||
out << ",";
|
||||
out << config_caches.second[i]->total_accesses();
|
||||
out << config_caches.second[i]->miss_ratio_stats().total_accesses();
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
out.close();
|
||||
}
|
||||
|
||||
void BlockCacheTraceAnalyzer::UpdateFeatureVectors(
|
||||
const std::vector<uint64_t>& access_sequence_number_timeline,
|
||||
const std::vector<uint64_t>& access_timeline, const std::string& label,
|
||||
std::map<std::string, Features>* label_features,
|
||||
std::map<std::string, Predictions>* label_predictions) const {
|
||||
if (access_sequence_number_timeline.empty() || access_timeline.empty()) {
|
||||
return;
|
||||
}
|
||||
assert(access_timeline.size() == access_sequence_number_timeline.size());
|
||||
uint64_t prev_access_sequence_number = access_sequence_number_timeline[0];
|
||||
uint64_t prev_access_timestamp = access_timeline[0];
|
||||
for (uint32_t i = 0; i < access_sequence_number_timeline.size(); i++) {
|
||||
uint64_t num_accesses_since_last_access =
|
||||
access_sequence_number_timeline[i] - prev_access_sequence_number;
|
||||
uint64_t elapsed_time_since_last_access =
|
||||
access_timeline[i] - prev_access_timestamp;
|
||||
prev_access_sequence_number = access_sequence_number_timeline[i];
|
||||
prev_access_timestamp = access_timeline[i];
|
||||
if (i < access_sequence_number_timeline.size() - 1) {
|
||||
(*label_features)[label].num_accesses_since_last_access.push_back(
|
||||
num_accesses_since_last_access);
|
||||
(*label_features)[label].num_past_accesses.push_back(i);
|
||||
(*label_features)[label].elapsed_time_since_last_access.push_back(
|
||||
elapsed_time_since_last_access);
|
||||
}
|
||||
if (i >= 1) {
|
||||
(*label_predictions)[label].num_accesses_till_next_access.push_back(
|
||||
num_accesses_since_last_access);
|
||||
(*label_predictions)[label].elapsed_time_till_next_access.push_back(
|
||||
elapsed_time_since_last_access);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlockCacheTraceAnalyzer::WriteMissRatioTimeline(uint64_t time_unit) const {
|
||||
if (!cache_simulator_ || output_dir_.empty()) {
|
||||
return;
|
||||
}
|
||||
std::map<uint64_t, std::map<std::string, std::map<uint64_t, double>>>
|
||||
cs_name_timeline;
|
||||
uint64_t start_time = port::kMaxUint64;
|
||||
uint64_t end_time = 0;
|
||||
const std::map<uint64_t, uint64_t>& trace_num_misses =
|
||||
adjust_time_unit(miss_ratio_stats_.num_misses_timeline(), time_unit);
|
||||
const std::map<uint64_t, uint64_t>& trace_num_accesses =
|
||||
adjust_time_unit(miss_ratio_stats_.num_accesses_timeline(), time_unit);
|
||||
assert(trace_num_misses.size() == trace_num_accesses.size());
|
||||
for (auto const& num_miss : trace_num_misses) {
|
||||
uint64_t time = num_miss.first;
|
||||
start_time = std::min(start_time, time);
|
||||
end_time = std::max(end_time, time);
|
||||
uint64_t miss = num_miss.second;
|
||||
auto it = trace_num_accesses.find(time);
|
||||
assert(it != trace_num_accesses.end());
|
||||
uint64_t access = it->second;
|
||||
cs_name_timeline[port::kMaxUint64]["trace"][time] = percent(miss, access);
|
||||
}
|
||||
for (auto const& config_caches : cache_simulator_->sim_caches()) {
|
||||
const CacheConfiguration& config = config_caches.first;
|
||||
std::string cache_label = config.cache_name + "-" +
|
||||
std::to_string(config.num_shard_bits) + "-" +
|
||||
std::to_string(config.ghost_cache_capacity);
|
||||
for (uint32_t i = 0; i < config.cache_capacities.size(); i++) {
|
||||
const std::map<uint64_t, uint64_t>& num_misses = adjust_time_unit(
|
||||
config_caches.second[i]->miss_ratio_stats().num_misses_timeline(),
|
||||
time_unit);
|
||||
const std::map<uint64_t, uint64_t>& num_accesses = adjust_time_unit(
|
||||
config_caches.second[i]->miss_ratio_stats().num_accesses_timeline(),
|
||||
time_unit);
|
||||
assert(num_misses.size() == num_accesses.size());
|
||||
for (auto const& num_miss : num_misses) {
|
||||
uint64_t time = num_miss.first;
|
||||
start_time = std::min(start_time, time);
|
||||
end_time = std::max(end_time, time);
|
||||
uint64_t miss = num_miss.second;
|
||||
auto it = num_accesses.find(time);
|
||||
assert(it != num_accesses.end());
|
||||
uint64_t access = it->second;
|
||||
cs_name_timeline[config.cache_capacities[i]][cache_label][time] =
|
||||
percent(miss, access);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto const& it : cs_name_timeline) {
|
||||
const std::string output_miss_ratio_timeline_path =
|
||||
output_dir_ + "/" + std::to_string(it.first) + "_" +
|
||||
std::to_string(time_unit) + "_" + kFileNameSuffixMissRatioTimeline;
|
||||
std::ofstream out(output_miss_ratio_timeline_path);
|
||||
if (!out.is_open()) {
|
||||
return;
|
||||
}
|
||||
std::string header("time");
|
||||
for (uint64_t now = start_time; now <= end_time; now++) {
|
||||
header += ",";
|
||||
header += std::to_string(now);
|
||||
}
|
||||
out << header << std::endl;
|
||||
for (auto const& label : it.second) {
|
||||
std::string row(label.first);
|
||||
for (uint64_t now = start_time; now <= end_time; now++) {
|
||||
auto misses = label.second.find(now);
|
||||
row += ",";
|
||||
if (misses != label.second.end()) {
|
||||
row += std::to_string(misses->second);
|
||||
} else {
|
||||
row += "0";
|
||||
}
|
||||
}
|
||||
out << row << std::endl;
|
||||
}
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockCacheTraceAnalyzer::WriteMissTimeline(uint64_t time_unit) const {
|
||||
if (!cache_simulator_ || output_dir_.empty()) {
|
||||
return;
|
||||
}
|
||||
std::map<uint64_t, std::map<std::string, std::map<uint64_t, uint64_t>>>
|
||||
cs_name_timeline;
|
||||
uint64_t start_time = port::kMaxUint64;
|
||||
uint64_t end_time = 0;
|
||||
const std::map<uint64_t, uint64_t>& trace_num_misses =
|
||||
adjust_time_unit(miss_ratio_stats_.num_misses_timeline(), time_unit);
|
||||
for (auto const& num_miss : trace_num_misses) {
|
||||
uint64_t time = num_miss.first;
|
||||
start_time = std::min(start_time, time);
|
||||
end_time = std::max(end_time, time);
|
||||
uint64_t miss = num_miss.second;
|
||||
cs_name_timeline[port::kMaxUint64]["trace"][time] = miss;
|
||||
}
|
||||
for (auto const& config_caches : cache_simulator_->sim_caches()) {
|
||||
const CacheConfiguration& config = config_caches.first;
|
||||
std::string cache_label = config.cache_name + "-" +
|
||||
std::to_string(config.num_shard_bits) + "-" +
|
||||
std::to_string(config.ghost_cache_capacity);
|
||||
for (uint32_t i = 0; i < config.cache_capacities.size(); i++) {
|
||||
const std::map<uint64_t, uint64_t>& num_misses = adjust_time_unit(
|
||||
config_caches.second[i]->miss_ratio_stats().num_misses_timeline(),
|
||||
time_unit);
|
||||
for (auto const& num_miss : num_misses) {
|
||||
uint64_t time = num_miss.first;
|
||||
start_time = std::min(start_time, time);
|
||||
end_time = std::max(end_time, time);
|
||||
uint64_t miss = num_miss.second;
|
||||
cs_name_timeline[config.cache_capacities[i]][cache_label][time] = miss;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto const& it : cs_name_timeline) {
|
||||
const std::string output_miss_ratio_timeline_path =
|
||||
output_dir_ + "/" + std::to_string(it.first) + "_" +
|
||||
std::to_string(time_unit) + "_" + kFileNameSuffixMissTimeline;
|
||||
std::ofstream out(output_miss_ratio_timeline_path);
|
||||
if (!out.is_open()) {
|
||||
return;
|
||||
}
|
||||
std::string header("time");
|
||||
for (uint64_t now = start_time; now <= end_time; now++) {
|
||||
header += ",";
|
||||
header += std::to_string(now);
|
||||
}
|
||||
out << header << std::endl;
|
||||
for (auto const& label : it.second) {
|
||||
std::string row(label.first);
|
||||
for (uint64_t now = start_time; now <= end_time; now++) {
|
||||
auto misses = label.second.find(now);
|
||||
row += ",";
|
||||
if (misses != label.second.end()) {
|
||||
row += std::to_string(misses->second);
|
||||
} else {
|
||||
row += "0";
|
||||
}
|
||||
}
|
||||
out << row << std::endl;
|
||||
}
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockCacheTraceAnalyzer::WriteCorrelationFeatures(
|
||||
const std::string& label_str, uint32_t max_number_of_values) const {
|
||||
std::set<std::string> labels = ParseLabelStr(label_str);
|
||||
std::map<std::string, Features> label_features;
|
||||
std::map<std::string, Predictions> label_predictions;
|
||||
auto block_callback =
|
||||
[&](const std::string& cf_name, uint64_t fd, uint32_t level,
|
||||
TraceType block_type, const std::string& /*block_key*/,
|
||||
uint64_t /*block_key_id*/, const BlockAccessInfo& block) {
|
||||
if (labels.find(kGroupbyCaller) != labels.end()) {
|
||||
// Group by caller.
|
||||
for (auto const& caller_map : block.caller_access_timeline) {
|
||||
const std::string label =
|
||||
BuildLabel(labels, cf_name, fd, level, block_type,
|
||||
caller_map.first, /*block_id=*/0);
|
||||
auto it = block.caller_access_sequence__number_timeline.find(
|
||||
caller_map.first);
|
||||
assert(it != block.caller_access_sequence__number_timeline.end());
|
||||
UpdateFeatureVectors(it->second, caller_map.second, label,
|
||||
&label_features, &label_predictions);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const std::string label = BuildLabel(
|
||||
labels, cf_name, fd, level, block_type,
|
||||
TableReaderCaller::kMaxBlockCacheLookupCaller, /*block_id=*/0);
|
||||
UpdateFeatureVectors(block.access_sequence_number_timeline,
|
||||
block.access_timeline, label, &label_features,
|
||||
&label_predictions);
|
||||
};
|
||||
TraverseBlocks(block_callback);
|
||||
WriteCorrelationFeaturesToFile(label_str, label_features, label_predictions,
|
||||
max_number_of_values);
|
||||
}
|
||||
|
||||
void BlockCacheTraceAnalyzer::WriteCorrelationFeaturesToFile(
|
||||
const std::string& label,
|
||||
const std::map<std::string, Features>& label_features,
|
||||
const std::map<std::string, Predictions>& label_predictions,
|
||||
uint32_t max_number_of_values) const {
|
||||
std::default_random_engine rand_engine(env_->NowMicros());
|
||||
for (auto const& label_feature_vectors : label_features) {
|
||||
const Features& past = label_feature_vectors.second;
|
||||
auto it = label_predictions.find(label_feature_vectors.first);
|
||||
assert(it != label_predictions.end());
|
||||
const Predictions& future = it->second;
|
||||
const std::string output_path = output_dir_ + "/" + label + "_" +
|
||||
label_feature_vectors.first + "_" +
|
||||
kFileNameSuffixCorrelation;
|
||||
std::ofstream out(output_path);
|
||||
if (!out.is_open()) {
|
||||
return;
|
||||
}
|
||||
std::string header(
|
||||
"num_accesses_since_last_access,elapsed_time_since_last_access,num_"
|
||||
"past_accesses,num_accesses_till_next_access,elapsed_time_till_next_"
|
||||
"access");
|
||||
out << header << std::endl;
|
||||
std::vector<uint32_t> indexes;
|
||||
for (uint32_t i = 0; i < past.num_accesses_since_last_access.size(); i++) {
|
||||
indexes.push_back(i);
|
||||
}
|
||||
std::shuffle(indexes.begin(), indexes.end(), rand_engine);
|
||||
for (uint32_t i = 0; i < max_number_of_values && i < indexes.size(); i++) {
|
||||
uint32_t rand_index = indexes[i];
|
||||
out << std::to_string(past.num_accesses_since_last_access[rand_index])
|
||||
<< ",";
|
||||
out << std::to_string(past.elapsed_time_since_last_access[rand_index])
|
||||
<< ",";
|
||||
out << std::to_string(past.num_past_accesses[rand_index]) << ",";
|
||||
out << std::to_string(future.num_accesses_till_next_access[rand_index])
|
||||
<< ",";
|
||||
out << std::to_string(future.elapsed_time_till_next_access[rand_index])
|
||||
<< std::endl;
|
||||
}
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockCacheTraceAnalyzer::WriteCorrelationFeaturesForGet(
|
||||
uint32_t max_number_of_values) const {
|
||||
std::string label = "GetKeyInfo";
|
||||
std::map<std::string, Features> label_features;
|
||||
std::map<std::string, Predictions> label_predictions;
|
||||
for (auto const& get_info : get_key_info_map_) {
|
||||
const GetKeyInfo& info = get_info.second;
|
||||
UpdateFeatureVectors(info.access_sequence_number_timeline,
|
||||
info.access_timeline, label, &label_features,
|
||||
&label_predictions);
|
||||
}
|
||||
WriteCorrelationFeaturesToFile(label, label_features, label_predictions,
|
||||
max_number_of_values);
|
||||
}
|
||||
|
||||
std::set<std::string> BlockCacheTraceAnalyzer::ParseLabelStr(
|
||||
const std::string& label_str) const {
|
||||
std::stringstream ss(label_str);
|
||||
@@ -371,7 +684,6 @@ void BlockCacheTraceAnalyzer::TraverseBlocks(
|
||||
uint64_t /*block_key_id*/,
|
||||
const BlockAccessInfo& /*block_access_info*/)>
|
||||
block_callback) const {
|
||||
uint64_t block_id = 0;
|
||||
for (auto const& cf_aggregates : cf_aggregates_map_) {
|
||||
// Stats per column family.
|
||||
const std::string& cf_name = cf_aggregates.first;
|
||||
@@ -387,8 +699,8 @@ void BlockCacheTraceAnalyzer::TraverseBlocks(
|
||||
block_type_aggregates.second.block_access_info_map) {
|
||||
// Stats per block.
|
||||
block_callback(cf_name, fd, level, type, block_access_info.first,
|
||||
block_id, block_access_info.second);
|
||||
block_id++;
|
||||
block_access_info.second.block_id,
|
||||
block_access_info.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1046,12 +1358,15 @@ void BlockCacheTraceAnalyzer::WriteAccessCountSummaryStats(
|
||||
|
||||
BlockCacheTraceAnalyzer::BlockCacheTraceAnalyzer(
|
||||
const std::string& trace_file_path, const std::string& output_dir,
|
||||
bool compute_reuse_distance,
|
||||
const std::string& human_readable_trace_file_path,
|
||||
bool compute_reuse_distance, bool mrc_only,
|
||||
std::unique_ptr<BlockCacheTraceSimulator>&& cache_simulator)
|
||||
: env_(rocksdb::Env::Default()),
|
||||
trace_file_path_(trace_file_path),
|
||||
output_dir_(output_dir),
|
||||
human_readable_trace_file_path_(human_readable_trace_file_path),
|
||||
compute_reuse_distance_(compute_reuse_distance),
|
||||
mrc_only_(mrc_only),
|
||||
cache_simulator_(std::move(cache_simulator)) {}
|
||||
|
||||
void BlockCacheTraceAnalyzer::ComputeReuseDistance(
|
||||
@@ -1072,7 +1387,28 @@ void BlockCacheTraceAnalyzer::ComputeReuseDistance(
|
||||
info->unique_blocks_since_last_access.clear();
|
||||
}
|
||||
|
||||
void BlockCacheTraceAnalyzer::RecordAccess(
|
||||
Status BlockCacheTraceAnalyzer::WriteHumanReadableTraceRecord(
|
||||
const BlockCacheTraceRecord& access, uint64_t block_id,
|
||||
uint64_t get_key_id) {
|
||||
if (!human_readable_trace_file_writer_) {
|
||||
return Status::OK();
|
||||
}
|
||||
int ret = snprintf(
|
||||
trace_record_buffer_, sizeof(trace_record_buffer_),
|
||||
"%" PRIu64 ",%" PRIu64 ",%u,%" PRIu64 ",%" PRIu64 ",%s,%" PRIu32
|
||||
",%" PRIu64 ",%u,%u,%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%u\n",
|
||||
access.access_timestamp, block_id, access.block_type, access.block_size,
|
||||
access.cf_id, access.cf_name.c_str(), access.level, access.sst_fd_number,
|
||||
access.caller, access.no_insert, access.get_id, get_key_id,
|
||||
access.referenced_data_size, access.is_cache_hit);
|
||||
if (ret < 0) {
|
||||
return Status::IOError("failed to format the output");
|
||||
}
|
||||
std::string printout(trace_record_buffer_);
|
||||
return human_readable_trace_file_writer_->Append(printout);
|
||||
}
|
||||
|
||||
Status BlockCacheTraceAnalyzer::RecordAccess(
|
||||
const BlockCacheTraceRecord& access) {
|
||||
ColumnFamilyAccessInfoAggregate& cf_aggr = cf_aggregates_map_[access.cf_name];
|
||||
SSTFileAccessInfoAggregate& file_aggr =
|
||||
@@ -1080,18 +1416,30 @@ void BlockCacheTraceAnalyzer::RecordAccess(
|
||||
file_aggr.level = access.level;
|
||||
BlockTypeAccessInfoAggregate& block_type_aggr =
|
||||
file_aggr.block_type_aggregates_map[access.block_type];
|
||||
if (block_type_aggr.block_access_info_map.find(access.block_key) ==
|
||||
block_type_aggr.block_access_info_map.end()) {
|
||||
block_type_aggr.block_access_info_map[access.block_key].block_id =
|
||||
unique_block_id_;
|
||||
unique_block_id_++;
|
||||
}
|
||||
BlockAccessInfo& block_access_info =
|
||||
block_type_aggr.block_access_info_map[access.block_key];
|
||||
if (compute_reuse_distance_) {
|
||||
ComputeReuseDistance(&block_access_info);
|
||||
}
|
||||
block_access_info.AddAccess(access);
|
||||
block_access_info.AddAccess(access, access_sequence_number_);
|
||||
block_info_map_[access.block_key] = &block_access_info;
|
||||
if (trace_start_timestamp_in_seconds_ == 0) {
|
||||
trace_start_timestamp_in_seconds_ =
|
||||
access.access_timestamp / kMicrosInSecond;
|
||||
uint64_t get_key_id = 0;
|
||||
if (access.caller == TableReaderCaller::kUserGet &&
|
||||
access.get_id != BlockCacheTraceHelper::kReservedGetId) {
|
||||
std::string row_key = BlockCacheTraceHelper::ComputeRowKey(access);
|
||||
if (get_key_info_map_.find(row_key) == get_key_info_map_.end()) {
|
||||
get_key_info_map_[row_key].key_id = unique_get_key_id_;
|
||||
get_key_id = unique_get_key_id_;
|
||||
unique_get_key_id_++;
|
||||
}
|
||||
get_key_info_map_[row_key].AddAccess(access, access_sequence_number_);
|
||||
}
|
||||
trace_end_timestamp_in_seconds_ = access.access_timestamp / kMicrosInSecond;
|
||||
|
||||
if (compute_reuse_distance_) {
|
||||
// Add this block to all existing blocks.
|
||||
@@ -1108,6 +1456,8 @@ void BlockCacheTraceAnalyzer::RecordAccess(
|
||||
}
|
||||
}
|
||||
}
|
||||
return WriteHumanReadableTraceRecord(access, block_access_info.block_id,
|
||||
get_key_id);
|
||||
}
|
||||
|
||||
Status BlockCacheTraceAnalyzer::Analyze() {
|
||||
@@ -1122,32 +1472,68 @@ Status BlockCacheTraceAnalyzer::Analyze() {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (!human_readable_trace_file_path_.empty()) {
|
||||
s = env_->NewWritableFile(human_readable_trace_file_path_,
|
||||
&human_readable_trace_file_writer_, EnvOptions());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
uint64_t start = env_->NowMicros();
|
||||
uint64_t processed_records = 0;
|
||||
uint64_t time_interval = 0;
|
||||
while (s.ok()) {
|
||||
BlockCacheTraceRecord access;
|
||||
s = reader.ReadAccess(&access);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
break;
|
||||
}
|
||||
RecordAccess(access);
|
||||
if (!mrc_only_) {
|
||||
s = RecordAccess(access);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trace_start_timestamp_in_seconds_ == 0) {
|
||||
trace_start_timestamp_in_seconds_ =
|
||||
access.access_timestamp / kMicrosInSecond;
|
||||
}
|
||||
trace_end_timestamp_in_seconds_ = access.access_timestamp / kMicrosInSecond;
|
||||
miss_ratio_stats_.UpdateMetrics(access.access_timestamp,
|
||||
is_user_access(access.caller),
|
||||
access.is_cache_hit == Boolean::kFalse);
|
||||
if (cache_simulator_) {
|
||||
cache_simulator_->Access(access);
|
||||
}
|
||||
processed_records++;
|
||||
access_sequence_number_++;
|
||||
uint64_t now = env_->NowMicros();
|
||||
uint64_t duration = (now - start) / kMicrosInSecond;
|
||||
if (duration > 10 * time_interval) {
|
||||
uint64_t trace_duration =
|
||||
trace_end_timestamp_in_seconds_ - trace_start_timestamp_in_seconds_;
|
||||
fprintf(stdout,
|
||||
"Running for %" PRIu64 " seconds: Processed %" PRIu64
|
||||
" records/second\n",
|
||||
duration, processed_records / duration);
|
||||
processed_records = 0;
|
||||
" records/second. Trace duration %" PRIu64
|
||||
" seconds. Observed miss ratio %.2f\n",
|
||||
duration, duration > 0 ? access_sequence_number_ / duration : 0,
|
||||
trace_duration, miss_ratio_stats_.miss_ratio());
|
||||
time_interval++;
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
if (human_readable_trace_file_writer_) {
|
||||
human_readable_trace_file_writer_->Flush();
|
||||
human_readable_trace_file_writer_->Close();
|
||||
}
|
||||
uint64_t now = env_->NowMicros();
|
||||
uint64_t duration = (now - start) / kMicrosInSecond;
|
||||
uint64_t trace_duration =
|
||||
trace_end_timestamp_in_seconds_ - trace_start_timestamp_in_seconds_;
|
||||
fprintf(stdout,
|
||||
"Running for %" PRIu64 " seconds: Processed %" PRIu64
|
||||
" records/second. Trace duration %" PRIu64
|
||||
" seconds. Observed miss ratio %.2f\n",
|
||||
duration, duration > 0 ? access_sequence_number_ / duration : 0,
|
||||
trace_duration, miss_ratio_stats_.miss_ratio());
|
||||
return s;
|
||||
}
|
||||
|
||||
void BlockCacheTraceAnalyzer::PrintBlockSizeStats() const {
|
||||
@@ -1250,7 +1636,7 @@ void BlockCacheTraceAnalyzer::PrintAccessCountStats(bool user_access_only,
|
||||
}
|
||||
fprintf(stdout,
|
||||
"Bottom %" PRIu32 " access count. Access count=%" PRIu64
|
||||
" nblocks=%" PRIu64 " %s\n",
|
||||
" nblocks=%" ROCKSDB_PRIszt " %s\n",
|
||||
bottom_k, naccess_it->first, naccess_it->second.size(),
|
||||
statistics.c_str());
|
||||
}
|
||||
@@ -1321,15 +1707,6 @@ void BlockCacheTraceAnalyzer::PrintAccessCountStats(bool user_access_only,
|
||||
"Top %" PRIu32 " access count blocks access_count=%" PRIu64
|
||||
" %s\n",
|
||||
top_k, naccess_it->first, statistics.c_str());
|
||||
// if (block->referenced_data_size > block->block_size) {
|
||||
// for (auto const& ref_key_it : block->key_num_access_map) {
|
||||
// ParsedInternalKey internal_key;
|
||||
// ParseInternalKey(ref_key_it.first, &internal_key);
|
||||
// printf("######%lu %lu %d %s\n", block->referenced_data_size,
|
||||
// block->block_size, internal_key.type,
|
||||
// internal_key.user_key.ToString().c_str());
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1696,16 +2073,32 @@ int block_cache_trace_analyzer_tool(int argc, char** argv) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
BlockCacheTraceAnalyzer analyzer(
|
||||
FLAGS_block_cache_trace_path, FLAGS_block_cache_analysis_result_dir,
|
||||
!FLAGS_reuse_distance_labels.empty(), std::move(cache_simulator));
|
||||
BlockCacheTraceAnalyzer analyzer(FLAGS_block_cache_trace_path,
|
||||
FLAGS_block_cache_analysis_result_dir,
|
||||
FLAGS_human_readable_trace_file_path,
|
||||
!FLAGS_reuse_distance_labels.empty(),
|
||||
FLAGS_mrc_only, std::move(cache_simulator));
|
||||
Status s = analyzer.Analyze();
|
||||
if (!s.IsIncomplete()) {
|
||||
if (!s.IsIncomplete() && !s.ok()) {
|
||||
// Read all traces.
|
||||
fprintf(stderr, "Cannot process the trace %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
fprintf(stdout, "Status: %s\n", s.ToString().c_str());
|
||||
analyzer.WriteMissRatioCurves();
|
||||
analyzer.WriteMissRatioTimeline(1);
|
||||
analyzer.WriteMissRatioTimeline(kSecondInMinute);
|
||||
analyzer.WriteMissRatioTimeline(kSecondInHour);
|
||||
analyzer.WriteMissTimeline(1);
|
||||
analyzer.WriteMissTimeline(kSecondInMinute);
|
||||
analyzer.WriteMissTimeline(kSecondInHour);
|
||||
|
||||
if (FLAGS_mrc_only) {
|
||||
fprintf(stdout,
|
||||
"Skipping the analysis statistics since the user wants to compute "
|
||||
"MRC only");
|
||||
return 0;
|
||||
}
|
||||
|
||||
analyzer.PrintStatsSummary();
|
||||
if (FLAGS_print_access_count_stats) {
|
||||
@@ -1727,7 +2120,6 @@ int block_cache_trace_analyzer_tool(int argc, char** argv) {
|
||||
analyzer.PrintDataBlockAccessStats();
|
||||
}
|
||||
print_break_lines(/*num_break_lines=*/3);
|
||||
analyzer.WriteMissRatioCurves();
|
||||
|
||||
if (!FLAGS_timeline_labels.empty()) {
|
||||
std::stringstream ss(FLAGS_timeline_labels);
|
||||
@@ -1741,6 +2133,7 @@ int block_cache_trace_analyzer_tool(int argc, char** argv) {
|
||||
analyzer.WriteAccessTimeline(label, kSecondInHour, false);
|
||||
} else {
|
||||
analyzer.WriteAccessTimeline(label, kSecondInMinute, false);
|
||||
analyzer.WriteAccessTimeline(label, kSecondInHour, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1819,6 +2212,18 @@ int block_cache_trace_analyzer_tool(int argc, char** argv) {
|
||||
analyzer.WriteGetSpatialLocality(label, buckets);
|
||||
}
|
||||
}
|
||||
|
||||
if (!FLAGS_analyze_correlation_coefficients_labels.empty()) {
|
||||
std::stringstream ss(FLAGS_analyze_correlation_coefficients_labels);
|
||||
while (ss.good()) {
|
||||
std::string label;
|
||||
getline(ss, label, ',');
|
||||
analyzer.WriteCorrelationFeatures(
|
||||
label, FLAGS_analyze_correlation_coefficients_max_number_of_values);
|
||||
}
|
||||
analyzer.WriteCorrelationFeaturesForGet(
|
||||
FLAGS_analyze_correlation_coefficients_max_number_of_values);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
+92
-3
@@ -16,8 +16,23 @@
|
||||
#include "utilities/simulator_cache/cache_simulator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Statistics of a key refereneced by a Get.
|
||||
struct GetKeyInfo {
|
||||
uint64_t key_id = 0;
|
||||
std::vector<uint64_t> access_sequence_number_timeline;
|
||||
std::vector<uint64_t> access_timeline;
|
||||
|
||||
void AddAccess(const BlockCacheTraceRecord& access,
|
||||
uint64_t access_sequnce_number) {
|
||||
access_sequence_number_timeline.push_back(access_sequnce_number);
|
||||
access_timeline.push_back(access.access_timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
// Statistics of a block.
|
||||
struct BlockAccessInfo {
|
||||
uint64_t block_id = 0;
|
||||
uint64_t num_accesses = 0;
|
||||
uint64_t block_size = 0;
|
||||
uint64_t first_access_time = 0;
|
||||
@@ -39,7 +54,16 @@ struct BlockAccessInfo {
|
||||
// Number of reuses grouped by reuse distance.
|
||||
std::map<uint64_t, uint64_t> reuse_distance_count;
|
||||
|
||||
void AddAccess(const BlockCacheTraceRecord& access) {
|
||||
// The access sequence numbers of this block.
|
||||
std::vector<uint64_t> access_sequence_number_timeline;
|
||||
std::map<TableReaderCaller, std::vector<uint64_t>>
|
||||
caller_access_sequence__number_timeline;
|
||||
// The access timestamp in microseconds of this block.
|
||||
std::vector<uint64_t> access_timeline;
|
||||
std::map<TableReaderCaller, std::vector<uint64_t>> caller_access_timeline;
|
||||
|
||||
void AddAccess(const BlockCacheTraceRecord& access,
|
||||
uint64_t access_sequnce_number) {
|
||||
if (block_size != 0 && access.block_size != 0) {
|
||||
assert(block_size == access.block_size);
|
||||
}
|
||||
@@ -57,6 +81,12 @@ struct BlockAccessInfo {
|
||||
const uint64_t timestamp_in_seconds =
|
||||
access.access_timestamp / kMicrosInSecond;
|
||||
caller_num_accesses_timeline[access.caller][timestamp_in_seconds] += 1;
|
||||
// Populate the feature vectors.
|
||||
access_sequence_number_timeline.push_back(access_sequnce_number);
|
||||
caller_access_sequence__number_timeline[access.caller].push_back(
|
||||
access_sequnce_number);
|
||||
access_timeline.push_back(access.access_timestamp);
|
||||
caller_access_timeline[access.caller].push_back(access.access_timestamp);
|
||||
if (BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(access.block_type,
|
||||
access.caller)) {
|
||||
num_keys = access.num_keys_in_block;
|
||||
@@ -94,11 +124,23 @@ struct ColumnFamilyAccessInfoAggregate {
|
||||
std::map<uint64_t, SSTFileAccessInfoAggregate> fd_aggregates_map;
|
||||
};
|
||||
|
||||
struct Features {
|
||||
std::vector<uint64_t> elapsed_time_since_last_access;
|
||||
std::vector<uint64_t> num_accesses_since_last_access;
|
||||
std::vector<uint64_t> num_past_accesses;
|
||||
};
|
||||
|
||||
struct Predictions {
|
||||
std::vector<uint64_t> elapsed_time_till_next_access;
|
||||
std::vector<uint64_t> num_accesses_till_next_access;
|
||||
};
|
||||
|
||||
class BlockCacheTraceAnalyzer {
|
||||
public:
|
||||
BlockCacheTraceAnalyzer(
|
||||
const std::string& trace_file_path, const std::string& output_dir,
|
||||
bool compute_reuse_distance,
|
||||
const std::string& human_readable_trace_file_path,
|
||||
bool compute_reuse_distance, bool mrc_only,
|
||||
std::unique_ptr<BlockCacheTraceSimulator>&& cache_simulator);
|
||||
~BlockCacheTraceAnalyzer() = default;
|
||||
// No copy and move.
|
||||
@@ -184,6 +226,24 @@ class BlockCacheTraceAnalyzer {
|
||||
// "cache_name,num_shard_bits,capacity,miss_ratio,total_accesses".
|
||||
void WriteMissRatioCurves() const;
|
||||
|
||||
// Write miss ratio timeline of simulated cache configurations into several
|
||||
// csv files, one per cache capacity saved in 'output_dir'.
|
||||
//
|
||||
// The file format is
|
||||
// "time,label_1_access_per_second,label_2_access_per_second,...,label_N_access_per_second"
|
||||
// where N is the number of unique cache names
|
||||
// (cache_name+num_shard_bits+ghost_capacity).
|
||||
void WriteMissRatioTimeline(uint64_t time_unit) const;
|
||||
|
||||
// Write misses timeline of simulated cache configurations into several
|
||||
// csv files, one per cache capacity saved in 'output_dir'.
|
||||
//
|
||||
// The file format is
|
||||
// "time,label_1_access_per_second,label_2_access_per_second,...,label_N_access_per_second"
|
||||
// where N is the number of unique cache names
|
||||
// (cache_name+num_shard_bits+ghost_capacity).
|
||||
void WriteMissTimeline(uint64_t time_unit) const;
|
||||
|
||||
// Write the access timeline into a csv file saved in 'output_dir'.
|
||||
//
|
||||
// The file is named "label_access_timeline".The file format is
|
||||
@@ -236,6 +296,11 @@ class BlockCacheTraceAnalyzer {
|
||||
const std::string& label_str,
|
||||
const std::vector<uint64_t>& percent_buckets) const;
|
||||
|
||||
void WriteCorrelationFeatures(const std::string& label_str,
|
||||
uint32_t max_number_of_values) const;
|
||||
|
||||
void WriteCorrelationFeaturesForGet(uint32_t max_number_of_values) const;
|
||||
|
||||
const std::map<std::string, ColumnFamilyAccessInfoAggregate>&
|
||||
TEST_cf_aggregates_map() const {
|
||||
return cf_aggregates_map_;
|
||||
@@ -251,7 +316,7 @@ class BlockCacheTraceAnalyzer {
|
||||
|
||||
void ComputeReuseDistance(BlockAccessInfo* info) const;
|
||||
|
||||
void RecordAccess(const BlockCacheTraceRecord& access);
|
||||
Status RecordAccess(const BlockCacheTraceRecord& access);
|
||||
|
||||
void UpdateReuseIntervalStats(
|
||||
const std::string& label, const std::vector<uint64_t>& time_buckets,
|
||||
@@ -278,17 +343,41 @@ class BlockCacheTraceAnalyzer {
|
||||
const BlockAccessInfo& /*block_access_info*/)>
|
||||
block_callback) const;
|
||||
|
||||
void UpdateFeatureVectors(
|
||||
const std::vector<uint64_t>& access_sequence_number_timeline,
|
||||
const std::vector<uint64_t>& access_timeline, const std::string& label,
|
||||
std::map<std::string, Features>* label_features,
|
||||
std::map<std::string, Predictions>* label_predictions) const;
|
||||
|
||||
void WriteCorrelationFeaturesToFile(
|
||||
const std::string& label,
|
||||
const std::map<std::string, Features>& label_features,
|
||||
const std::map<std::string, Predictions>& label_predictions,
|
||||
uint32_t max_number_of_values) const;
|
||||
|
||||
Status WriteHumanReadableTraceRecord(const BlockCacheTraceRecord& access,
|
||||
uint64_t block_id, uint64_t get_key_id);
|
||||
|
||||
rocksdb::Env* env_;
|
||||
const std::string trace_file_path_;
|
||||
const std::string output_dir_;
|
||||
std::string human_readable_trace_file_path_;
|
||||
const bool compute_reuse_distance_;
|
||||
const bool mrc_only_;
|
||||
|
||||
BlockCacheTraceHeader header_;
|
||||
std::unique_ptr<BlockCacheTraceSimulator> cache_simulator_;
|
||||
std::map<std::string, ColumnFamilyAccessInfoAggregate> cf_aggregates_map_;
|
||||
std::map<std::string, BlockAccessInfo*> block_info_map_;
|
||||
std::unordered_map<std::string, GetKeyInfo> get_key_info_map_;
|
||||
uint64_t access_sequence_number_ = 0;
|
||||
uint64_t trace_start_timestamp_in_seconds_ = 0;
|
||||
uint64_t trace_end_timestamp_in_seconds_ = 0;
|
||||
MissRatioStats miss_ratio_stats_;
|
||||
uint64_t unique_block_id_ = 1;
|
||||
uint64_t unique_get_key_id_ = 1;
|
||||
char trace_record_buffer_[1024 * 1024];
|
||||
std::unique_ptr<rocksdb::WritableFile> human_readable_trace_file_writer_;
|
||||
};
|
||||
|
||||
int block_cache_trace_analyzer_tool(int argc, char** argv);
|
||||
File renamed without changes.
+73
-7
@@ -23,7 +23,7 @@ int main() {
|
||||
#include "rocksdb/trace_reader_writer.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "tools/block_cache_trace_analyzer.h"
|
||||
#include "tools/block_cache_analyzer/block_cache_trace_analyzer.h"
|
||||
#include "trace_replay/block_cache_tracer.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -117,7 +117,8 @@ class BlockCacheTracerTest : public testing::Test {
|
||||
// Provide these fields for all block types.
|
||||
// The writer should only write these fields for data blocks and the
|
||||
// caller is either GET or MGET.
|
||||
record.referenced_key = kRefKeyPrefix + std::to_string(key_id);
|
||||
record.referenced_key =
|
||||
kRefKeyPrefix + std::to_string(key_id) + std::string(8, 0);
|
||||
record.referenced_key_exist_in_block = Boolean::kTrue;
|
||||
record.num_keys_in_block = kNumKeysInBlock;
|
||||
ASSERT_OK(writer->WriteBlockAccess(
|
||||
@@ -179,7 +180,8 @@ class BlockCacheTracerTest : public testing::Test {
|
||||
"-analyze_get_spatial_locality_labels=" +
|
||||
analyze_get_spatial_locality_labels_,
|
||||
"-analyze_get_spatial_locality_buckets=" +
|
||||
analyze_get_spatial_locality_buckets_};
|
||||
analyze_get_spatial_locality_buckets_,
|
||||
"-analyze_correlation_coefficients_labels=all"};
|
||||
char arg_buffer[kArgBufferSize];
|
||||
char* argv[kMaxArgCount];
|
||||
int argc = 0;
|
||||
@@ -236,9 +238,9 @@ TEST_F(BlockCacheTracerTest, BlockCacheAnalyzer) {
|
||||
RunBlockCacheTraceAnalyzer();
|
||||
{
|
||||
// Validate the cache miss ratios.
|
||||
const std::vector<uint64_t> expected_capacities{1024, 1024 * 1024,
|
||||
1024 * 1024 * 1024};
|
||||
const std::string mrc_path = test_path_ + "/mrc";
|
||||
std::vector<uint64_t> expected_capacities{1024, 1024 * 1024,
|
||||
1024 * 1024 * 1024};
|
||||
const std::string mrc_path = test_path_ + "/49_50_mrc";
|
||||
std::ifstream infile(mrc_path);
|
||||
uint32_t config_index = 0;
|
||||
std::string line;
|
||||
@@ -266,6 +268,68 @@ TEST_F(BlockCacheTracerTest, BlockCacheAnalyzer) {
|
||||
ASSERT_EQ(expected_capacities.size(), config_index);
|
||||
infile.close();
|
||||
ASSERT_OK(env_->DeleteFile(mrc_path));
|
||||
|
||||
const std::vector<std::string> time_units{"1", "60", "3600"};
|
||||
expected_capacities.push_back(port::kMaxUint64);
|
||||
for (auto const& expected_capacity : expected_capacities) {
|
||||
for (auto const& time_unit : time_units) {
|
||||
const std::string miss_ratio_timeline_path =
|
||||
test_path_ + "/" + std::to_string(expected_capacity) + "_" +
|
||||
time_unit + "_miss_ratio_timeline";
|
||||
std::ifstream mrt_file(miss_ratio_timeline_path);
|
||||
// Read header.
|
||||
ASSERT_TRUE(getline(mrt_file, line));
|
||||
ASSERT_TRUE(getline(mrt_file, line));
|
||||
std::stringstream ss(line);
|
||||
bool read_header = false;
|
||||
while (ss.good()) {
|
||||
std::string substr;
|
||||
getline(ss, substr, ',');
|
||||
if (!read_header) {
|
||||
if (expected_capacity == port::kMaxUint64) {
|
||||
ASSERT_EQ("trace", substr);
|
||||
} else {
|
||||
ASSERT_EQ("lru-1-0", substr);
|
||||
}
|
||||
read_header = true;
|
||||
continue;
|
||||
}
|
||||
ASSERT_DOUBLE_EQ(100.0, ParseDouble(substr));
|
||||
}
|
||||
ASSERT_FALSE(getline(mrt_file, line));
|
||||
mrt_file.close();
|
||||
ASSERT_OK(env_->DeleteFile(miss_ratio_timeline_path));
|
||||
}
|
||||
for (auto const& time_unit : time_units) {
|
||||
const std::string miss_timeline_path =
|
||||
test_path_ + "/" + std::to_string(expected_capacity) + "_" +
|
||||
time_unit + "_miss_timeline";
|
||||
std::ifstream mt_file(miss_timeline_path);
|
||||
// Read header.
|
||||
ASSERT_TRUE(getline(mt_file, line));
|
||||
ASSERT_TRUE(getline(mt_file, line));
|
||||
std::stringstream ss(line);
|
||||
uint32_t num_misses = 0;
|
||||
while (ss.good()) {
|
||||
std::string substr;
|
||||
getline(ss, substr, ',');
|
||||
if (num_misses == 0) {
|
||||
if (expected_capacity == port::kMaxUint64) {
|
||||
ASSERT_EQ("trace", substr);
|
||||
} else {
|
||||
ASSERT_EQ("lru-1-0", substr);
|
||||
}
|
||||
num_misses++;
|
||||
continue;
|
||||
}
|
||||
num_misses += ParseInt(substr);
|
||||
}
|
||||
ASSERT_EQ(51, num_misses);
|
||||
ASSERT_FALSE(getline(mt_file, line));
|
||||
mt_file.close();
|
||||
ASSERT_OK(env_->DeleteFile(miss_timeline_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Validate the timeline csv files.
|
||||
@@ -279,7 +343,7 @@ TEST_F(BlockCacheTracerTest, BlockCacheAnalyzer) {
|
||||
std::string l;
|
||||
ASSERT_TRUE(getline(ss, l, ','));
|
||||
if (l.find("block") == std::string::npos) {
|
||||
if (unit != "_60" || user_access_only != "all_access_") {
|
||||
if (user_access_only != "all_access_") {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -543,7 +607,9 @@ TEST_F(BlockCacheTracerTest, MixedBlocks) {
|
||||
// Read blocks.
|
||||
BlockCacheTraceAnalyzer analyzer(trace_file_path_,
|
||||
/*output_miss_ratio_curve_path=*/"",
|
||||
/*human_readable_trace_file_path=*/"",
|
||||
/*compute_reuse_distance=*/true,
|
||||
/*mrc_only=*/false,
|
||||
/*simulator=*/nullptr);
|
||||
// The analyzer ends when it detects an incomplete access record.
|
||||
ASSERT_EQ(Status::Incomplete(""), analyzer.Analyze());
|
||||
+1
-1
@@ -11,7 +11,7 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
#else // GFLAGS
|
||||
#include "tools/block_cache_trace_analyzer.h"
|
||||
#include "tools/block_cache_analyzer/block_cache_trace_analyzer.h"
|
||||
int main(int argc, char** argv) {
|
||||
return rocksdb::block_cache_trace_analyzer_tool(argc, argv);
|
||||
}
|
||||
@@ -3049,8 +3049,9 @@ class Benchmark {
|
||||
std::shared_ptr<TimestampEmulator> timestamp_emulator_;
|
||||
std::unique_ptr<port::Thread> secondary_update_thread_;
|
||||
std::atomic<int> secondary_update_stopped_{0};
|
||||
#ifndef ROCKSDB_LITE
|
||||
uint64_t secondary_db_updates_ = 0;
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
struct ThreadArg {
|
||||
Benchmark* bm;
|
||||
SharedState* shared;
|
||||
@@ -6366,13 +6367,12 @@ int db_bench_tool(int argc, char** argv) {
|
||||
exit(1);
|
||||
}
|
||||
if (!FLAGS_statistics_string.empty()) {
|
||||
std::unique_ptr<Statistics> custom_stats_guard;
|
||||
dbstats.reset(NewCustomObject<Statistics>(FLAGS_statistics_string,
|
||||
&custom_stats_guard));
|
||||
custom_stats_guard.release();
|
||||
Status s = ObjectRegistry::NewInstance()->NewSharedObject<Statistics>(
|
||||
FLAGS_statistics_string, &dbstats);
|
||||
if (dbstats == nullptr) {
|
||||
fprintf(stderr, "No Statistics registered matching string: %s\n",
|
||||
FLAGS_statistics_string.c_str());
|
||||
fprintf(stderr,
|
||||
"No Statistics registered matching string: %s status=%s\n",
|
||||
FLAGS_statistics_string.c_str(), s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -6400,12 +6400,11 @@ int db_bench_tool(int argc, char** argv) {
|
||||
StringToCompressionType(FLAGS_compression_type.c_str());
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
std::unique_ptr<Env> custom_env_guard;
|
||||
if (!FLAGS_hdfs.empty() && !FLAGS_env_uri.empty()) {
|
||||
fprintf(stderr, "Cannot provide both --hdfs and --env_uri.\n");
|
||||
exit(1);
|
||||
} else if (!FLAGS_env_uri.empty()) {
|
||||
FLAGS_env = NewCustomObject<Env>(FLAGS_env_uri, &custom_env_guard);
|
||||
Status s = Env::LoadEnv(FLAGS_env_uri, &FLAGS_env);
|
||||
if (FLAGS_env == nullptr) {
|
||||
fprintf(stderr, "No Env registered for URI: %s\n", FLAGS_env_uri.c_str());
|
||||
exit(1);
|
||||
|
||||
@@ -141,6 +141,7 @@ atomic_flush_params = {
|
||||
"write_buffer_size": 1024 * 1024,
|
||||
# disable pipelined write when test_atomic_flush is true
|
||||
"enable_pipelined_write": 0,
|
||||
"snap_refresh_nanos": 0,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -512,6 +512,10 @@ DEFINE_uint64(num_iterations, 10, "Number of iterations per MultiIterate run");
|
||||
static const bool FLAGS_num_iterations_dummy __attribute__((__unused__)) =
|
||||
RegisterFlagValidator(&FLAGS_num_iterations, &ValidateUint32Range);
|
||||
|
||||
DEFINE_uint64(
|
||||
snap_refresh_nanos, 100 * 1000 * 1000,
|
||||
"If non-zero, compactions will periodically refresh snapshot list.");
|
||||
|
||||
namespace {
|
||||
enum rocksdb::CompressionType StringToCompressionType(const char* ctype) {
|
||||
assert(ctype);
|
||||
@@ -2724,6 +2728,8 @@ class StressTest {
|
||||
fprintf(stdout, " %s\n", p.c_str());
|
||||
}
|
||||
}
|
||||
fprintf(stdout, "Snapshot refresh nanos : %" PRIu64 "\n",
|
||||
FLAGS_snap_refresh_nanos);
|
||||
|
||||
fprintf(stdout, "------------------------------------------------\n");
|
||||
}
|
||||
@@ -2873,6 +2879,7 @@ class StressTest {
|
||||
} else {
|
||||
options_.merge_operator = MergeOperators::CreatePutOperator();
|
||||
}
|
||||
options_.snap_refresh_nanos = FLAGS_snap_refresh_nanos;
|
||||
|
||||
fprintf(stdout, "DB path: [%s]\n", FLAGS_db.c_str());
|
||||
|
||||
|
||||
+22
-6
@@ -20,7 +20,6 @@
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
@@ -31,6 +30,7 @@
|
||||
#include "util/coding.h"
|
||||
#include "util/stderr_logger.h"
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "utilities/ttl/db_ttl_impl.h"
|
||||
|
||||
#include <cstdlib>
|
||||
@@ -353,11 +353,24 @@ void LDBCommand::OpenDB() {
|
||||
stderr,
|
||||
"wal_dir loaded from the option file doesn't exist. Ignore it.\n");
|
||||
}
|
||||
|
||||
// If merge operator is not set, set a string append operator. There is
|
||||
// no harm doing it.
|
||||
for (auto& cf_entry : column_families_) {
|
||||
if (!cf_entry.options.merge_operator) {
|
||||
cf_entry.options.merge_operator =
|
||||
MergeOperators::CreateStringAppendOperator(':');
|
||||
}
|
||||
}
|
||||
}
|
||||
options_ = PrepareOptionsForOpenDB();
|
||||
if (!exec_state_.IsNotStarted()) {
|
||||
return;
|
||||
}
|
||||
if (column_families_.empty() && !options_.merge_operator) {
|
||||
// No harm to add a general merge operator if it is not specified.
|
||||
options_.merge_operator = MergeOperators::CreateStringAppendOperator(':');
|
||||
}
|
||||
// Open the DB.
|
||||
Status st;
|
||||
std::vector<ColumnFamilyHandle*> handles_opened;
|
||||
@@ -1301,7 +1314,8 @@ void InternalDumpCommand::DoCommand() {
|
||||
|
||||
// Cast as DBImpl to get internal iterator
|
||||
std::vector<KeyVersion> key_versions;
|
||||
Status st = GetAllKeyVersions(db_, from_, to_, max_keys_, &key_versions);
|
||||
Status st = GetAllKeyVersions(db_, GetCfHandle(), from_, to_, max_keys_,
|
||||
&key_versions);
|
||||
if (!st.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed(st.ToString());
|
||||
return;
|
||||
@@ -2839,8 +2853,9 @@ void BackupCommand::DoCommand() {
|
||||
return;
|
||||
}
|
||||
printf("open db OK\n");
|
||||
std::unique_ptr<Env> custom_env_guard;
|
||||
Env* custom_env = NewCustomObject<Env>(backup_env_uri_, &custom_env_guard);
|
||||
Env* custom_env = nullptr;
|
||||
Env::LoadEnv(backup_env_uri_, &custom_env);
|
||||
|
||||
BackupableDBOptions backup_options =
|
||||
BackupableDBOptions(backup_dir_, custom_env);
|
||||
backup_options.info_log = logger_.get();
|
||||
@@ -2874,8 +2889,9 @@ void RestoreCommand::Help(std::string& ret) {
|
||||
}
|
||||
|
||||
void RestoreCommand::DoCommand() {
|
||||
std::unique_ptr<Env> custom_env_guard;
|
||||
Env* custom_env = NewCustomObject<Env>(backup_env_uri_, &custom_env_guard);
|
||||
Env* custom_env = nullptr;
|
||||
Env::LoadEnv(backup_env_uri_, &custom_env);
|
||||
|
||||
std::unique_ptr<BackupEngineReadOnly> restore_engine;
|
||||
Status status;
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "trace_replay/block_cache_tracer.h"
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/hash.h"
|
||||
@@ -54,6 +55,19 @@ bool BlockCacheTraceHelper::IsUserAccess(TableReaderCaller caller) {
|
||||
caller == TableReaderCaller::kUserVerifyChecksum;
|
||||
}
|
||||
|
||||
std::string BlockCacheTraceHelper::ComputeRowKey(
|
||||
const BlockCacheTraceRecord& access) {
|
||||
if (!IsGetOrMultiGet(access.caller)) {
|
||||
return "";
|
||||
}
|
||||
Slice key = ExtractUserKey(access.referenced_key);
|
||||
uint64_t seq_no = access.get_from_user_specified_snapshot == Boolean::kFalse
|
||||
? 0
|
||||
: 1 + GetInternalKeySeqno(access.referenced_key);
|
||||
return std::to_string(access.sst_fd_number) + "_" + key.ToString() + "_" +
|
||||
std::to_string(seq_no);
|
||||
}
|
||||
|
||||
BlockCacheTraceWriter::BlockCacheTraceWriter(
|
||||
Env* env, const TraceOptions& trace_options,
|
||||
std::unique_ptr<TraceWriter>&& trace_writer)
|
||||
|
||||
@@ -20,6 +20,7 @@ extern const uint64_t kMicrosInSecond;
|
||||
extern const uint64_t kSecondInMinute;
|
||||
extern const uint64_t kSecondInHour;
|
||||
|
||||
struct BlockCacheTraceRecord;
|
||||
|
||||
class BlockCacheTraceHelper {
|
||||
public:
|
||||
@@ -27,7 +28,9 @@ class BlockCacheTraceHelper {
|
||||
TableReaderCaller caller);
|
||||
static bool IsGetOrMultiGet(TableReaderCaller caller);
|
||||
static bool IsUserAccess(TableReaderCaller caller);
|
||||
|
||||
// Row key is a concatenation of the access's fd_number and the referenced
|
||||
// user key.
|
||||
static std::string ComputeRowKey(const BlockCacheTraceRecord& access);
|
||||
static const std::string kUnknownColumnFamilyName;
|
||||
static const uint64_t kReservedGetId;
|
||||
};
|
||||
|
||||
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user