mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 19:12:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a66daec541 | ||
|
|
537ea661cb | ||
|
|
29219c208c | ||
|
|
15a6520154 | ||
|
|
54d6286027 | ||
|
|
cc069f25b3 | ||
|
|
88bc91f3cc | ||
|
|
cd577f6059 | ||
|
|
5502f06729 | ||
|
|
e7c6259447 | ||
|
|
cd21e4e69d | ||
|
|
c74531b1d2 | ||
|
|
d926593df5 | ||
|
|
d8e47620d7 | ||
|
|
ebb5242d55 | ||
|
|
c2ab4e754b | ||
|
|
c1b84d0437 | ||
|
|
c96d9a0fbb | ||
|
|
5c5e018943 | ||
|
|
5a063ecd34 | ||
|
|
4f04f96742 | ||
|
|
44fd914128 | ||
|
|
a143f93236 | ||
|
|
179d2c7646 | ||
|
|
c77b50a4fd | ||
|
|
0ebe1614cb | ||
|
|
2045fe4693 | ||
|
|
ba8fa0f546 | ||
|
|
dce3ca5ab8 | ||
|
|
06dc32ef25 | ||
|
|
be3bc36811 | ||
|
|
7eca51dfc3 | ||
|
|
b760af321f | ||
|
|
a7779458bd | ||
|
|
d68f45e777 | ||
|
|
acc078f878 | ||
|
|
4d04138512 | ||
|
|
6e7701d49b | ||
|
|
4dd2bb8f70 | ||
|
|
f6fd4b9dbd | ||
|
|
a140b519b1 | ||
|
|
324453e579 | ||
|
|
ddb7df10ef |
No files matched your search
@@ -914,8 +914,6 @@ workflows:
|
||||
- build-linux-mini-crashtest
|
||||
jobs-windows:
|
||||
jobs:
|
||||
- build-windows-vs2022-avx2
|
||||
- build-windows-vs2022
|
||||
- build-windows-vs2019
|
||||
- build-cmake-mingw
|
||||
jobs-java:
|
||||
@@ -964,3 +962,5 @@ workflows:
|
||||
- build-linux-non-shm
|
||||
- build-linux-clang-13-asan-ubsan-with-folly
|
||||
- build-linux-valgrind
|
||||
- build-windows-vs2022-avx2
|
||||
- build-windows-vs2022
|
||||
+68
@@ -1,6 +1,74 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 8.10.2 (02/16/2024)
|
||||
* Update zlib to 1.3.1 for Java builds
|
||||
|
||||
## 8.10.1 (01/16/2024)
|
||||
### Bug Fixes
|
||||
* Fix bug in auto_readahead_size that combined with IndexType::kBinarySearchWithFirstKey + fails or iterator lands at a wrong key
|
||||
|
||||
## 8.10.0 (12/15/2023)
|
||||
### New Features
|
||||
* Provide support for async_io to trim readahead_size by doing block cache lookup
|
||||
* Added initial wide-column support in `WriteBatchWithIndex`. This includes the `PutEntity` API and support for wide columns in the existing read APIs (`GetFromBatch`, `GetFromBatchAndDB`, `MultiGetFromBatchAndDB`, and `BaseDeltaIterator`).
|
||||
|
||||
### Public API Changes
|
||||
* Custom implementations of `TablePropertiesCollectorFactory` may now return a `nullptr` collector to decline processing a file, reducing callback overheads in such cases.
|
||||
|
||||
### Behavior Changes
|
||||
* Make ReadOptions.auto_readahead_size default true which does prefetching optimizations for forward scans if iterate_upper_bound and block_cache is also specified.
|
||||
* Compactions can be scheduled in parallel in an additional scenario: high compaction debt relative to the data size
|
||||
* HyperClockCache now has built-in protection against excessive CPU consumption under the extreme stress condition of no (or very few) evictable cache entries, which can slightly increase memory usage such conditions. New option `HyperClockCacheOptions::eviction_effort_cap` controls the space-time trade-off of the response. The default should be generally well-balanced, with no measurable affect on normal operation.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a corner case with auto_readahead_size where Prev Operation returns NOT SUPPORTED error when scans direction is changed from forward to backward.
|
||||
* Avoid destroying the periodic task scheduler's default timer in order to prevent static destruction order issues.
|
||||
* Fix double counting of BYTES_WRITTEN ticker when doing writes with transactions.
|
||||
* Fix a WRITE_STALL counter that was reporting wrong value in few cases.
|
||||
* A lookup by MultiGet in a TieredCache that goes to the local flash cache and finishes with very low latency, i.e before the subsequent call to WaitAll, is ignored, resulting in a false negative and a memory leak.
|
||||
|
||||
### Performance Improvements
|
||||
* Java API extensions to improve consistency and completeness of APIs
|
||||
1 Extended `RocksDB.get([ColumnFamilyHandle columnFamilyHandle,] ReadOptions opt, ByteBuffer key, ByteBuffer value)` which now accepts indirect buffer parameters as well as direct buffer parameters
|
||||
2 Extended `RocksDB.put( [ColumnFamilyHandle columnFamilyHandle,] WriteOptions writeOpts, final ByteBuffer key, final ByteBuffer value)` which now accepts indirect buffer parameters as well as direct buffer parameters
|
||||
3 Added `RocksDB.merge([ColumnFamilyHandle columnFamilyHandle,] WriteOptions writeOptions, ByteBuffer key, ByteBuffer value)` methods with the same parameter options as `put(...)` - direct and indirect buffers are supported
|
||||
4 Added `RocksIterator.key( byte[] key [, int offset, int len])` methods which retrieve the iterator key into the supplied buffer
|
||||
5 Added `RocksIterator.value( byte[] value [, int offset, int len])` methods which retrieve the iterator value into the supplied buffer
|
||||
6 Deprecated `get(final ColumnFamilyHandle columnFamilyHandle, final ReadOptions readOptions, byte[])` in favour of `get(final ReadOptions readOptions, final ColumnFamilyHandle columnFamilyHandle, byte[])` which has consistent parameter ordering with other methods in the same class
|
||||
7 Added `Transaction.get( ReadOptions opt, [ColumnFamilyHandle columnFamilyHandle, ] byte[] key, byte[] value)` methods which retrieve the requested value into the supplied buffer
|
||||
8 Added `Transaction.get( ReadOptions opt, [ColumnFamilyHandle columnFamilyHandle, ] ByteBuffer key, ByteBuffer value)` methods which retrieve the requested value into the supplied buffer
|
||||
9 Added `Transaction.getForUpdate( ReadOptions readOptions, [ColumnFamilyHandle columnFamilyHandle, ] byte[] key, byte[] value, boolean exclusive [, boolean doValidate])` methods which retrieve the requested value into the supplied buffer
|
||||
10 Added `Transaction.getForUpdate( ReadOptions readOptions, [ColumnFamilyHandle columnFamilyHandle, ] ByteBuffer key, ByteBuffer value, boolean exclusive [, boolean doValidate])` methods which retrieve the requested value into the supplied buffer
|
||||
11 Added `Transaction.getIterator()` method as a convenience which defaults the `ReadOptions` value supplied to existing `Transaction.iterator()` methods. This mirrors the existing `RocksDB.iterator()` method.
|
||||
12 Added `Transaction.put([ColumnFamilyHandle columnFamilyHandle, ] ByteBuffer key, ByteBuffer value [, boolean assumeTracked])` methods which supply the key, and the value to be written in a `ByteBuffer` parameter
|
||||
13 Added `Transaction.merge([ColumnFamilyHandle columnFamilyHandle, ] ByteBuffer key, ByteBuffer value [, boolean assumeTracked])` methods which supply the key, and the value to be written/merged in a `ByteBuffer` parameter
|
||||
14 Added `Transaction.mergeUntracked([ColumnFamilyHandle columnFamilyHandle, ] ByteBuffer key, ByteBuffer value)` methods which supply the key, and the value to be written/merged in a `ByteBuffer` parameter
|
||||
|
||||
|
||||
## 8.9.0 (11/17/2023)
|
||||
### New Features
|
||||
* Add GetEntity() and PutEntity() API implementation for Attribute Group support. Through the use of Column Families, AttributeGroup enables users to logically group wide-column entities.
|
||||
|
||||
### Public API Changes
|
||||
* Added rocksdb_ratelimiter_create_auto_tuned API to create an auto-tuned GenericRateLimiter.
|
||||
* Added clipColumnFamily() to the Java API to clip the entries in the CF according to the range [begin_key, end_key).
|
||||
* Make the `EnableFileDeletion` API not default to force enabling. For users that rely on this default behavior and still
|
||||
want to continue to use force enabling, they need to explicitly pass a `true` to `EnableFileDeletion`.
|
||||
* Add new Cache APIs GetSecondaryCacheCapacity() and GetSecondaryCachePinnedUsage() to return the configured capacity, and cache reservation charged to the secondary cache.
|
||||
|
||||
### Behavior Changes
|
||||
* During off-peak hours defined by `daily_offpeak_time_utc`, the compaction picker will select a larger number of files for periodic compaction. This selection will include files that are projected to expire by the next off-peak start time, ensuring that these files are not chosen for periodic compaction outside of off-peak hours.
|
||||
* If an error occurs when writing to a trace file after `DB::StartTrace()`, the subsequent trace writes are skipped to avoid writing to a file that has previously seen error. In this case, `DB::EndTrace()` will also return a non-ok status with info about the error occured previously in its status message.
|
||||
* Deleting stale files upon recovery are delegated to SstFileManger if available so they can be rate limited.
|
||||
* Make RocksDB only call `TablePropertiesCollector::Finish()` once.
|
||||
* When `WAL_ttl_seconds > 0`, we now process archived WALs for deletion at least every `WAL_ttl_seconds / 2` seconds. Previously it could be less frequent in case of small `WAL_ttl_seconds` values when size-based expiration (`WAL_size_limit_MB > 0 `) was simultaneously enabled.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a crash or assertion failure bug in experimental new HyperClockCache variant, especially when running with a SecondaryCache.
|
||||
* Fix a race between flush error recovery and db destruction that can lead to db crashing.
|
||||
* Fixed some bugs in the index builder/reader path for user-defined timestamps in Memtable only feature.
|
||||
|
||||
## 8.8.0 (10/23/2023)
|
||||
### New Features
|
||||
* Introduce AttributeGroup by adding the first AttributeGroup support API, MultiGetEntity(). Through the use of Column Families, AttributeGroup enables users to logically group wide-column entities. More APIs to support AttributeGroup will come soon, including GetEntity, PutEntity, and others.
|
||||
|
||||
@@ -539,7 +539,7 @@ endif
|
||||
|
||||
ifdef USE_CLANG
|
||||
# Used by some teams in Facebook
|
||||
WARNING_FLAGS += -Wshift-sign-overflow -Wambiguous-reversed-operator
|
||||
WARNING_FLAGS += -Wshift-sign-overflow -Wambiguous-reversed-operator -Wimplicit-fallthrough
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
@@ -2060,7 +2060,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 amd64 ppc64 ppc64le s390x arm64 aarch64 sparc64 loongarch64, $(MACHINE)))
|
||||
ifneq (,$(filter amd64 ppc64 ppc64le s390x arm64 aarch64 riscv64 sparc64 loongarch64, $(MACHINE)))
|
||||
ARCH := 64
|
||||
else
|
||||
ARCH := 32
|
||||
@@ -2081,7 +2081,7 @@ ifneq ($(origin JNI_LIBC), undefined)
|
||||
endif
|
||||
|
||||
ifeq (,$(ROCKSDBJNILIB))
|
||||
ifneq (,$(filter ppc% s390x arm64 aarch64 sparc64 loongarch64, $(MACHINE)))
|
||||
ifneq (,$(filter ppc% s390x arm64 aarch64 riscv64 sparc64 loongarch64, $(MACHINE)))
|
||||
ROCKSDBJNILIB = librocksdbjni-linux-$(MACHINE)$(JNI_LIBC_POSTFIX).so
|
||||
else
|
||||
ROCKSDBJNILIB = librocksdbjni-linux$(ARCH)$(JNI_LIBC_POSTFIX).so
|
||||
@@ -2094,8 +2094,8 @@ ROCKSDB_JAVADOCS_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-javadoc.jar
|
||||
ROCKSDB_SOURCES_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-sources.jar
|
||||
SHA256_CMD = sha256sum
|
||||
|
||||
ZLIB_VER ?= 1.3
|
||||
ZLIB_SHA256 ?= ff0ba4c292013dbc27530b3a81e1f9a813cd39de01ca5e0f8bf355702efa593e
|
||||
ZLIB_VER ?= 1.3.1
|
||||
ZLIB_SHA256 ?= 9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23
|
||||
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
@@ -2346,6 +2346,10 @@ rocksdbjavastaticdockers390x:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_s390x-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:ubuntu18_s390x-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerriscv64:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_riscv64-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:ubuntu20_riscv64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerx86musl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x86-musl-be --platform linux/386 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-alpine.sh
|
||||
|
||||
@@ -647,8 +647,10 @@ if [ "$PORTABLE" == "" ] || [ "$PORTABLE" == 0 ]; then
|
||||
fi
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^riscv64`"; then
|
||||
RISC_ISA=$(cat /proc/cpuinfo | grep isa | head -1 | cut --delimiter=: -f 2 | cut -b 2-)
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}"
|
||||
RISC_ISA=$(cat /proc/cpuinfo | grep -E '^isa\s*:' | head -1 | cut --delimiter=: -f 2 | cut -b 2-)
|
||||
if [ -n "${RISCV_ISA}" ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}"
|
||||
fi
|
||||
elif [ "$TARGET_OS" == "IOS" ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
else
|
||||
@@ -660,8 +662,7 @@ else
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z196 "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^riscv64`"; then
|
||||
RISC_ISA=$(cat /proc/cpuinfo | grep isa | head -1 | cut --delimiter=: -f 2 | cut -b 2-)
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}"
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=rv64gc"
|
||||
elif test "$USE_SSE"; then
|
||||
# USE_SSE is DEPRECATED
|
||||
# This is a rough approximation of the old USE_SSE behavior
|
||||
|
||||
Vendored
+5
@@ -48,6 +48,10 @@ DEFINE_uint64(cache_size, 1 * GiB,
|
||||
"Number of bytes to use as a cache of uncompressed data.");
|
||||
DEFINE_int32(num_shard_bits, -1,
|
||||
"ShardedCacheOptions::shard_bits. Default = auto");
|
||||
DEFINE_int32(
|
||||
eviction_effort_cap,
|
||||
ROCKSDB_NAMESPACE::HyperClockCacheOptions(1, 1).eviction_effort_cap,
|
||||
"HyperClockCacheOptions::eviction_effort_cap");
|
||||
|
||||
DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
@@ -391,6 +395,7 @@ class CacheBench {
|
||||
FLAGS_cache_size, /*estimated_entry_charge=*/0, FLAGS_num_shard_bits);
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
opts.memory_allocator = allocator;
|
||||
opts.eviction_effort_cap = FLAGS_eviction_effort_cap;
|
||||
if (FLAGS_cache_type == "fixed_hyper_clock_cache" ||
|
||||
FLAGS_cache_type == "hyper_clock_cache") {
|
||||
opts.estimated_entry_charge = FLAGS_value_bytes_estimate > 0
|
||||
|
||||
Vendored
+98
-44
@@ -15,6 +15,7 @@
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
@@ -51,14 +52,15 @@ inline uint64_t GetInitialCountdown(Cache::Priority priority) {
|
||||
switch (priority) {
|
||||
case Cache::Priority::HIGH:
|
||||
return ClockHandle::kHighCountdown;
|
||||
default:
|
||||
assert(false);
|
||||
FALLTHROUGH_INTENDED;
|
||||
case Cache::Priority::LOW:
|
||||
return ClockHandle::kLowCountdown;
|
||||
case Cache::Priority::BOTTOM:
|
||||
return ClockHandle::kBottomCountdown;
|
||||
}
|
||||
// Switch should have been exhaustive.
|
||||
assert(false);
|
||||
// For release build, fall back on something reasonable.
|
||||
return ClockHandle::kLowCountdown;
|
||||
}
|
||||
|
||||
inline void MarkEmpty(ClockHandle& h) {
|
||||
@@ -93,7 +95,8 @@ inline void Unref(const ClockHandle& h, uint64_t count = 1) {
|
||||
(void)old_meta;
|
||||
}
|
||||
|
||||
inline bool ClockUpdate(ClockHandle& h, bool* purgeable = nullptr) {
|
||||
inline bool ClockUpdate(ClockHandle& h, BaseClockTable::EvictionData* data,
|
||||
bool* purgeable = nullptr) {
|
||||
uint64_t meta;
|
||||
if (purgeable) {
|
||||
assert(*purgeable == false);
|
||||
@@ -125,6 +128,7 @@ inline bool ClockUpdate(ClockHandle& h, bool* purgeable = nullptr) {
|
||||
(meta >> ClockHandle::kReleaseCounterShift) & ClockHandle::kCounterMask;
|
||||
if (acquire_count != release_count) {
|
||||
// Only clock update entries with no outstanding refs
|
||||
data->seen_pinned_count++;
|
||||
return false;
|
||||
}
|
||||
if ((meta >> ClockHandle::kStateShift == ClockHandle::kStateVisible) &&
|
||||
@@ -148,6 +152,8 @@ inline bool ClockUpdate(ClockHandle& h, bool* purgeable = nullptr) {
|
||||
<< ClockHandle::kStateShift) |
|
||||
(meta & ClockHandle::kHitBitMask))) {
|
||||
// Took ownership.
|
||||
data->freed_charge += h.GetTotalCharge();
|
||||
data->freed_count += 1;
|
||||
return true;
|
||||
} else {
|
||||
// Compare-exchange failing probably
|
||||
@@ -355,6 +361,18 @@ void ConstApplyToEntriesRange(const Func& func, const HandleImpl* begin,
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint32_t kStrictCapacityLimitBit = 1u << 31;
|
||||
|
||||
uint32_t SanitizeEncodeEecAndScl(int eviction_effort_cap,
|
||||
bool strict_capacit_limit) {
|
||||
eviction_effort_cap = std::max(int{1}, eviction_effort_cap);
|
||||
eviction_effort_cap =
|
||||
std::min(static_cast<int>(~kStrictCapacityLimitBit), eviction_effort_cap);
|
||||
uint32_t eec_and_scl = static_cast<uint32_t>(eviction_effort_cap);
|
||||
eec_and_scl |= strict_capacit_limit ? kStrictCapacityLimitBit : 0;
|
||||
return eec_and_scl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ClockHandleBasicData::FreeData(MemoryAllocator* allocator) const {
|
||||
@@ -384,17 +402,20 @@ HandleImpl* BaseClockTable::StandaloneInsert(
|
||||
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
ClockHandleBasicData& proto, size_t capacity, bool strict_capacity_limit,
|
||||
ClockHandleBasicData& proto, size_t capacity, uint32_t eec_and_scl,
|
||||
bool allow_uncharged) {
|
||||
Table& derived = static_cast<Table&>(*this);
|
||||
typename Table::InsertState state;
|
||||
derived.StartInsert(state);
|
||||
|
||||
const size_t total_charge = proto.GetTotalCharge();
|
||||
if (strict_capacity_limit) {
|
||||
// NOTE: we can use eec_and_scl as eviction_effort_cap below because
|
||||
// strict_capacity_limit=true is supposed to disable the limit on eviction
|
||||
// effort, and a large value effectively does that.
|
||||
if (eec_and_scl & kStrictCapacityLimitBit) {
|
||||
Status s = ChargeUsageMaybeEvictStrict<Table>(
|
||||
total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false, state);
|
||||
/*need_evict_for_occupancy=*/false, eec_and_scl, state);
|
||||
if (!s.ok()) {
|
||||
if (allow_uncharged) {
|
||||
proto.total_charge = 0;
|
||||
@@ -406,7 +427,7 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
// Case strict_capacity_limit == false
|
||||
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
|
||||
total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false, state);
|
||||
/*need_evict_for_occupancy=*/false, eec_and_scl, state);
|
||||
if (!success) {
|
||||
// Force the issue
|
||||
usage_.FetchAddRelaxed(total_charge);
|
||||
@@ -419,7 +440,7 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
template <class Table>
|
||||
Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state) {
|
||||
uint32_t eviction_effort_cap, typename Table::InsertState& state) {
|
||||
if (total_charge > capacity) {
|
||||
return Status::MemoryLimit(
|
||||
"Cache entry too large for a single cache shard: " +
|
||||
@@ -444,7 +465,8 @@ Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
}
|
||||
if (request_evict_charge > 0) {
|
||||
EvictionData data;
|
||||
static_cast<Table*>(this)->Evict(request_evict_charge, state, &data);
|
||||
static_cast<Table*>(this)->Evict(request_evict_charge, state, &data,
|
||||
eviction_effort_cap);
|
||||
occupancy_.FetchSub(data.freed_count);
|
||||
if (LIKELY(data.freed_charge > need_evict_charge)) {
|
||||
assert(data.freed_count > 0);
|
||||
@@ -474,7 +496,7 @@ Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
template <class Table>
|
||||
inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state) {
|
||||
uint32_t eviction_effort_cap, typename Table::InsertState& state) {
|
||||
// For simplicity, we consider that either the cache can accept the insert
|
||||
// with no evictions, or we must evict enough to make (at least) enough
|
||||
// space. It could lead to unnecessary failures or excessive evictions in
|
||||
@@ -510,7 +532,8 @@ inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
}
|
||||
EvictionData data;
|
||||
if (need_evict_charge > 0) {
|
||||
static_cast<Table*>(this)->Evict(need_evict_charge, state, &data);
|
||||
static_cast<Table*>(this)->Evict(need_evict_charge, state, &data,
|
||||
eviction_effort_cap);
|
||||
// Deal with potential occupancy deficit
|
||||
if (UNLIKELY(need_evict_for_occupancy) && data.freed_count == 0) {
|
||||
assert(data.freed_charge == 0);
|
||||
@@ -529,11 +552,7 @@ inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
return true;
|
||||
}
|
||||
|
||||
void BaseClockTable::TrackAndReleaseEvictedEntry(
|
||||
ClockHandle* h, BaseClockTable::EvictionData* data) {
|
||||
data->freed_charge += h->GetTotalCharge();
|
||||
data->freed_count += 1;
|
||||
|
||||
void BaseClockTable::TrackAndReleaseEvictedEntry(ClockHandle* h) {
|
||||
bool took_value_ownership = false;
|
||||
if (eviction_callback_) {
|
||||
// For key reconstructed from hash
|
||||
@@ -550,11 +569,20 @@ void BaseClockTable::TrackAndReleaseEvictedEntry(
|
||||
MarkEmpty(*h);
|
||||
}
|
||||
|
||||
bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data,
|
||||
uint32_t eviction_effort_cap) {
|
||||
// Basically checks whether the ratio of useful effort to wasted effort is
|
||||
// too low, with a start-up allowance for wasted effort before any useful
|
||||
// effort.
|
||||
return (data.freed_count + 1U) * uint64_t{eviction_effort_cap} <=
|
||||
data.seen_pinned_count;
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle,
|
||||
Cache::Priority priority, size_t capacity,
|
||||
bool strict_capacity_limit) {
|
||||
uint32_t eec_and_scl) {
|
||||
using HandleImpl = typename Table::HandleImpl;
|
||||
Table& derived = static_cast<Table&>(*this);
|
||||
|
||||
@@ -572,9 +600,12 @@ Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
// strict_capacity_limit, but mostly pessimistic.
|
||||
bool use_standalone_insert = false;
|
||||
const size_t total_charge = proto.GetTotalCharge();
|
||||
if (strict_capacity_limit) {
|
||||
// NOTE: we can use eec_and_scl as eviction_effort_cap below because
|
||||
// strict_capacity_limit=true is supposed to disable the limit on eviction
|
||||
// effort, and a large value effectively does that.
|
||||
if (eec_and_scl & kStrictCapacityLimitBit) {
|
||||
Status s = ChargeUsageMaybeEvictStrict<Table>(
|
||||
total_charge, capacity, need_evict_for_occupancy, state);
|
||||
total_charge, capacity, need_evict_for_occupancy, eec_and_scl, state);
|
||||
if (!s.ok()) {
|
||||
// Revert occupancy
|
||||
occupancy_.FetchSubRelaxed(1);
|
||||
@@ -583,7 +614,7 @@ Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
} else {
|
||||
// Case strict_capacity_limit == false
|
||||
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
|
||||
total_charge, capacity, need_evict_for_occupancy, state);
|
||||
total_charge, capacity, need_evict_for_occupancy, eec_and_scl, state);
|
||||
if (!success) {
|
||||
// Revert occupancy
|
||||
occupancy_.FetchSubRelaxed(1);
|
||||
@@ -687,8 +718,7 @@ void BaseClockTable::TEST_ReleaseNMinus1(ClockHandle* h, size_t n) {
|
||||
#endif
|
||||
|
||||
FixedHyperClockTable::FixedHyperClockTable(
|
||||
size_t capacity, bool /*strict_capacity_limit*/,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
size_t capacity, CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const Opts& opts)
|
||||
@@ -1083,7 +1113,8 @@ inline void FixedHyperClockTable::ReclaimEntryUsage(size_t total_charge) {
|
||||
}
|
||||
|
||||
inline void FixedHyperClockTable::Evict(size_t requested_charge, InsertState&,
|
||||
EvictionData* data) {
|
||||
EvictionData* data,
|
||||
uint32_t eviction_effort_cap) {
|
||||
// precondition
|
||||
assert(requested_charge > 0);
|
||||
|
||||
@@ -1104,10 +1135,10 @@ inline void FixedHyperClockTable::Evict(size_t requested_charge, InsertState&,
|
||||
for (;;) {
|
||||
for (size_t i = 0; i < step_size; i++) {
|
||||
HandleImpl& h = array_[ModTableSize(Lower32of64(old_clock_pointer + i))];
|
||||
bool evicting = ClockUpdate(h);
|
||||
bool evicting = ClockUpdate(h, data);
|
||||
if (evicting) {
|
||||
Rollback(h.hashed_key, &h);
|
||||
TrackAndReleaseEvictedEntry(&h, data);
|
||||
TrackAndReleaseEvictedEntry(&h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1118,6 +1149,10 @@ inline void FixedHyperClockTable::Evict(size_t requested_charge, InsertState&,
|
||||
if (old_clock_pointer >= max_clock_pointer) {
|
||||
return;
|
||||
}
|
||||
if (IsEvictionEffortExceeded(*data, eviction_effort_cap)) {
|
||||
eviction_effort_exceeded_count_.FetchAddRelaxed(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance clock pointer (concurrently)
|
||||
old_clock_pointer = clock_pointer_.FetchAddRelaxed(step_size);
|
||||
@@ -1132,10 +1167,11 @@ ClockCacheShard<Table>::ClockCacheShard(
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const typename Table::Opts& opts)
|
||||
: CacheShardBase(metadata_charge_policy),
|
||||
table_(capacity, strict_capacity_limit, metadata_charge_policy, allocator,
|
||||
eviction_callback, hash_seed, opts),
|
||||
table_(capacity, metadata_charge_policy, allocator, eviction_callback,
|
||||
hash_seed, opts),
|
||||
capacity_(capacity),
|
||||
strict_capacity_limit_(strict_capacity_limit) {
|
||||
eec_and_scl_(SanitizeEncodeEecAndScl(opts.eviction_effort_cap,
|
||||
strict_capacity_limit)) {
|
||||
// Initial charge metadata should not exceed capacity
|
||||
assert(table_.GetUsage() <= capacity_.LoadRelaxed() ||
|
||||
capacity_.LoadRelaxed() < sizeof(HandleImpl));
|
||||
@@ -1211,7 +1247,11 @@ void ClockCacheShard<Table>::SetCapacity(size_t capacity) {
|
||||
template <class Table>
|
||||
void ClockCacheShard<Table>::SetStrictCapacityLimit(
|
||||
bool strict_capacity_limit) {
|
||||
strict_capacity_limit_.StoreRelaxed(strict_capacity_limit);
|
||||
if (strict_capacity_limit) {
|
||||
eec_and_scl_.FetchOrRelaxed(kStrictCapacityLimitBit);
|
||||
} else {
|
||||
eec_and_scl_.FetchAndRelaxed(~kStrictCapacityLimitBit);
|
||||
}
|
||||
// next Insert will take care of any necessary evictions
|
||||
}
|
||||
|
||||
@@ -1233,7 +1273,7 @@ Status ClockCacheShard<Table>::Insert(const Slice& key,
|
||||
proto.total_charge = charge;
|
||||
return table_.template Insert<Table>(proto, handle, priority,
|
||||
capacity_.LoadRelaxed(),
|
||||
strict_capacity_limit_.LoadRelaxed());
|
||||
eec_and_scl_.LoadRelaxed());
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1248,9 +1288,9 @@ typename Table::HandleImpl* ClockCacheShard<Table>::CreateStandalone(
|
||||
proto.value = obj;
|
||||
proto.helper = helper;
|
||||
proto.total_charge = charge;
|
||||
return table_.template CreateStandalone<Table>(
|
||||
proto, capacity_.LoadRelaxed(), strict_capacity_limit_.LoadRelaxed(),
|
||||
allow_uncharged);
|
||||
return table_.template CreateStandalone<Table>(proto, capacity_.LoadRelaxed(),
|
||||
eec_and_scl_.LoadRelaxed(),
|
||||
allow_uncharged);
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1502,14 +1542,20 @@ void BaseHyperClockCache<Table>::ReportProblems(
|
||||
const std::shared_ptr<Logger>& info_log) const {
|
||||
if (info_log->GetInfoLogLevel() <= InfoLogLevel::DEBUG_LEVEL) {
|
||||
LoadVarianceStats slot_stats;
|
||||
uint64_t eviction_effort_exceeded_count = 0;
|
||||
this->ForEachShard([&](const BaseHyperClockCache<Table>::Shard* shard) {
|
||||
size_t count = shard->GetTableAddressCount();
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
slot_stats.Add(IsSlotOccupied(*shard->GetTable().HandlePtr(i)));
|
||||
}
|
||||
eviction_effort_exceeded_count +=
|
||||
shard->GetTable().GetEvictionEffortExceededCount();
|
||||
});
|
||||
ROCKS_LOG_AT_LEVEL(info_log, InfoLogLevel::DEBUG_LEVEL,
|
||||
"Slot occupancy stats: %s", slot_stats.Report().c_str());
|
||||
ROCKS_LOG_AT_LEVEL(info_log, InfoLogLevel::DEBUG_LEVEL,
|
||||
"Eviction effort exceeded: %" PRIu64,
|
||||
eviction_effort_exceeded_count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1907,8 +1953,7 @@ class AutoHyperClockTable::ChainRewriteLock {
|
||||
};
|
||||
|
||||
AutoHyperClockTable::AutoHyperClockTable(
|
||||
size_t capacity, bool /*strict_capacity_limit*/,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
size_t capacity, CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const Opts& opts)
|
||||
@@ -2589,7 +2634,8 @@ using ClockUpdateChainLockedOpData =
|
||||
template <class OpData>
|
||||
void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
ChainRewriteLock& rewrite_lock,
|
||||
size_t home) {
|
||||
size_t home,
|
||||
BaseClockTable::EvictionData* data) {
|
||||
constexpr bool kIsPurge = std::is_same_v<OpData, PurgeLockedOpData>;
|
||||
constexpr bool kIsClockUpdateChain =
|
||||
std::is_same_v<OpData, ClockUpdateChainLockedOpData>;
|
||||
@@ -2631,7 +2677,7 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
assert(home == BottomNBits(h->hashed_key[1], home_shift));
|
||||
if constexpr (kIsClockUpdateChain) {
|
||||
// Clock update and/or check for purgeable (under (de)construction)
|
||||
if (ClockUpdate(*h, &purgeable)) {
|
||||
if (ClockUpdate(*h, data, &purgeable)) {
|
||||
// Remember for finishing eviction
|
||||
op_data->push_back(h);
|
||||
// Entries for eviction become purgeable
|
||||
@@ -2641,6 +2687,7 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
}
|
||||
} else {
|
||||
(void)op_data;
|
||||
(void)data;
|
||||
purgeable = ((h->meta.Load() >> ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateShareableBit) == 0;
|
||||
}
|
||||
@@ -2718,7 +2765,8 @@ using PurgeOpData = const UniqueId64x2;
|
||||
using ClockUpdateChainOpData = ClockUpdateChainLockedOpData;
|
||||
|
||||
template <class OpData>
|
||||
void AutoHyperClockTable::PurgeImpl(OpData* op_data, size_t home) {
|
||||
void AutoHyperClockTable::PurgeImpl(OpData* op_data, size_t home,
|
||||
BaseClockTable::EvictionData* data) {
|
||||
// Early efforts to make AutoHCC fully wait-free ran into too many problems
|
||||
// that needed obscure and potentially inefficient work-arounds to have a
|
||||
// chance at working.
|
||||
@@ -2799,9 +2847,9 @@ void AutoHyperClockTable::PurgeImpl(OpData* op_data, size_t home) {
|
||||
if (!rewrite_lock.IsEnd()) {
|
||||
if constexpr (kIsPurge) {
|
||||
PurgeLockedOpData* locked_op_data{};
|
||||
PurgeImplLocked(locked_op_data, rewrite_lock, home);
|
||||
PurgeImplLocked(locked_op_data, rewrite_lock, home, data);
|
||||
} else {
|
||||
PurgeImplLocked(op_data, rewrite_lock, home);
|
||||
PurgeImplLocked(op_data, rewrite_lock, home, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3404,7 +3452,8 @@ void AutoHyperClockTable::EraseUnRefEntries() {
|
||||
}
|
||||
|
||||
void AutoHyperClockTable::Evict(size_t requested_charge, InsertState& state,
|
||||
EvictionData* data) {
|
||||
EvictionData* data,
|
||||
uint32_t eviction_effort_cap) {
|
||||
// precondition
|
||||
assert(requested_charge > 0);
|
||||
|
||||
@@ -3462,12 +3511,12 @@ void AutoHyperClockTable::Evict(size_t requested_charge, InsertState& state,
|
||||
if (home >= used_length) {
|
||||
break;
|
||||
}
|
||||
PurgeImpl(&to_finish_eviction, home);
|
||||
PurgeImpl(&to_finish_eviction, home, data);
|
||||
}
|
||||
}
|
||||
|
||||
for (HandleImpl* h : to_finish_eviction) {
|
||||
TrackAndReleaseEvictedEntry(h, data);
|
||||
TrackAndReleaseEvictedEntry(h);
|
||||
// NOTE: setting likely_empty_slot here can cause us to reduce the
|
||||
// portion of "at home" entries, probably because an evicted entry
|
||||
// is more likely to come back than a random new entry and would be
|
||||
@@ -3495,6 +3544,11 @@ void AutoHyperClockTable::Evict(size_t requested_charge, InsertState& state,
|
||||
if (old_clock_pointer + step_size >= max_clock_pointer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsEvictionEffortExceeded(*data, eviction_effort_cap)) {
|
||||
eviction_effort_exceeded_count_.FetchAddRelaxed(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+49
-19
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -374,6 +375,14 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
|
||||
class BaseClockTable {
|
||||
public:
|
||||
struct BaseOpts {
|
||||
explicit BaseOpts(int _eviction_effort_cap)
|
||||
: eviction_effort_cap(_eviction_effort_cap) {}
|
||||
explicit BaseOpts(const HyperClockCacheOptions& opts)
|
||||
: BaseOpts(opts.eviction_effort_cap) {}
|
||||
int eviction_effort_cap;
|
||||
};
|
||||
|
||||
BaseClockTable(CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
@@ -386,13 +395,13 @@ class BaseClockTable {
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* CreateStandalone(ClockHandleBasicData& proto,
|
||||
size_t capacity,
|
||||
bool strict_capacity_limit,
|
||||
uint32_t eec_and_scl,
|
||||
bool allow_uncharged);
|
||||
|
||||
template <class Table>
|
||||
Status Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle, Cache::Priority priority,
|
||||
size_t capacity, bool strict_capacity_limit);
|
||||
size_t capacity, uint32_t eec_and_scl);
|
||||
|
||||
void Ref(ClockHandle& handle);
|
||||
|
||||
@@ -406,12 +415,17 @@ class BaseClockTable {
|
||||
|
||||
uint64_t GetYieldCount() const { return yield_count_.LoadRelaxed(); }
|
||||
|
||||
uint64_t GetEvictionEffortExceededCount() const {
|
||||
return eviction_effort_exceeded_count_.LoadRelaxed();
|
||||
}
|
||||
|
||||
struct EvictionData {
|
||||
size_t freed_charge = 0;
|
||||
size_t freed_count = 0;
|
||||
size_t seen_pinned_count = 0;
|
||||
};
|
||||
|
||||
void TrackAndReleaseEvictedEntry(ClockHandle* h, EvictionData* data);
|
||||
void TrackAndReleaseEvictedEntry(ClockHandle* h);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Acquire N references
|
||||
@@ -436,6 +450,7 @@ class BaseClockTable {
|
||||
template <class Table>
|
||||
Status ChargeUsageMaybeEvictStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
uint32_t eviction_effort_cap,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
@@ -449,6 +464,7 @@ class BaseClockTable {
|
||||
template <class Table>
|
||||
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
uint32_t eviction_effort_cap,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
protected: // data
|
||||
@@ -461,9 +477,15 @@ class BaseClockTable {
|
||||
RelaxedAtomic<uint64_t> clock_pointer_{};
|
||||
|
||||
// Counter for number of times we yield to wait on another thread.
|
||||
// It is normal for this to occur rarely in normal operation.
|
||||
// (Relaxed: a simple stat counter.)
|
||||
RelaxedAtomic<uint64_t> yield_count_{};
|
||||
|
||||
// Counter for number of times eviction effort cap is exceeded.
|
||||
// It is normal for this to occur rarely in normal operation.
|
||||
// (Relaxed: a simple stat counter.)
|
||||
RelaxedAtomic<uint64_t> eviction_effort_exceeded_count_{};
|
||||
|
||||
// TODO: is this separation needed if we don't do background evictions?
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
@@ -517,17 +539,19 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
inline void SetStandalone() { standalone = true; }
|
||||
}; // struct HandleImpl
|
||||
|
||||
struct Opts {
|
||||
explicit Opts(size_t _estimated_value_size)
|
||||
: estimated_value_size(_estimated_value_size) {}
|
||||
explicit Opts(const HyperClockCacheOptions& opts) {
|
||||
struct Opts : public BaseOpts {
|
||||
explicit Opts(size_t _estimated_value_size, int _eviction_effort_cap)
|
||||
: BaseOpts(_eviction_effort_cap),
|
||||
estimated_value_size(_estimated_value_size) {}
|
||||
explicit Opts(const HyperClockCacheOptions& opts)
|
||||
: BaseOpts(opts.eviction_effort_cap) {
|
||||
assert(opts.estimated_entry_charge > 0);
|
||||
estimated_value_size = opts.estimated_entry_charge;
|
||||
}
|
||||
size_t estimated_value_size;
|
||||
};
|
||||
|
||||
FixedHyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
FixedHyperClockTable(size_t capacity,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
@@ -549,7 +573,8 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data,
|
||||
uint32_t eviction_effort_cap);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
@@ -803,18 +828,20 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
}
|
||||
}; // struct HandleImpl
|
||||
|
||||
struct Opts {
|
||||
explicit Opts(size_t _min_avg_value_size)
|
||||
: min_avg_value_size(_min_avg_value_size) {}
|
||||
struct Opts : public BaseOpts {
|
||||
explicit Opts(size_t _min_avg_value_size, int _eviction_effort_cap)
|
||||
: BaseOpts(_eviction_effort_cap),
|
||||
min_avg_value_size(_min_avg_value_size) {}
|
||||
|
||||
explicit Opts(const HyperClockCacheOptions& opts) {
|
||||
explicit Opts(const HyperClockCacheOptions& opts)
|
||||
: BaseOpts(opts.eviction_effort_cap) {
|
||||
assert(opts.estimated_entry_charge == 0);
|
||||
min_avg_value_size = opts.min_avg_entry_charge;
|
||||
}
|
||||
size_t min_avg_value_size;
|
||||
};
|
||||
|
||||
AutoHyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
AutoHyperClockTable(size_t capacity,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
@@ -841,7 +868,8 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data,
|
||||
uint32_t eviction_effort_cap);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
@@ -906,7 +934,8 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
// with proper handling to ensure all existing data is seen even in the
|
||||
// presence of concurrent insertions, etc. (See implementation.)
|
||||
template <class OpData>
|
||||
void PurgeImpl(OpData* op_data, size_t home = SIZE_MAX);
|
||||
void PurgeImpl(OpData* op_data, size_t home = SIZE_MAX,
|
||||
EvictionData* data = nullptr);
|
||||
|
||||
// An RAII wrapper for locking a chain of entries for removals. See
|
||||
// implementation.
|
||||
@@ -916,7 +945,7 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
// implementation.
|
||||
template <class OpData>
|
||||
void PurgeImplLocked(OpData* op_data, ChainRewriteLock& rewrite_lock,
|
||||
size_t home);
|
||||
size_t home, EvictionData* data);
|
||||
|
||||
// Update length_info_ as much as possible without waiting, given a known
|
||||
// usable (ready for inserts and lookups) grow_home. (Previous grow_homes
|
||||
@@ -1078,9 +1107,10 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
// (Relaxed: eventual consistency/update is OK)
|
||||
RelaxedAtomic<size_t> capacity_;
|
||||
|
||||
// Whether to reject insertion if cache reaches its full capacity.
|
||||
// Encodes eviction_effort_cap (bottom 31 bits) and strict_capacity_limit
|
||||
// (top bit). See HyperClockCacheOptions::eviction_effort_cap etc.
|
||||
// (Relaxed: eventual consistency/update is OK)
|
||||
RelaxedAtomic<bool> strict_capacity_limit_;
|
||||
RelaxedAtomic<uint32_t> eec_and_scl_;
|
||||
}; // class ClockCacheShard
|
||||
|
||||
template <class Table>
|
||||
|
||||
Vendored
+3
-1
@@ -31,7 +31,7 @@ CompressedSecondaryCache::~CompressedSecondaryCache() {}
|
||||
std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
|
||||
bool& kept_in_sec_cache) {
|
||||
Statistics* stats, bool& kept_in_sec_cache) {
|
||||
assert(helper);
|
||||
// This is a minor optimization. Its ok to skip it in TSAN in order to
|
||||
// avoid a false positive.
|
||||
@@ -51,6 +51,7 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
void* handle_value = cache_->Value(lru_handle);
|
||||
if (handle_value == nullptr) {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
RecordTick(stats, COMPRESSED_SECONDARY_CACHE_DUMMY_HITS);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -137,6 +138,7 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
handle.reset(new CompressedSecondaryCacheResultHandle(value, charge));
|
||||
RecordTick(stats, COMPRESSED_SECONDARY_CACHE_HITS);
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -86,7 +86,7 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
|
||||
bool& kept_in_sec_cache) override;
|
||||
Statistics* stats, bool& kept_in_sec_cache) override;
|
||||
|
||||
bool SupportForceErase() const override { return true; }
|
||||
|
||||
|
||||
+15
-13
@@ -44,7 +44,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
// Lookup an non-existent key.
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle0 =
|
||||
sec_cache->Lookup(key0, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle0, nullptr);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -59,7 +59,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1_1, nullptr);
|
||||
|
||||
// Insert and Lookup the item k1 for the second time and advise erasing it.
|
||||
@@ -68,7 +68,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_NE(handle1_2, nullptr);
|
||||
ASSERT_FALSE(kept_in_sec_cache);
|
||||
if (sec_cache_is_compressed) {
|
||||
@@ -89,7 +89,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
// Lookup the item k1 again.
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_3 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1_3, nullptr);
|
||||
|
||||
// Insert and Lookup the item k2.
|
||||
@@ -99,7 +99,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle2_1, nullptr);
|
||||
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
@@ -115,7 +115,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
}
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_2 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_NE(handle2_2, nullptr);
|
||||
std::unique_ptr<TestItem> val2 =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2_2->Value()));
|
||||
@@ -196,14 +196,14 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
bool kept_in_sec_cache{false};
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
|
||||
// Insert k2 and k1 is evicted.
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
std::unique_ptr<TestItem> val2 =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2->Value()));
|
||||
@@ -215,14 +215,14 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1_1, nullptr);
|
||||
|
||||
// Create Fails.
|
||||
SetFailCreate(true);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
kept_in_sec_cache);
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle2_1, nullptr);
|
||||
|
||||
// Save Fails.
|
||||
@@ -912,9 +912,9 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1U);
|
||||
|
||||
bool kept_in_sec_cache{true};
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle =
|
||||
sec_cache->Lookup(ith_key, GetHelper(role), this, true,
|
||||
/*advise_erase=*/true, kept_in_sec_cache);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle = sec_cache->Lookup(
|
||||
ith_key, GetHelper(role), this, true,
|
||||
/*advise_erase=*/true, /*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
|
||||
// Lookup returns the right data
|
||||
@@ -992,6 +992,8 @@ class CompressedSecCacheTestWithTiered
|
||||
/*_capacity=*/0,
|
||||
/*_estimated_entry_charge=*/256 << 10,
|
||||
/*_num_shard_bits=*/0);
|
||||
// eviction_effort_cap setting simply to avoid churn in existing test
|
||||
hcc_opts.eviction_effort_cap = 100;
|
||||
TieredCacheOptions opts;
|
||||
lru_opts.capacity = 0;
|
||||
lru_opts.num_shard_bits = 0;
|
||||
|
||||
Vendored
+60
-3
@@ -389,12 +389,13 @@ class ClockCacheTest : public testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
void NewShard(size_t capacity, bool strict_capacity_limit = true) {
|
||||
void NewShard(size_t capacity, bool strict_capacity_limit = true,
|
||||
int eviction_effort_cap = 30) {
|
||||
DeleteShard();
|
||||
shard_ =
|
||||
reinterpret_cast<Shard*>(port::cacheline_aligned_alloc(sizeof(Shard)));
|
||||
|
||||
TableOpts opts{1 /*value_size*/};
|
||||
TableOpts opts{1 /*value_size*/, eviction_effort_cap};
|
||||
new (shard_)
|
||||
Shard(capacity, strict_capacity_limit, kDontChargeCacheMetadata,
|
||||
/*allocator*/ nullptr, &eviction_callback_, &hash_seed_, opts);
|
||||
@@ -445,12 +446,20 @@ class ClockCacheTest : public testing::Test {
|
||||
return Slice(reinterpret_cast<const char*>(&hashed_key), 16U);
|
||||
}
|
||||
|
||||
// A bad hash function for testing / stressing collision handling
|
||||
static inline UniqueId64x2 TestHashedKey(char key) {
|
||||
// For testing hash near-collision behavior, put the variance in
|
||||
// hashed_key in bits that are unlikely to be used as hash bits.
|
||||
return {(static_cast<uint64_t>(key) << 56) + 1234U, 5678U};
|
||||
}
|
||||
|
||||
// A reasonable hash function, for testing "typical behavior" etc.
|
||||
template <typename T>
|
||||
static inline UniqueId64x2 CheapHash(T i) {
|
||||
return {static_cast<uint64_t>(i) * uint64_t{0x85EBCA77C2B2AE63},
|
||||
static_cast<uint64_t>(i) * uint64_t{0xC2B2AE3D27D4EB4F}};
|
||||
}
|
||||
|
||||
Shard* shard_ = nullptr;
|
||||
|
||||
private:
|
||||
@@ -683,6 +692,53 @@ TYPED_TEST(ClockCacheTest, ClockEvictionTest) {
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(ClockCacheTest, ClockEvictionEffortCapTest) {
|
||||
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
|
||||
for (bool strict_capacity_limit : {true, false}) {
|
||||
SCOPED_TRACE("strict_capacity_limit = " +
|
||||
std::to_string(strict_capacity_limit));
|
||||
for (int eec : {-42, 0, 1, 10, 100, 1000}) {
|
||||
SCOPED_TRACE("eviction_effort_cap = " + std::to_string(eec));
|
||||
constexpr size_t kCapacity = 1000;
|
||||
// Start with much larger capacity to ensure that we can go way over
|
||||
// capacity without reaching table occupancy limit.
|
||||
this->NewShard(3 * kCapacity, strict_capacity_limit, eec);
|
||||
auto& shard = *this->shard_;
|
||||
shard.SetCapacity(kCapacity);
|
||||
|
||||
// Nearly fill the cache with pinned entries, then add a bunch of
|
||||
// non-pinned entries. eviction_effort_cap should affect how many
|
||||
// evictable entries are present beyond the cache capacity, despite
|
||||
// being evictable.
|
||||
constexpr size_t kCount = kCapacity - 1;
|
||||
std::unique_ptr<HandleImpl* []> ha { new HandleImpl* [kCount] {} };
|
||||
for (size_t i = 0; i < 2 * kCount; ++i) {
|
||||
UniqueId64x2 hkey = this->CheapHash(i);
|
||||
ASSERT_OK(shard.Insert(
|
||||
this->TestKey(hkey), hkey, nullptr /*value*/, &kNoopCacheItemHelper,
|
||||
1 /*charge*/, i < kCount ? &ha[i] : nullptr, Cache::Priority::LOW));
|
||||
}
|
||||
|
||||
if (strict_capacity_limit) {
|
||||
// If strict_capacity_limit is enabled, the cache will never exceed its
|
||||
// capacity
|
||||
EXPECT_EQ(shard.GetOccupancyCount(), kCapacity);
|
||||
} else {
|
||||
// Rough inverse relationship between cap and possible memory
|
||||
// explosion, which shows up as increased table occupancy count.
|
||||
int effective_eec = std::max(int{1}, eec) + 1;
|
||||
EXPECT_NEAR(shard.GetOccupancyCount() * 1.0,
|
||||
kCount * (1 + 1.4 / effective_eec),
|
||||
kCount * (0.6 / effective_eec) + 1.0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < kCount; ++i) {
|
||||
shard.Release(ha[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct DeleteCounter {
|
||||
int deleted = 0;
|
||||
@@ -1035,7 +1091,8 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool /*wait*/,
|
||||
bool /*advise_erase*/, bool& kept_in_sec_cache) override {
|
||||
bool /*advise_erase*/, Statistics* /*stats*/,
|
||||
bool& kept_in_sec_cache) override {
|
||||
std::string key_str = key.ToString();
|
||||
TEST_SYNC_POINT_CALLBACK("TestSecondaryCache::Lookup", &key_str);
|
||||
|
||||
|
||||
Vendored
+8
-5
@@ -294,7 +294,8 @@ Cache::Handle* CacheWithSecondaryAdapter::Lookup(const Slice& key,
|
||||
bool kept_in_sec_cache = false;
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(key, helper, create_context, /*wait*/ true,
|
||||
found_dummy_entry, /*out*/ kept_in_sec_cache);
|
||||
found_dummy_entry, stats,
|
||||
/*out*/ kept_in_sec_cache);
|
||||
if (secondary_handle) {
|
||||
result = Promote(std::move(secondary_handle), key, helper, priority,
|
||||
stats, found_dummy_entry, kept_in_sec_cache);
|
||||
@@ -348,10 +349,10 @@ void CacheWithSecondaryAdapter::StartAsyncLookupOnMySecondary(
|
||||
assert(async_handle.result_handle == nullptr);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(async_handle.key, async_handle.helper,
|
||||
async_handle.create_context, /*wait*/ false,
|
||||
async_handle.found_dummy_entry,
|
||||
/*out*/ async_handle.kept_in_sec_cache);
|
||||
secondary_cache_->Lookup(
|
||||
async_handle.key, async_handle.helper, async_handle.create_context,
|
||||
/*wait*/ false, async_handle.found_dummy_entry, async_handle.stats,
|
||||
/*out*/ async_handle.kept_in_sec_cache);
|
||||
if (secondary_handle) {
|
||||
// TODO with stacked secondaries: Check & process if already ready?
|
||||
async_handle.pending_handle = secondary_handle.release();
|
||||
@@ -683,12 +684,14 @@ std::shared_ptr<Cache> NewTieredCache(const TieredCacheOptions& _opts) {
|
||||
*(static_cast_with_check<LRUCacheOptions, ShardedCacheOptions>(
|
||||
opts.cache_opts));
|
||||
cache_opts.capacity = opts.total_capacity;
|
||||
cache_opts.secondary_cache = nullptr;
|
||||
cache = cache_opts.MakeSharedCache();
|
||||
} else if (opts.cache_type == PrimaryCacheType::kCacheTypeHCC) {
|
||||
HyperClockCacheOptions cache_opts =
|
||||
*(static_cast_with_check<HyperClockCacheOptions, ShardedCacheOptions>(
|
||||
opts.cache_opts));
|
||||
cache_opts.capacity = opts.total_capacity;
|
||||
cache_opts.secondary_cache = nullptr;
|
||||
cache = cache_opts.MakeSharedCache();
|
||||
} else {
|
||||
return nullptr;
|
||||
|
||||
Vendored
+15
-9
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "cache/tiered_secondary_cache.h"
|
||||
|
||||
#include "monitoring/statistics_impl.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Creation callback for use in the lookup path. It calls the upper layer
|
||||
@@ -29,6 +31,9 @@ Status TieredSecondaryCache::MaybeInsertAndCreate(
|
||||
// TODO: Don't hardcode the source
|
||||
context->comp_sec_cache->InsertSaved(*context->key, data, type, source)
|
||||
.PermitUncheckedError();
|
||||
RecordTick(context->stats, COMPRESSED_SECONDARY_CACHE_PROMOTIONS);
|
||||
} else {
|
||||
RecordTick(context->stats, COMPRESSED_SECONDARY_CACHE_PROMOTION_SKIPS);
|
||||
}
|
||||
// Primary cache will accept the object, so call its helper to create
|
||||
// the object
|
||||
@@ -43,10 +48,10 @@ Status TieredSecondaryCache::MaybeInsertAndCreate(
|
||||
std::unique_ptr<SecondaryCacheResultHandle> TieredSecondaryCache::Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool wait, bool advise_erase,
|
||||
bool& kept_in_sec_cache) {
|
||||
Statistics* stats, bool& kept_in_sec_cache) {
|
||||
bool dummy = false;
|
||||
std::unique_ptr<SecondaryCacheResultHandle> result =
|
||||
target()->Lookup(key, helper, create_context, wait, advise_erase,
|
||||
target()->Lookup(key, helper, create_context, wait, advise_erase, stats,
|
||||
/*kept_in_sec_cache=*/dummy);
|
||||
// We never want the item to spill back into the secondary cache
|
||||
kept_in_sec_cache = true;
|
||||
@@ -66,9 +71,10 @@ std::unique_ptr<SecondaryCacheResultHandle> TieredSecondaryCache::Lookup(
|
||||
ctx.helper = helper;
|
||||
ctx.inner_ctx = create_context;
|
||||
ctx.comp_sec_cache = target();
|
||||
ctx.stats = stats;
|
||||
|
||||
return nvm_sec_cache_->Lookup(key, outer_helper, &ctx, wait, advise_erase,
|
||||
kept_in_sec_cache);
|
||||
stats, kept_in_sec_cache);
|
||||
}
|
||||
|
||||
// If wait is false, i.e its an async lookup, we have to allocate a result
|
||||
@@ -80,8 +86,10 @@ std::unique_ptr<SecondaryCacheResultHandle> TieredSecondaryCache::Lookup(
|
||||
handle->ctx()->helper = helper;
|
||||
handle->ctx()->inner_ctx = create_context;
|
||||
handle->ctx()->comp_sec_cache = target();
|
||||
handle->SetInnerHandle(nvm_sec_cache_->Lookup(
|
||||
key, outer_helper, handle->ctx(), wait, advise_erase, kept_in_sec_cache));
|
||||
handle->ctx()->stats = stats;
|
||||
handle->SetInnerHandle(
|
||||
nvm_sec_cache_->Lookup(key, outer_helper, handle->ctx(), wait,
|
||||
advise_erase, stats, kept_in_sec_cache));
|
||||
if (!handle->inner_handle()) {
|
||||
handle.reset();
|
||||
} else {
|
||||
@@ -109,10 +117,8 @@ void TieredSecondaryCache::WaitAll(
|
||||
}
|
||||
nvm_sec_cache_->WaitAll(nvm_handles);
|
||||
for (auto handle : my_handles) {
|
||||
assert(handle->IsReady());
|
||||
auto nvm_handle = handle->inner_handle();
|
||||
handle->SetSize(nvm_handle->Size());
|
||||
handle->SetValue(nvm_handle->Value());
|
||||
assert(handle->inner_handle()->IsReady());
|
||||
handle->Complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+8
-3
@@ -59,7 +59,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
virtual std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool wait, bool advise_erase,
|
||||
bool& kept_in_sec_cache) override;
|
||||
Statistics* stats, bool& kept_in_sec_cache) override;
|
||||
|
||||
virtual void WaitAll(
|
||||
std::vector<SecondaryCacheResultHandle*> handles) override;
|
||||
@@ -72,6 +72,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
Cache::CreateContext* inner_ctx;
|
||||
std::shared_ptr<SecondaryCacheResultHandle> inner_handle;
|
||||
SecondaryCache* comp_sec_cache;
|
||||
Statistics* stats;
|
||||
};
|
||||
|
||||
class ResultHandle : public SecondaryCacheResultHandle {
|
||||
@@ -79,7 +80,10 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
~ResultHandle() override {}
|
||||
|
||||
bool IsReady() override {
|
||||
return !inner_handle_ || inner_handle_->IsReady();
|
||||
if (inner_handle_ && inner_handle_->IsReady()) {
|
||||
Complete();
|
||||
}
|
||||
return ready_;
|
||||
}
|
||||
|
||||
void Wait() override {
|
||||
@@ -92,10 +96,10 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
Cache::ObjectPtr Value() override { return value_; }
|
||||
|
||||
void Complete() {
|
||||
assert(IsReady());
|
||||
size_ = inner_handle_->Size();
|
||||
value_ = inner_handle_->Value();
|
||||
inner_handle_.reset();
|
||||
ready_ = true;
|
||||
}
|
||||
|
||||
void SetInnerHandle(std::unique_ptr<SecondaryCacheResultHandle>&& handle) {
|
||||
@@ -115,6 +119,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
CreateContext ctx_;
|
||||
size_t size_;
|
||||
Cache::ObjectPtr value_;
|
||||
bool ready_ = false;
|
||||
};
|
||||
|
||||
static void NoopDelete(Cache::ObjectPtr /*obj*/,
|
||||
|
||||
Vendored
+130
-10
@@ -15,10 +15,11 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class TestSecondaryCache : public SecondaryCache {
|
||||
public:
|
||||
explicit TestSecondaryCache(size_t capacity)
|
||||
explicit TestSecondaryCache(size_t capacity, bool ready_before_wait)
|
||||
: cache_(NewLRUCache(capacity, 0, false, 0.5 /* high_pri_pool_ratio */,
|
||||
nullptr, kDefaultToAdaptiveMutex,
|
||||
kDontChargeCacheMetadata)),
|
||||
ready_before_wait_(ready_before_wait),
|
||||
num_insert_saved_(0),
|
||||
num_hits_(0),
|
||||
num_misses_(0) {}
|
||||
@@ -61,7 +62,7 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool wait, bool /*advise_erase*/,
|
||||
bool& kept_in_sec_cache) override {
|
||||
Statistics* /*stats*/, bool& kept_in_sec_cache) override {
|
||||
std::string key_str = key.ToString();
|
||||
TEST_SYNC_POINT_CALLBACK("TestSecondaryCache::Lookup", &key_str);
|
||||
|
||||
@@ -88,7 +89,8 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
/*alloc*/ nullptr, &value, &charge);
|
||||
if (s.ok()) {
|
||||
secondary_handle.reset(new TestSecondaryCacheResultHandle(
|
||||
cache_.get(), handle, value, charge, /*ready=*/wait));
|
||||
cache_.get(), handle, value, charge,
|
||||
/*ready=*/wait || ready_before_wait_));
|
||||
kept_in_sec_cache = true;
|
||||
} else {
|
||||
cache_.Release(handle);
|
||||
@@ -168,6 +170,7 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
BasicTypedSharedCacheInterface<char[], CacheEntryRole::kMisc>;
|
||||
using TypedHandle = SharedCache::TypedHandle;
|
||||
SharedCache cache_;
|
||||
bool ready_before_wait_;
|
||||
uint32_t num_insert_saved_;
|
||||
uint32_t num_hits_;
|
||||
uint32_t num_misses_;
|
||||
@@ -179,11 +182,10 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
DBTieredSecondaryCacheTest()
|
||||
: DBTestBase("db_tiered_secondary_cache_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
std::shared_ptr<Cache> NewCache(size_t pri_capacity,
|
||||
size_t compressed_capacity,
|
||||
size_t nvm_capacity,
|
||||
TieredAdmissionPolicy adm_policy =
|
||||
TieredAdmissionPolicy::kAdmPolicyAuto) {
|
||||
std::shared_ptr<Cache> NewCache(
|
||||
size_t pri_capacity, size_t compressed_capacity, size_t nvm_capacity,
|
||||
TieredAdmissionPolicy adm_policy = TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
bool ready_before_wait = false) {
|
||||
LRUCacheOptions lru_opts;
|
||||
TieredCacheOptions opts;
|
||||
lru_opts.capacity = 0;
|
||||
@@ -194,10 +196,11 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
opts.comp_cache_opts.capacity = 0;
|
||||
opts.comp_cache_opts.num_shard_bits = 0;
|
||||
opts.total_capacity = pri_capacity + compressed_capacity;
|
||||
opts.compressed_secondary_ratio =
|
||||
opts.compressed_secondary_ratio = compressed_secondary_ratio_ =
|
||||
(double)compressed_capacity / opts.total_capacity;
|
||||
if (nvm_capacity > 0) {
|
||||
nvm_sec_cache_.reset(new TestSecondaryCache(nvm_capacity));
|
||||
nvm_sec_cache_.reset(
|
||||
new TestSecondaryCache(nvm_capacity, ready_before_wait));
|
||||
opts.nvm_sec_cache = nvm_sec_cache_;
|
||||
}
|
||||
opts.adm_policy = adm_policy;
|
||||
@@ -207,6 +210,12 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
return cache_;
|
||||
}
|
||||
|
||||
void ClearPrimaryCache() {
|
||||
ASSERT_EQ(UpdateTieredCache(cache_, -1, 1.0), Status::OK());
|
||||
ASSERT_EQ(UpdateTieredCache(cache_, -1, compressed_secondary_ratio_),
|
||||
Status::OK());
|
||||
}
|
||||
|
||||
TestSecondaryCache* nvm_sec_cache() { return nvm_sec_cache_.get(); }
|
||||
|
||||
CompressedSecondaryCache* compressed_secondary_cache() {
|
||||
@@ -218,6 +227,7 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<TestSecondaryCache> nvm_sec_cache_;
|
||||
double compressed_secondary_ratio_;
|
||||
};
|
||||
|
||||
// In this test, the block size is set to 4096. Each value is 1007 bytes, so
|
||||
@@ -582,6 +592,116 @@ TEST_F(DBTieredSecondaryCacheTest, WaitAllTest) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBTieredSecondaryCacheTest, ReadyBeforeWaitAllTest) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
return;
|
||||
}
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = NewCache(250 * 1024, 20 * 1024, 256 * 1024,
|
||||
TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
/*ready_before_wait=*/true);
|
||||
table_options.block_size = 4 * 1024;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
options.paranoid_file_checks = false;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v;
|
||||
test::CompressibleString(&rnd, 0.5, 1007, &p_v);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
|
||||
keys.push_back(Key(0));
|
||||
keys.push_back(Key(4));
|
||||
keys.push_back(Key(8));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 3u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 3u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 0u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 3u);
|
||||
|
||||
keys.clear();
|
||||
values.clear();
|
||||
keys.push_back(Key(12));
|
||||
keys.push_back(Key(16));
|
||||
keys.push_back(Key(20));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 6u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 6u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 0u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 6u);
|
||||
|
||||
keys.clear();
|
||||
values.clear();
|
||||
keys.push_back(Key(0));
|
||||
keys.push_back(Key(4));
|
||||
keys.push_back(Key(8));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 6u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 6u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 3u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 6u);
|
||||
|
||||
ClearPrimaryCache();
|
||||
|
||||
keys.clear();
|
||||
values.clear();
|
||||
keys.push_back(Key(0));
|
||||
keys.push_back(Key(32));
|
||||
keys.push_back(Key(36));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 8u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 8u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 4u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 8u);
|
||||
|
||||
keys.clear();
|
||||
values.clear();
|
||||
keys.push_back(Key(0));
|
||||
keys.push_back(Key(32));
|
||||
keys.push_back(Key(36));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 8u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 8u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 4u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 8u);
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
// This test is for iteration. It iterates through a set of keys in two
|
||||
// passes. First pass loads the compressed blocks into the nvm tier, and
|
||||
// the second pass should hit all of those blocks.
|
||||
|
||||
@@ -1220,7 +1220,7 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
auto sec_handle0 = secondary_cache->Lookup(
|
||||
key0, BlobSource::SharedCacheInterface::GetFullHelper(),
|
||||
/*context*/ nullptr, true,
|
||||
/*advise_erase=*/true, kept_in_sec_cache);
|
||||
/*advise_erase=*/true, /*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_FALSE(kept_in_sec_cache);
|
||||
ASSERT_NE(sec_handle0, nullptr);
|
||||
ASSERT_TRUE(sec_handle0->IsReady());
|
||||
@@ -1248,7 +1248,7 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
auto sec_handle1 = secondary_cache->Lookup(
|
||||
key1, BlobSource::SharedCacheInterface::GetFullHelper(),
|
||||
/*context*/ nullptr, true,
|
||||
/*advise_erase=*/true, kept_in_sec_cache);
|
||||
/*advise_erase=*/true, /*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_FALSE(kept_in_sec_cache);
|
||||
ASSERT_EQ(sec_handle1, nullptr);
|
||||
|
||||
|
||||
+45
-15
@@ -833,8 +833,8 @@ std::unique_ptr<WriteControllerToken> SetupDelay(
|
||||
return write_controller->GetDelayToken(write_rate);
|
||||
}
|
||||
|
||||
int GetL0ThresholdSpeedupCompaction(int level0_file_num_compaction_trigger,
|
||||
int level0_slowdown_writes_trigger) {
|
||||
int GetL0FileCountForCompactionSpeedup(int level0_file_num_compaction_trigger,
|
||||
int level0_slowdown_writes_trigger) {
|
||||
// SanitizeOptions() ensures it.
|
||||
assert(level0_file_num_compaction_trigger <= level0_slowdown_writes_trigger);
|
||||
|
||||
@@ -864,6 +864,36 @@ int GetL0ThresholdSpeedupCompaction(int level0_file_num_compaction_trigger,
|
||||
return static_cast<int>(res);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t GetPendingCompactionBytesForCompactionSpeedup(
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const VersionStorageInfo* vstorage) {
|
||||
// Compaction debt relatively large compared to the stable (bottommost) data
|
||||
// size indicates compaction fell behind.
|
||||
const uint64_t kBottommostSizeMultiplier = 8;
|
||||
// Meaningful progress toward the slowdown trigger is another good indication.
|
||||
const uint64_t kSlowdownTriggerDivisor = 4;
|
||||
|
||||
uint64_t bottommost_files_size = 0;
|
||||
for (const auto& level_and_file : vstorage->BottommostFiles()) {
|
||||
bottommost_files_size += level_and_file.second->fd.GetFileSize();
|
||||
}
|
||||
|
||||
// Slowdown trigger might be zero but that means compaction speedup should
|
||||
// always happen (undocumented/historical), so no special treatment is needed.
|
||||
uint64_t slowdown_threshold =
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit /
|
||||
kSlowdownTriggerDivisor;
|
||||
|
||||
// Size of zero, however, should not be used to decide to speedup compaction.
|
||||
if (bottommost_files_size == 0) {
|
||||
return slowdown_threshold;
|
||||
}
|
||||
|
||||
uint64_t size_threshold =
|
||||
MultiplyCheckOverflow(bottommost_files_size, kBottommostSizeMultiplier);
|
||||
return std::min(size_threshold, slowdown_threshold);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
std::pair<WriteStallCondition, WriteStallCause>
|
||||
@@ -1019,7 +1049,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
} else {
|
||||
assert(write_stall_condition == WriteStallCondition::kNormal);
|
||||
if (vstorage->l0_delay_trigger_count() >=
|
||||
GetL0ThresholdSpeedupCompaction(
|
||||
GetL0FileCountForCompactionSpeedup(
|
||||
mutable_cf_options.level0_file_num_compaction_trigger,
|
||||
mutable_cf_options.level0_slowdown_writes_trigger)) {
|
||||
write_controller_token_ =
|
||||
@@ -1029,22 +1059,22 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
"[%s] Increasing compaction threads because we have %d level-0 "
|
||||
"files ",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
} else if (vstorage->estimated_compaction_needed_bytes() >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit / 4) {
|
||||
// Increase compaction threads if bytes needed for compaction exceeds
|
||||
// 1/4 of threshold for slowing down.
|
||||
} else if (mutable_cf_options.soft_pending_compaction_bytes_limit == 0) {
|
||||
// If soft pending compaction byte limit is not set, always speed up
|
||||
// compaction.
|
||||
write_controller_token_ =
|
||||
write_controller->GetCompactionPressureToken();
|
||||
if (mutable_cf_options.soft_pending_compaction_bytes_limit > 0) {
|
||||
ROCKS_LOG_INFO(
|
||||
ioptions_.logger,
|
||||
"[%s] Increasing compaction threads because of estimated pending "
|
||||
"compaction "
|
||||
"bytes %" PRIu64,
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes());
|
||||
}
|
||||
} else if (vstorage->estimated_compaction_needed_bytes() >=
|
||||
GetPendingCompactionBytesForCompactionSpeedup(
|
||||
mutable_cf_options, vstorage)) {
|
||||
write_controller_token_ =
|
||||
write_controller->GetCompactionPressureToken();
|
||||
ROCKS_LOG_INFO(
|
||||
ioptions_.logger,
|
||||
"[%s] Increasing compaction threads because of estimated pending "
|
||||
"compaction "
|
||||
"bytes %" PRIu64,
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes());
|
||||
} else {
|
||||
write_controller_token_.reset();
|
||||
}
|
||||
|
||||
@@ -2971,6 +2971,43 @@ TEST_P(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) {
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, CompactionSpeedupForCompactionDebt) {
|
||||
db_options_.max_background_compactions = 6;
|
||||
Open();
|
||||
ColumnFamilyData* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())->cfd();
|
||||
MutableCFOptions mutable_cf_options(column_family_options_);
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit =
|
||||
std::numeric_limits<uint64_t>::max();
|
||||
|
||||
{
|
||||
// No bottommost data, so debt ratio cannot trigger speedup.
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(1048576 /* 1MB */);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
}
|
||||
|
||||
// Add a tiny amount of bottommost data.
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "foo", "bar"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
{
|
||||
// 1MB debt is way bigger than bottommost data so definitely triggers
|
||||
// speedup.
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(1048576 /* 1MB */);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
// Eight bytes is way smaller than bottommost data so definitely does not
|
||||
// trigger speedup.
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(8);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, CreateAndDestroyOptions) {
|
||||
std::unique_ptr<ColumnFamilyOptions> cfo(new ColumnFamilyOptions());
|
||||
ColumnFamilyHandle* cfh;
|
||||
|
||||
@@ -3301,7 +3301,7 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FlushFinish:0");
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
// There is no need to do these clean up if the flush job is rescheduled
|
||||
// There is no need to find obsolete files if the flush job is rescheduled
|
||||
// to retain user-defined timestamps because the job doesn't get to the
|
||||
// stage of actually flushing the MemTables.
|
||||
if (!flush_rescheduled_to_retain_udt) {
|
||||
@@ -3309,25 +3309,25 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
// have created. Thus, we force full scan in FindObsoleteFiles()
|
||||
FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress() &&
|
||||
!s.IsColumnFamilyDropped());
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FilesFound");
|
||||
// Have to flush the info logs before bg_flush_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
// released, the deconstructor of DB can kick in and destroy all the
|
||||
// states of DB so info_log might not be available after that point.
|
||||
// It also applies to access other states that DB owns.
|
||||
log_buffer.FlushBufferToLog();
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
mutex_.Lock();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:ContextCleanedUp");
|
||||
}
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FilesFound");
|
||||
// Have to flush the info logs before bg_flush_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
// released, the deconstructor of DB can kick in and destroy all the
|
||||
// states of DB so info_log might not be available after that point.
|
||||
// It also applies to access other states that DB owns.
|
||||
log_buffer.FlushBufferToLog();
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
mutex_.Lock();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:ContextCleanedUp");
|
||||
|
||||
assert(num_running_flushes_ > 0);
|
||||
num_running_flushes_--;
|
||||
|
||||
@@ -437,10 +437,10 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
valid_batches += writer->batch_cnt;
|
||||
if (writer->ShouldWriteToMemtable()) {
|
||||
total_count += WriteBatchInternal::Count(writer->batch);
|
||||
total_byte_size = WriteBatchInternal::AppendedByteSize(
|
||||
total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
|
||||
parallel = parallel && !writer->batch->HasMerge();
|
||||
}
|
||||
total_byte_size = WriteBatchInternal::AppendedByteSize(
|
||||
total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
|
||||
if (writer->pre_release_callback) {
|
||||
pre_release_callback_cnt++;
|
||||
}
|
||||
@@ -720,11 +720,11 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
if (writer->ShouldWriteToMemtable()) {
|
||||
writer->sequence = next_sequence;
|
||||
size_t count = WriteBatchInternal::Count(writer->batch);
|
||||
total_byte_size = WriteBatchInternal::AppendedByteSize(
|
||||
total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
|
||||
next_sequence += count;
|
||||
total_count += count;
|
||||
}
|
||||
total_byte_size = WriteBatchInternal::AppendedByteSize(
|
||||
total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
|
||||
}
|
||||
}
|
||||
if (w.disable_wal) {
|
||||
@@ -1839,11 +1839,12 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
|
||||
delay = 0;
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:Start");
|
||||
start_time = immutable_db_options_.clock->NowMicros();
|
||||
|
||||
if (delay > 0) {
|
||||
if (write_options.no_slowdown) {
|
||||
return Status::Incomplete("Write stall");
|
||||
}
|
||||
start_time = immutable_db_options_.clock->NowMicros();
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:Sleep");
|
||||
|
||||
// Notify write_thread about the stall so it can setup a barrier and
|
||||
|
||||
+16
-32
@@ -83,8 +83,7 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
cfd_(cfd),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
timestamp_lb_(read_options.iter_start_ts),
|
||||
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0),
|
||||
auto_readahead_size_(read_options.auto_readahead_size) {
|
||||
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0) {
|
||||
RecordTick(statistics_, NO_ITERATOR_CREATED);
|
||||
if (pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
@@ -395,9 +394,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
case kTypeValue:
|
||||
case kTypeBlobIndex:
|
||||
case kTypeWideColumnEntity:
|
||||
if (!iter_.PrepareValue()) {
|
||||
assert(!iter_.status().ok());
|
||||
valid_ = false;
|
||||
if (!PrepareValue()) {
|
||||
return false;
|
||||
}
|
||||
if (timestamp_lb_) {
|
||||
@@ -428,9 +425,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
return true;
|
||||
break;
|
||||
case kTypeMerge:
|
||||
if (!iter_.PrepareValue()) {
|
||||
assert(!iter_.status().ok());
|
||||
valid_ = false;
|
||||
if (!PrepareValue()) {
|
||||
return false;
|
||||
}
|
||||
saved_key_.SetUserKey(
|
||||
@@ -575,8 +570,7 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
iter_.Next();
|
||||
break;
|
||||
}
|
||||
if (!iter_.PrepareValue()) {
|
||||
valid_ = false;
|
||||
if (!PrepareValue()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -747,22 +741,15 @@ bool DBIter::ReverseToBackward() {
|
||||
// When current_entry_is_merged_ is true, iter_ may be positioned on the next
|
||||
// key, which may not exist or may have prefix different from current.
|
||||
// If that's the case, seek to saved_key_.
|
||||
//
|
||||
// In case of auto_readahead_size enabled, index_iter moves forward during
|
||||
// forward scan for block cache lookup and points to different block. If Prev
|
||||
// op is called, it needs to call SeekForPrev to point to right index_iter_ in
|
||||
// BlockBasedTableIterator. This only happens when direction is changed from
|
||||
// forward to backward.
|
||||
if ((current_entry_is_merged_ &&
|
||||
(!expect_total_order_inner_iter() || !iter_.Valid())) ||
|
||||
auto_readahead_size_) {
|
||||
if (current_entry_is_merged_ &&
|
||||
(!expect_total_order_inner_iter() || !iter_.Valid())) {
|
||||
IterKey last_key;
|
||||
// Using kMaxSequenceNumber and kValueTypeForSeek
|
||||
// (not kValueTypeForSeekForPrev) to seek to a key strictly smaller
|
||||
// than saved_key_.
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
if (!expect_total_order_inner_iter() || auto_readahead_size_) {
|
||||
if (!expect_total_order_inner_iter()) {
|
||||
iter_.SeekForPrev(last_key.GetInternalKey());
|
||||
} else {
|
||||
// Some iterators may not support SeekForPrev(), so we avoid using it
|
||||
@@ -909,8 +896,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
return FindValueForCurrentKeyUsingSeek();
|
||||
}
|
||||
|
||||
if (!iter_.PrepareValue()) {
|
||||
valid_ = false;
|
||||
if (!PrepareValue()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1149,8 +1135,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!iter_.PrepareValue()) {
|
||||
valid_ = false;
|
||||
if (!PrepareValue()) {
|
||||
return false;
|
||||
}
|
||||
if (timestamp_size_ > 0) {
|
||||
@@ -1215,8 +1200,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
ikey.type == kTypeDeletionWithTimestamp) {
|
||||
break;
|
||||
}
|
||||
if (!iter_.PrepareValue()) {
|
||||
valid_ = false;
|
||||
if (!PrepareValue()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1293,8 +1277,8 @@ bool DBIter::MergeWithNoBaseValue(const Slice& user_key) {
|
||||
const Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, user_key, MergeHelper::kNoBaseValue,
|
||||
merge_context_.GetOperands(), logger_, statistics_, clock_,
|
||||
/* update_num_ops_stats */ true, &saved_value_, &pinned_value_,
|
||||
&result_type, /* op_failure_scope */ nullptr);
|
||||
/* update_num_ops_stats */ true, /* op_failure_scope */ nullptr,
|
||||
&saved_value_, &pinned_value_, &result_type);
|
||||
return SetValueAndColumnsFromMergeResult(s, result_type);
|
||||
}
|
||||
|
||||
@@ -1306,8 +1290,8 @@ bool DBIter::MergeWithPlainBaseValue(const Slice& value,
|
||||
const Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, user_key, MergeHelper::kPlainBaseValue, value,
|
||||
merge_context_.GetOperands(), logger_, statistics_, clock_,
|
||||
/* update_num_ops_stats */ true, &saved_value_, &pinned_value_,
|
||||
&result_type, /* op_failure_scope */ nullptr);
|
||||
/* update_num_ops_stats */ true, /* op_failure_scope */ nullptr,
|
||||
&saved_value_, &pinned_value_, &result_type);
|
||||
return SetValueAndColumnsFromMergeResult(s, result_type);
|
||||
}
|
||||
|
||||
@@ -1319,8 +1303,8 @@ bool DBIter::MergeWithWideColumnBaseValue(const Slice& entity,
|
||||
const Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, user_key, MergeHelper::kWideBaseValue, entity,
|
||||
merge_context_.GetOperands(), logger_, statistics_, clock_,
|
||||
/* update_num_ops_stats */ true, &saved_value_, &pinned_value_,
|
||||
&result_type, /* op_failure_scope */ nullptr);
|
||||
/* update_num_ops_stats */ true, /* op_failure_scope */ nullptr,
|
||||
&saved_value_, &pinned_value_, &result_type);
|
||||
return SetValueAndColumnsFromMergeResult(s, result_type);
|
||||
}
|
||||
|
||||
|
||||
+16
-1
@@ -328,6 +328,22 @@ class DBIter final : public Iterator {
|
||||
bool MergeWithPlainBaseValue(const Slice& value, const Slice& user_key);
|
||||
bool MergeWithWideColumnBaseValue(const Slice& entity, const Slice& user_key);
|
||||
|
||||
bool PrepareValue() {
|
||||
if (!iter_.PrepareValue()) {
|
||||
assert(!iter_.status().ok());
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
// ikey_ could change as BlockBasedTableIterator does Block cache
|
||||
// lookup and index_iter_ could point to different block resulting
|
||||
// in ikey_ pointing to wrong key. So ikey_ needs to be updated in
|
||||
// case of Seek/Next calls to point to right key again.
|
||||
if (!ParseKey(&ikey_)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
Env* const env_;
|
||||
SystemClock* clock_;
|
||||
@@ -402,7 +418,6 @@ class DBIter final : public Iterator {
|
||||
const Slice* const timestamp_lb_;
|
||||
const size_t timestamp_size_;
|
||||
std::string saved_timestamp_;
|
||||
bool auto_readahead_size_;
|
||||
};
|
||||
|
||||
// Return a new iterator that converts internal keys (yielded by
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#include <string>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -283,6 +285,78 @@ TEST_F(DBStatisticsTest, BlockChecksumStats) {
|
||||
options.statistics->getTickerCount(BLOCK_CHECKSUM_MISMATCH_COUNT));
|
||||
}
|
||||
|
||||
TEST_F(DBStatisticsTest, BytesWrittenStats) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kExceptHistogramOrTimers);
|
||||
Reopen(options);
|
||||
|
||||
EXPECT_EQ(0, options.statistics->getAndResetTickerCount(WAL_FILE_BYTES));
|
||||
EXPECT_EQ(0, options.statistics->getAndResetTickerCount(BYTES_WRITTEN));
|
||||
|
||||
const int kNumKeysWritten = 100;
|
||||
|
||||
// Scenario 0: Not using transactions.
|
||||
// This will write to WAL and memtable directly.
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
|
||||
for (int i = 0; i < kNumKeysWritten; ++i) {
|
||||
ASSERT_OK(Put(Key(i), "val"));
|
||||
}
|
||||
|
||||
EXPECT_EQ(options.statistics->getAndResetTickerCount(WAL_FILE_BYTES),
|
||||
options.statistics->getAndResetTickerCount(BYTES_WRITTEN));
|
||||
|
||||
// Scenario 1: Using transactions.
|
||||
// This should not double count BYTES_WRITTEN (issue #12061).
|
||||
for (bool enable_pipelined_write : {false, true}) {
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
|
||||
// Destroy the DB to recreate as a TransactionDB.
|
||||
Destroy(options, true);
|
||||
|
||||
// Create a TransactionDB.
|
||||
TransactionDB* txn_db = nullptr;
|
||||
TransactionDBOptions txn_db_opts;
|
||||
txn_db_opts.write_policy = TxnDBWritePolicy::WRITE_COMMITTED;
|
||||
options.enable_pipelined_write = enable_pipelined_write;
|
||||
ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, &txn_db));
|
||||
ASSERT_NE(txn_db, nullptr);
|
||||
db_ = txn_db->GetBaseDB();
|
||||
|
||||
WriteOptions wopts;
|
||||
TransactionOptions txn_opts;
|
||||
Transaction* txn = txn_db->BeginTransaction(wopts, txn_opts, nullptr);
|
||||
ASSERT_NE(txn, nullptr);
|
||||
ASSERT_OK(txn->SetName("txn1"));
|
||||
|
||||
for (int i = 0; i < kNumKeysWritten; ++i) {
|
||||
ASSERT_OK(txn->Put(Key(i), "val"));
|
||||
}
|
||||
|
||||
// Prepare() writes to WAL, but not to memtable. (WriteCommitted)
|
||||
ASSERT_OK(txn->Prepare());
|
||||
EXPECT_NE(0, options.statistics->getTickerCount(WAL_FILE_BYTES));
|
||||
// BYTES_WRITTEN would have been non-zero previously (issue #12061).
|
||||
EXPECT_EQ(0, options.statistics->getTickerCount(BYTES_WRITTEN));
|
||||
|
||||
// Commit() writes to memtable and also a commit marker to WAL.
|
||||
ASSERT_OK(txn->Commit());
|
||||
delete txn;
|
||||
|
||||
// The WAL has an extra header of size `kHeader` written to it,
|
||||
// as we are writing twice to it (first during Prepare, second during
|
||||
// Commit).
|
||||
EXPECT_EQ(options.statistics->getAndResetTickerCount(WAL_FILE_BYTES),
|
||||
options.statistics->getAndResetTickerCount(BYTES_WRITTEN) +
|
||||
WriteBatchInternal::kHeader);
|
||||
|
||||
// Cleanup
|
||||
db_ = nullptr;
|
||||
delete txn_db;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "table/table_properties_internal.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/atomic.h"
|
||||
#include "util/random.h"
|
||||
|
||||
|
||||
@@ -417,6 +418,71 @@ TEST_F(DBTablePropertiesTest, GetDbIdentifiersProperty) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, FactoryReturnsNull) {
|
||||
struct JunkTablePropertiesCollector : public TablePropertiesCollector {
|
||||
const char* Name() const override { return "JunkTablePropertiesCollector"; }
|
||||
Status Finish(UserCollectedProperties* properties) override {
|
||||
properties->insert({"Junk", "Junk"});
|
||||
return Status::OK();
|
||||
}
|
||||
UserCollectedProperties GetReadableProperties() const override {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// Alternates between putting a "Junk" property and using `nullptr` to
|
||||
// opt out.
|
||||
static RelaxedAtomic<int> count{0};
|
||||
struct SometimesTablePropertiesCollectorFactory
|
||||
: public TablePropertiesCollectorFactory {
|
||||
const char* Name() const override {
|
||||
return "SometimesTablePropertiesCollectorFactory";
|
||||
}
|
||||
TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollectorFactory::Context /*context*/) override {
|
||||
if (count.FetchAddRelaxed(1) & 1) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return new JunkTablePropertiesCollector();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.table_properties_collector_factories.emplace_back(
|
||||
std::make_shared<SometimesTablePropertiesCollectorFactory>());
|
||||
// For plain table
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(4));
|
||||
for (std::shared_ptr<TableFactory> tf :
|
||||
{options.table_factory,
|
||||
std::shared_ptr<TableFactory>(NewPlainTableFactory({}))}) {
|
||||
SCOPED_TRACE("Table factory = " + std::string(tf->Name()));
|
||||
options.table_factory = tf;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("key0", "value1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("key0", "value2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
TablePropertiesCollection props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
|
||||
int no_junk_count = 0;
|
||||
int junk_count = 0;
|
||||
for (const auto& item : props) {
|
||||
if (item.second->user_collected_properties.find("Junk") !=
|
||||
item.second->user_collected_properties.end()) {
|
||||
junk_count++;
|
||||
} else {
|
||||
no_junk_count++;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(1, no_junk_count);
|
||||
EXPECT_EQ(1, junk_count);
|
||||
}
|
||||
}
|
||||
|
||||
class DBTableHostnamePropertyTest
|
||||
: public DBTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<int, std::string>> {
|
||||
|
||||
@@ -1646,35 +1646,88 @@ TEST_F(DBBasicTestWithTimestamp, GetWithRowCache) {
|
||||
|
||||
const Snapshot* snap_with_nothing = db_->GetSnapshot();
|
||||
ASSERT_OK(db_->Put(write_opts, "foo", ts_early, "bar"));
|
||||
const Snapshot* snap_with_foo = db_->GetSnapshot();
|
||||
ASSERT_OK(db_->Put(write_opts, "foo2", ts_early, "bar2"));
|
||||
ASSERT_OK(db_->Put(write_opts, "foo3", ts_early, "bar3"));
|
||||
|
||||
// Ensure file has sequence number greater than snapshot_with_foo
|
||||
for (int i = 0; i < 10; i++) {
|
||||
std::string numStr = std::to_string(i);
|
||||
ASSERT_OK(db_->Put(write_opts, numStr, ts_later, numStr));
|
||||
}
|
||||
const Snapshot* snap_with_foo = db_->GetSnapshot();
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 0);
|
||||
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &ts_later_slice;
|
||||
|
||||
std::string read_value;
|
||||
std::string read_ts;
|
||||
Status s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1);
|
||||
ASSERT_EQ(read_ts, ts_early);
|
||||
Status s;
|
||||
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 1);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1);
|
||||
// Row cache is not storing the ts when record is inserted/updated.
|
||||
// To be fixed after enabling ROW_CACHE with timestamp.
|
||||
// ASSERT_EQ(read_ts, ts_early);
|
||||
int expected_hit_count = 0;
|
||||
int expected_miss_count = 0;
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), expected_miss_count);
|
||||
|
||||
{
|
||||
read_opts.timestamp = nullptr;
|
||||
s = db_->Get(read_opts, "foo", &read_value);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
}
|
||||
|
||||
// Mix use of Get
|
||||
{
|
||||
read_opts.timestamp = &ts_later_slice;
|
||||
|
||||
// Use Get without ts first, expect cache entry to store the correct ts
|
||||
s = db_->Get(read_opts, "foo2", &read_value);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS),
|
||||
++expected_miss_count);
|
||||
ASSERT_EQ(read_value, "bar2");
|
||||
|
||||
s = db_->Get(read_opts, "foo2", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), ++expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), expected_miss_count);
|
||||
ASSERT_EQ(read_ts, ts_early);
|
||||
ASSERT_EQ(read_value, "bar2");
|
||||
|
||||
// Use Get with ts first, expect the Get without ts can get correct record
|
||||
s = db_->Get(read_opts, "foo3", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS),
|
||||
++expected_miss_count);
|
||||
ASSERT_EQ(read_ts, ts_early);
|
||||
ASSERT_EQ(read_value, "bar3");
|
||||
|
||||
s = db_->Get(read_opts, "foo3", &read_value);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), ++expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), expected_miss_count);
|
||||
ASSERT_EQ(read_value, "bar3");
|
||||
}
|
||||
|
||||
{
|
||||
// Test with consecutive calls of Get with ts.
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS),
|
||||
++expected_miss_count);
|
||||
ASSERT_EQ(read_ts, ts_early);
|
||||
ASSERT_EQ(read_value, "bar");
|
||||
|
||||
// Test repeated get on cache entry
|
||||
for (int i = 0; i < 3; i++) {
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT),
|
||||
++expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS),
|
||||
expected_miss_count);
|
||||
ASSERT_EQ(read_ts, ts_early);
|
||||
ASSERT_EQ(read_value, "bar");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::string ts_nothing = Timestamp(0, 0);
|
||||
@@ -1682,41 +1735,43 @@ TEST_F(DBBasicTestWithTimestamp, GetWithRowCache) {
|
||||
read_opts.timestamp = &ts_nothing_slice;
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 1);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
|
||||
|
||||
read_opts.timestamp = &ts_later_slice;
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS),
|
||||
++expected_miss_count);
|
||||
}
|
||||
|
||||
{
|
||||
read_opts.snapshot = snap_with_foo;
|
||||
read_opts.timestamp = &ts_later_slice;
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS),
|
||||
++expected_miss_count);
|
||||
ASSERT_EQ(read_ts, ts_early);
|
||||
ASSERT_EQ(read_value, "bar");
|
||||
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 3);
|
||||
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), ++expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), expected_miss_count);
|
||||
ASSERT_EQ(read_ts, ts_early);
|
||||
ASSERT_EQ(read_value, "bar");
|
||||
}
|
||||
|
||||
{
|
||||
read_opts.snapshot = snap_with_nothing;
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS),
|
||||
++expected_miss_count);
|
||||
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), expected_hit_count);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS),
|
||||
++expected_miss_count);
|
||||
}
|
||||
|
||||
db_->ReleaseSnapshot(snap_with_nothing);
|
||||
@@ -1724,6 +1779,65 @@ TEST_F(DBBasicTestWithTimestamp, GetWithRowCache) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, GetWithRowCacheMultiSST) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
LRUCacheOptions cache_options;
|
||||
cache_options.capacity = 8192;
|
||||
options.row_cache = cache_options.MakeSharedRowCache();
|
||||
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.merge_operator = MergeOperators::CreateStringAppendTESTOperator();
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string ts_early = Timestamp(1, 0);
|
||||
std::string ts_later = Timestamp(10, 0);
|
||||
Slice ts_later_slice = ts_later;
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "foo", ts_early, "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ColumnFamilyHandle* default_cf = db_->DefaultColumnFamily();
|
||||
ASSERT_OK(
|
||||
db_->Merge(WriteOptions(), default_cf, "foo", Timestamp(2, 0), "v2"));
|
||||
ASSERT_OK(
|
||||
db_->Merge(WriteOptions(), default_cf, "foo", Timestamp(3, 0), "v3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &ts_later_slice;
|
||||
|
||||
std::string read_value;
|
||||
std::string read_ts;
|
||||
Status s;
|
||||
|
||||
{
|
||||
// Since there are two SST files, will trigger the table lookup twice.
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
|
||||
ASSERT_EQ(read_ts, Timestamp(3, 0));
|
||||
ASSERT_EQ(read_value, "v1,v2,v3");
|
||||
|
||||
s = db_->Get(read_opts, "foo", &read_value, &read_ts);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
|
||||
ASSERT_EQ(read_ts, Timestamp(3, 0));
|
||||
ASSERT_EQ(read_value, "v1,v2,v3");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampTableOptions, MultiGetPrefixFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
|
||||
@@ -326,7 +326,7 @@ TEST_F(ImportColumnFamilyTest, ImportSSTFileWriterFilesWithRangeTombstone) {
|
||||
const SstFileMetaData* file_meta = nullptr;
|
||||
for (const auto& level_meta : import_cf_meta.levels) {
|
||||
if (!level_meta.files.empty()) {
|
||||
file_meta = &(level_meta.files[0]);
|
||||
file_meta = level_meta.files.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -389,7 +389,7 @@ TEST_F(ImportColumnFamilyTest, ImportExportedSSTFromAnotherCF) {
|
||||
*metadata_ptr_, &import_cfh2_));
|
||||
ASSERT_NE(import_cfh2_, nullptr);
|
||||
delete metadata_ptr_;
|
||||
metadata_ptr_ = NULL;
|
||||
metadata_ptr_ = nullptr;
|
||||
|
||||
std::string value1, value2;
|
||||
|
||||
|
||||
+4
-4
@@ -132,8 +132,8 @@ class TestCompactionListener : public EventListener {
|
||||
ASSERT_EQ(db->GetEnv()->GetThreadID(), ci.thread_id);
|
||||
ASSERT_GT(ci.thread_id, 0U);
|
||||
|
||||
for (auto fl : {ci.input_files, ci.output_files}) {
|
||||
for (auto fn : fl) {
|
||||
for (const auto& fl : {ci.input_files, ci.output_files}) {
|
||||
for (const auto& fn : fl) {
|
||||
auto it = ci.table_properties.find(fn);
|
||||
ASSERT_NE(it, ci.table_properties.end());
|
||||
auto tp = it->second;
|
||||
@@ -237,7 +237,7 @@ class TestFlushListener : public EventListener {
|
||||
std::vector<ThreadStatus> thread_list;
|
||||
ASSERT_OK(env_->GetThreadList(&thread_list));
|
||||
bool found_match = false;
|
||||
for (auto thread_status : thread_list) {
|
||||
for (const auto& thread_status : thread_list) {
|
||||
if (thread_status.operation_type == ThreadStatus::OP_FLUSH ||
|
||||
thread_status.operation_type == ThreadStatus::OP_COMPACTION) {
|
||||
if (thread_id == thread_status.thread_id) {
|
||||
@@ -893,7 +893,7 @@ class MemTableSealedListener : public EventListener {
|
||||
SequenceNumber latest_seq_number_;
|
||||
|
||||
public:
|
||||
MemTableSealedListener() {}
|
||||
MemTableSealedListener() = default;
|
||||
void OnMemTableSealed(const MemTableInfo& info) override {
|
||||
latest_seq_number_ = info.first_seqno;
|
||||
}
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "db/log_reader.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <cstdio>
|
||||
|
||||
#include "file/sequence_file_reader.h"
|
||||
#include "port/lang.h"
|
||||
@@ -21,7 +21,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace log {
|
||||
|
||||
Reader::Reporter::~Reporter() {}
|
||||
Reader::Reporter::~Reporter() = default;
|
||||
|
||||
Reader::Reader(std::shared_ptr<Logger> info_log,
|
||||
std::unique_ptr<SequentialFileReader>&& _file,
|
||||
|
||||
+1
-1
@@ -1167,7 +1167,7 @@ TEST_P(StreamingCompressionTest, Basic) {
|
||||
}
|
||||
allocator->Deallocate((void*)output_buffer);
|
||||
} while (remaining > 0);
|
||||
std::string uncompressed_buffer = "";
|
||||
std::string uncompressed_buffer;
|
||||
int ret_val = 0;
|
||||
size_t output_pos;
|
||||
char* uncompressed_output_buffer = (char*)allocator->Allocate(kBlockSize);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "db/log_writer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "rocksdb/env.h"
|
||||
|
||||
+1
-2
@@ -9,8 +9,7 @@
|
||||
|
||||
#include "db/malloc_stats.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "port/jemalloc_helper.h"
|
||||
|
||||
@@ -58,7 +58,7 @@ class ManualCompactionTest : public testing::Test {
|
||||
|
||||
class DestroyAllCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
DestroyAllCompactionFilter() {}
|
||||
DestroyAllCompactionFilter() = default;
|
||||
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& existing_value,
|
||||
std::string* /*new_value*/,
|
||||
|
||||
+9
-9
@@ -1061,8 +1061,8 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
merge_operator, s->key->user_key(),
|
||||
MergeHelper::kPlainBaseValue, v, merge_context->GetOperands(),
|
||||
s->logger, s->statistics, s->clock,
|
||||
/* update_num_ops_stats */ true, s->value, s->columns,
|
||||
/* op_failure_scope */ nullptr);
|
||||
/* update_num_ops_stats */ true, /* op_failure_scope */ nullptr,
|
||||
s->value, s->columns);
|
||||
}
|
||||
} else if (s->value) {
|
||||
s->value->assign(v.data(), v.size());
|
||||
@@ -1114,8 +1114,8 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), MergeHelper::kWideBaseValue,
|
||||
v, merge_context->GetOperands(), s->logger, s->statistics,
|
||||
s->clock, /* update_num_ops_stats */ true, s->value, s->columns,
|
||||
/* op_failure_scope */ nullptr);
|
||||
s->clock, /* update_num_ops_stats */ true,
|
||||
/* op_failure_scope */ nullptr, s->value, s->columns);
|
||||
}
|
||||
} else if (s->value) {
|
||||
Slice value_of_default;
|
||||
@@ -1152,8 +1152,8 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), MergeHelper::kNoBaseValue,
|
||||
merge_context->GetOperands(), s->logger, s->statistics,
|
||||
s->clock, /* update_num_ops_stats */ true, s->value, s->columns,
|
||||
/* op_failure_scope */ nullptr);
|
||||
s->clock, /* update_num_ops_stats */ true,
|
||||
/* op_failure_scope */ nullptr, s->value, s->columns);
|
||||
} else {
|
||||
// We have found a final value (a base deletion) and have newer
|
||||
// merge operands that we do not intend to merge. Nothing remains
|
||||
@@ -1192,8 +1192,8 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), MergeHelper::kNoBaseValue,
|
||||
merge_context->GetOperands(), s->logger, s->statistics,
|
||||
s->clock, /* update_num_ops_stats */ true, s->value, s->columns,
|
||||
/* op_failure_scope */ nullptr);
|
||||
s->clock, /* update_num_ops_stats */ true,
|
||||
/* op_failure_scope */ nullptr, s->value, s->columns);
|
||||
}
|
||||
|
||||
*(s->found_final_value) = true;
|
||||
@@ -1362,7 +1362,7 @@ void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
range_indexes[num_keys++] = iter.index();
|
||||
}
|
||||
}
|
||||
bloom_filter_->MayContain(num_keys, &bloom_keys[0], &may_match[0]);
|
||||
bloom_filter_->MayContain(num_keys, bloom_keys.data(), may_match.data());
|
||||
for (int i = 0; i < num_keys; ++i) {
|
||||
if (!may_match[i]) {
|
||||
temp_range.SkipIndex(range_indexes[i]);
|
||||
|
||||
+22
-156
@@ -12,7 +12,6 @@
|
||||
#include "db/blob/prefetch_buffer_collection.h"
|
||||
#include "db/compaction/compaction_iteration_stats.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/wide/wide_column_serialization.h"
|
||||
#include "db/wide/wide_columns_helper.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
@@ -111,9 +110,9 @@ Status MergeHelper::TimedFullMergeImpl(
|
||||
const MergeOperator* merge_operator, const Slice& key,
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue&& existing_value,
|
||||
const std::vector<Slice>& operands, Logger* logger, Statistics* statistics,
|
||||
SystemClock* clock, bool update_num_ops_stats, std::string* result,
|
||||
Slice* result_operand, ValueType* result_type,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
SystemClock* clock, bool update_num_ops_stats,
|
||||
MergeOperator::OpFailureScope* op_failure_scope, std::string* result,
|
||||
Slice* result_operand, ValueType* result_type) {
|
||||
assert(result);
|
||||
assert(result_type);
|
||||
|
||||
@@ -173,9 +172,9 @@ Status MergeHelper::TimedFullMergeImpl(
|
||||
const MergeOperator* merge_operator, const Slice& key,
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue&& existing_value,
|
||||
const std::vector<Slice>& operands, Logger* logger, Statistics* statistics,
|
||||
SystemClock* clock, bool update_num_ops_stats, std::string* result_value,
|
||||
PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
SystemClock* clock, bool update_num_ops_stats,
|
||||
MergeOperator::OpFailureScope* op_failure_scope, std::string* result_value,
|
||||
PinnableWideColumns* result_entity) {
|
||||
assert(result_value || result_entity);
|
||||
assert(!result_value || !result_entity);
|
||||
|
||||
@@ -245,141 +244,6 @@ Status MergeHelper::TimedFullMergeImpl(
|
||||
op_failure_scope, std::move(visitor));
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, NoBaseValueTag,
|
||||
const std::vector<Slice>& operands, Logger* logger, Statistics* statistics,
|
||||
SystemClock* clock, bool update_num_ops_stats, std::string* result,
|
||||
Slice* result_operand, ValueType* result_type,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value;
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result, result_operand,
|
||||
result_type, op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, PlainBaseValueTag,
|
||||
const Slice& value, const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result, Slice* result_operand, ValueType* result_type,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value(value);
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result, result_operand,
|
||||
result_type, op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const Slice& entity, const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result, Slice* result_operand, ValueType* result_type,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value;
|
||||
|
||||
Slice entity_copy(entity);
|
||||
WideColumns existing_columns;
|
||||
|
||||
const Status s =
|
||||
WideColumnSerialization::Deserialize(entity_copy, existing_columns);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
existing_value = std::move(existing_columns);
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result, result_operand,
|
||||
result_type, op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const WideColumns& columns, const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics, SystemClock* clock,
|
||||
bool update_num_ops_stats, std::string* result, Slice* result_operand,
|
||||
ValueType* result_type, MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value(columns);
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result, result_operand,
|
||||
result_type, op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, NoBaseValueTag,
|
||||
const std::vector<Slice>& operands, Logger* logger, Statistics* statistics,
|
||||
SystemClock* clock, bool update_num_ops_stats, std::string* result_value,
|
||||
PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value;
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result_value, result_entity,
|
||||
op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, PlainBaseValueTag,
|
||||
const Slice& value, const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result_value, PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value(value);
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result_value, result_entity,
|
||||
op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const Slice& entity, const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result_value, PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value;
|
||||
|
||||
Slice entity_copy(entity);
|
||||
WideColumns existing_columns;
|
||||
|
||||
const Status s =
|
||||
WideColumnSerialization::Deserialize(entity_copy, existing_columns);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
existing_value = std::move(existing_columns);
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result_value, result_entity,
|
||||
op_failure_scope);
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const WideColumns& columns, const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics, SystemClock* clock,
|
||||
bool update_num_ops_stats, std::string* result_value,
|
||||
PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value(columns);
|
||||
|
||||
return TimedFullMergeImpl(merge_operator, key, std::move(existing_value),
|
||||
operands, logger, statistics, clock,
|
||||
update_num_ops_stats, result_value, result_entity,
|
||||
op_failure_scope);
|
||||
}
|
||||
|
||||
// PRE: iter points to the first merge type entry
|
||||
// POST: iter points to the first entry beyond the merge process (or the end)
|
||||
// keys_, operands_ are updated to reflect the merge result.
|
||||
@@ -428,7 +292,9 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
|
||||
Status s = ParseInternalKey(original_key, &orig_ikey, allow_data_in_errors);
|
||||
assert(s.ok());
|
||||
if (!s.ok()) return s;
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
assert(kTypeMerge == orig_ikey.type);
|
||||
|
||||
@@ -517,14 +383,14 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
s = TimedFullMerge(user_merge_operator_, ikey.user_key, kNoBaseValue,
|
||||
merge_context_.GetOperands(), logger_, stats_,
|
||||
clock_, /* update_num_ops_stats */ false,
|
||||
&merge_result, /* result_operand */ nullptr,
|
||||
&merge_result_type, &op_failure_scope);
|
||||
&op_failure_scope, &merge_result,
|
||||
/* result_operand */ nullptr, &merge_result_type);
|
||||
} else if (ikey.type == kTypeValue) {
|
||||
s = TimedFullMerge(user_merge_operator_, ikey.user_key, kPlainBaseValue,
|
||||
iter->value(), merge_context_.GetOperands(), logger_,
|
||||
stats_, clock_, /* update_num_ops_stats */ false,
|
||||
&merge_result, /* result_operand */ nullptr,
|
||||
&merge_result_type, &op_failure_scope);
|
||||
&op_failure_scope, &merge_result,
|
||||
/* result_operand */ nullptr, &merge_result_type);
|
||||
} else if (ikey.type == kTypeBlobIndex) {
|
||||
BlobIndex blob_index;
|
||||
|
||||
@@ -557,20 +423,20 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
s = TimedFullMerge(user_merge_operator_, ikey.user_key, kPlainBaseValue,
|
||||
blob_value, merge_context_.GetOperands(), logger_,
|
||||
stats_, clock_, /* update_num_ops_stats */ false,
|
||||
&merge_result, /* result_operand */ nullptr,
|
||||
&merge_result_type, &op_failure_scope);
|
||||
&op_failure_scope, &merge_result,
|
||||
/* result_operand */ nullptr, &merge_result_type);
|
||||
} else if (ikey.type == kTypeWideColumnEntity) {
|
||||
s = TimedFullMerge(user_merge_operator_, ikey.user_key, kWideBaseValue,
|
||||
iter->value(), merge_context_.GetOperands(), logger_,
|
||||
stats_, clock_, /* update_num_ops_stats */ false,
|
||||
&merge_result, /* result_operand */ nullptr,
|
||||
&merge_result_type, &op_failure_scope);
|
||||
&op_failure_scope, &merge_result,
|
||||
/* result_operand */ nullptr, &merge_result_type);
|
||||
} else {
|
||||
s = TimedFullMerge(user_merge_operator_, ikey.user_key, kNoBaseValue,
|
||||
merge_context_.GetOperands(), logger_, stats_,
|
||||
clock_, /* update_num_ops_stats */ false,
|
||||
&merge_result, /* result_operand */ nullptr,
|
||||
&merge_result_type, &op_failure_scope);
|
||||
&op_failure_scope, &merge_result,
|
||||
/* result_operand */ nullptr, &merge_result_type);
|
||||
}
|
||||
|
||||
// We store the result in keys_.back() and operands_.back()
|
||||
@@ -712,9 +578,9 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
MergeOperator::OpFailureScope op_failure_scope;
|
||||
s = TimedFullMerge(user_merge_operator_, orig_ikey.user_key, kNoBaseValue,
|
||||
merge_context_.GetOperands(), logger_, stats_, clock_,
|
||||
/* update_num_ops_stats */ false, &merge_result,
|
||||
/* result_operand */ nullptr, &merge_result_type,
|
||||
&op_failure_scope);
|
||||
/* update_num_ops_stats */ false, &op_failure_scope,
|
||||
&merge_result,
|
||||
/* result_operand */ nullptr, &merge_result_type);
|
||||
if (s.ok()) {
|
||||
// The original key encountered
|
||||
// We are certain that keys_ is not empty here (see assertions couple of
|
||||
|
||||
+52
-46
@@ -12,6 +12,7 @@
|
||||
#include "db/merge_context.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/wide/wide_column_serialization.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
@@ -60,74 +61,73 @@ class MergeHelper {
|
||||
struct WideBaseValueTag {};
|
||||
static constexpr WideBaseValueTag kWideBaseValue{};
|
||||
|
||||
// Variants that expose the merge result directly (in serialized form for wide
|
||||
// columns) as well as its value type. Used by iterator and compaction.
|
||||
template <typename... ResultTs>
|
||||
static Status TimedFullMerge(const MergeOperator* merge_operator,
|
||||
const Slice& key, NoBaseValueTag,
|
||||
const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics,
|
||||
SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result, Slice* result_operand,
|
||||
ValueType* result_type,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
MergeOperator::OpFailureScope* op_failure_scope,
|
||||
ResultTs... results) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value;
|
||||
|
||||
return TimedFullMergeImpl(
|
||||
merge_operator, key, std::move(existing_value), operands, logger,
|
||||
statistics, clock, update_num_ops_stats, op_failure_scope, results...);
|
||||
}
|
||||
|
||||
template <typename... ResultTs>
|
||||
static Status TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, PlainBaseValueTag,
|
||||
const Slice& value, const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result, Slice* result_operand, ValueType* result_type,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
MergeOperator::OpFailureScope* op_failure_scope, ResultTs... results) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value(value);
|
||||
|
||||
return TimedFullMergeImpl(
|
||||
merge_operator, key, std::move(existing_value), operands, logger,
|
||||
statistics, clock, update_num_ops_stats, op_failure_scope, results...);
|
||||
}
|
||||
|
||||
template <typename... ResultTs>
|
||||
static Status TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const Slice& entity, const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result, Slice* result_operand, ValueType* result_type,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
MergeOperator::OpFailureScope* op_failure_scope, ResultTs... results) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value;
|
||||
|
||||
static Status TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const WideColumns& columns, const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics, SystemClock* clock,
|
||||
bool update_num_ops_stats, std::string* result, Slice* result_operand,
|
||||
ValueType* result_type, MergeOperator::OpFailureScope* op_failure_scope);
|
||||
Slice entity_copy(entity);
|
||||
WideColumns existing_columns;
|
||||
|
||||
// Variants that expose the merge result translated to the form requested by
|
||||
// the client. (For example, if the result is a wide-column structure but the
|
||||
// client requested the results in plain-value form, the value of the default
|
||||
// column is returned.) Used by point lookups.
|
||||
static Status TimedFullMerge(const MergeOperator* merge_operator,
|
||||
const Slice& key, NoBaseValueTag,
|
||||
const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics,
|
||||
SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result_value,
|
||||
PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
const Status s =
|
||||
WideColumnSerialization::Deserialize(entity_copy, existing_columns);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
static Status TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, PlainBaseValueTag,
|
||||
const Slice& value, const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result_value, PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
existing_value = std::move(existing_columns);
|
||||
|
||||
static Status TimedFullMerge(
|
||||
const MergeOperator* merge_operator, const Slice& key, WideBaseValueTag,
|
||||
const Slice& entity, const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result_value, PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
return TimedFullMergeImpl(
|
||||
merge_operator, key, std::move(existing_value), operands, logger,
|
||||
statistics, clock, update_num_ops_stats, op_failure_scope, results...);
|
||||
}
|
||||
|
||||
template <typename... ResultTs>
|
||||
static Status TimedFullMerge(const MergeOperator* merge_operator,
|
||||
const Slice& key, WideBaseValueTag,
|
||||
const WideColumns& columns,
|
||||
const std::vector<Slice>& operands,
|
||||
Logger* logger, Statistics* statistics,
|
||||
SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result_value,
|
||||
PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
MergeOperator::OpFailureScope* op_failure_scope,
|
||||
ResultTs... results) {
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue existing_value(columns);
|
||||
|
||||
return TimedFullMergeImpl(
|
||||
merge_operator, key, std::move(existing_value), operands, logger,
|
||||
statistics, clock, update_num_ops_stats, op_failure_scope, results...);
|
||||
}
|
||||
|
||||
// During compaction, merge entries until we hit
|
||||
// - a corrupted key
|
||||
@@ -271,21 +271,27 @@ class MergeHelper {
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
MergeOperator::OpFailureScope* op_failure_scope, Visitor&& visitor);
|
||||
|
||||
// Variant that exposes the merge result directly (in serialized form for wide
|
||||
// columns) as well as its value type. Used by iterator and compaction.
|
||||
static Status TimedFullMergeImpl(
|
||||
const MergeOperator* merge_operator, const Slice& key,
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue&& existing_value,
|
||||
const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result, Slice* result_operand, ValueType* result_type,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
MergeOperator::OpFailureScope* op_failure_scope, std::string* result,
|
||||
Slice* result_operand, ValueType* result_type);
|
||||
|
||||
// Variant that exposes the merge result translated into the form requested by
|
||||
// the client. (For example, if the result is a wide-column structure but the
|
||||
// client requested the results in plain-value form, the value of the default
|
||||
// column is returned.) Used by point lookups.
|
||||
static Status TimedFullMergeImpl(
|
||||
const MergeOperator* merge_operator, const Slice& key,
|
||||
MergeOperator::MergeOperationInputV3::ExistingValue&& existing_value,
|
||||
const std::vector<Slice>& operands, Logger* logger,
|
||||
Statistics* statistics, SystemClock* clock, bool update_num_ops_stats,
|
||||
std::string* result_value, PinnableWideColumns* result_entity,
|
||||
MergeOperator::OpFailureScope* op_failure_scope);
|
||||
MergeOperator::OpFailureScope* op_failure_scope,
|
||||
std::string* result_value, PinnableWideColumns* result_entity);
|
||||
};
|
||||
|
||||
// MergeOutputIterator can be used to iterate over the result of a merge.
|
||||
|
||||
+13
-8
@@ -3,8 +3,7 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
#include <assert.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
@@ -87,7 +86,9 @@ class EnvMergeTest : public EnvWrapper {
|
||||
static std::unique_ptr<EnvMergeTest> singleton_;
|
||||
|
||||
static EnvMergeTest* GetInstance() {
|
||||
if (nullptr == singleton_) singleton_.reset(new EnvMergeTest);
|
||||
if (nullptr == singleton_) {
|
||||
singleton_.reset(new EnvMergeTest);
|
||||
}
|
||||
return singleton_.get();
|
||||
}
|
||||
};
|
||||
@@ -145,7 +146,7 @@ class Counters {
|
||||
assert(db_);
|
||||
}
|
||||
|
||||
virtual ~Counters() {}
|
||||
virtual ~Counters() = default;
|
||||
|
||||
// public interface of Counters.
|
||||
// All four functions return false
|
||||
@@ -194,7 +195,7 @@ class Counters {
|
||||
std::cerr << "value corruption\n";
|
||||
return false;
|
||||
}
|
||||
*value = DecodeFixed64(&str[0]);
|
||||
*value = DecodeFixed64(str.data());
|
||||
return true;
|
||||
} else {
|
||||
std::cerr << s.ToString() << std::endl;
|
||||
@@ -220,14 +221,18 @@ class Counters {
|
||||
uint64_t value = default_;
|
||||
int result = get(key, &value);
|
||||
assert(result);
|
||||
if (result == 0) exit(1); // Disable unused variable warning.
|
||||
if (result == 0) {
|
||||
exit(1); // Disable unused variable warning.
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void assert_add(const std::string& key, uint64_t value) {
|
||||
int result = add(key, value);
|
||||
assert(result);
|
||||
if (result == 0) exit(1); // Disable unused variable warning.
|
||||
if (result == 0) {
|
||||
exit(1); // Disable unused variable warning.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -496,7 +501,7 @@ void testSingleBatchSuccessiveMerge(DB* db, size_t max_num_merges,
|
||||
std::string get_value_str;
|
||||
ASSERT_OK(db->Get(ReadOptions(), key, &get_value_str));
|
||||
assert(get_value_str.size() == sizeof(uint64_t));
|
||||
uint64_t get_value = DecodeFixed64(&get_value_str[0]);
|
||||
uint64_t get_value = DecodeFixed64(get_value_str.data());
|
||||
ASSERT_EQ(get_value, num_merges * merge_value);
|
||||
ASSERT_EQ(num_merge_operator_calls,
|
||||
static_cast<size_t>((num_merges % (max_num_merges + 1))));
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -68,7 +66,7 @@ class ObsoleteFilesTest : public DBTestBase {
|
||||
int log_cnt = 0;
|
||||
int sst_cnt = 0;
|
||||
int manifest_cnt = 0;
|
||||
for (auto file : filenames) {
|
||||
for (const auto& file : filenames) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(file, &number, &type)) {
|
||||
|
||||
@@ -28,7 +28,7 @@ void UpdateOptionsFiles(DB* db,
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
*options_files_count = 0;
|
||||
for (auto filename : filenames) {
|
||||
for (const auto& filename : filenames) {
|
||||
if (ParseFileName(filename, &number, &type) && type == kOptionsFile) {
|
||||
filename_history->insert(filename);
|
||||
(*options_files_count)++;
|
||||
@@ -44,16 +44,16 @@ void VerifyOptionsFileName(
|
||||
EXPECT_OK(db->GetEnv()->GetChildren(db->GetName(), &filenames));
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
for (auto filename : filenames) {
|
||||
for (const auto& filename : filenames) {
|
||||
if (ParseFileName(filename, &number, &type) && type == kOptionsFile) {
|
||||
current_filenames.insert(filename);
|
||||
}
|
||||
}
|
||||
for (auto past_filename : past_filenames) {
|
||||
for (const auto& past_filename : past_filenames) {
|
||||
if (current_filenames.find(past_filename) != current_filenames.end()) {
|
||||
continue;
|
||||
}
|
||||
for (auto filename : current_filenames) {
|
||||
for (const auto& filename : current_filenames) {
|
||||
ASSERT_GT(filename, past_filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1050,7 +1050,7 @@ TEST_F(PerfContextTest, MergeOperandCount) {
|
||||
std::vector<Status> statuses(num_keys);
|
||||
|
||||
db->MultiGet(ReadOptions(), db->DefaultColumnFamily(), num_keys,
|
||||
&key_slices[0], &results[0], &statuses[0]);
|
||||
key_slices.data(), results.data(), statuses.data());
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
@@ -1068,7 +1068,7 @@ TEST_F(PerfContextTest, MergeOperandCount) {
|
||||
std::vector<Status> statuses(num_keys);
|
||||
|
||||
db->MultiGetEntity(ReadOptions(), db->DefaultColumnFamily(), num_keys,
|
||||
&key_slices[0], &results[0], &statuses[0]);
|
||||
key_slices.data(), results.data(), statuses.data());
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
|
||||
@@ -94,7 +94,7 @@ Status PeriodicTaskScheduler::Unregister(PeriodicTaskType task_type) {
|
||||
}
|
||||
|
||||
Timer* PeriodicTaskScheduler::Default() {
|
||||
static Timer timer(SystemClock::Default().get());
|
||||
STATIC_AVOID_DESTRUCTION(Timer, timer)(SystemClock::Default().get());
|
||||
return &timer;
|
||||
}
|
||||
|
||||
@@ -108,4 +108,3 @@ void PeriodicTaskScheduler::TEST_OverrideTimer(SystemClock* clock) {
|
||||
#endif // NDEBUG
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -292,7 +292,7 @@ class TestPlainTableReader : public PlainTableReader {
|
||||
table_properties_ = std::move(props);
|
||||
}
|
||||
|
||||
~TestPlainTableReader() override {}
|
||||
~TestPlainTableReader() override = default;
|
||||
|
||||
private:
|
||||
bool MatchBloom(uint32_t hash) const override {
|
||||
|
||||
+21
-8
@@ -89,8 +89,12 @@ class TestKeyComparator : public Comparator {
|
||||
const TestKey* key_a = &kkey_a;
|
||||
const TestKey* key_b = &kkey_b;
|
||||
if (key_a->prefix != key_b->prefix) {
|
||||
if (key_a->prefix < key_b->prefix) return -1;
|
||||
if (key_a->prefix > key_b->prefix) return 1;
|
||||
if (key_a->prefix < key_b->prefix) {
|
||||
return -1;
|
||||
}
|
||||
if (key_a->prefix > key_b->prefix) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
EXPECT_TRUE(key_a->prefix == key_b->prefix);
|
||||
// note, both a and b could be prefix only
|
||||
@@ -99,8 +103,12 @@ class TestKeyComparator : public Comparator {
|
||||
EXPECT_TRUE(
|
||||
(a.size() == sizeof(uint64_t) && b.size() == sizeof(TestKey)) ||
|
||||
(b.size() == sizeof(uint64_t) && a.size() == sizeof(TestKey)));
|
||||
if (a.size() < b.size()) return -1;
|
||||
if (a.size() > b.size()) return 1;
|
||||
if (a.size() < b.size()) {
|
||||
return -1;
|
||||
}
|
||||
if (a.size() > b.size()) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// both a and b are prefix
|
||||
if (a.size() == sizeof(uint64_t)) {
|
||||
@@ -109,9 +117,15 @@ class TestKeyComparator : public Comparator {
|
||||
|
||||
// both a and b are whole key
|
||||
EXPECT_TRUE(a.size() == sizeof(TestKey) && b.size() == sizeof(TestKey));
|
||||
if (key_a->sorted < key_b->sorted) return -1;
|
||||
if (key_a->sorted > key_b->sorted) return 1;
|
||||
if (key_a->sorted == key_b->sorted) return 0;
|
||||
if (key_a->sorted < key_b->sorted) {
|
||||
return -1;
|
||||
}
|
||||
if (key_a->sorted > key_b->sorted) {
|
||||
return 1;
|
||||
}
|
||||
if (key_a->sorted == key_b->sorted) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -892,4 +906,3 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
#endif // GFLAGS
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "db/compaction/compaction_iteration_stats.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
|
||||
+1
-2
@@ -378,8 +378,7 @@ TEST_P(RepairTestWithTimestamp, UnflushedSst) {
|
||||
|
||||
ColumnFamilyOptions cf_options(options);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options);
|
||||
|
||||
ASSERT_OK(DB::Open(options, dbname_, column_families, &handles_, &db_));
|
||||
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ TableCache::TableCache(const ImmutableOptions& ioptions,
|
||||
}
|
||||
}
|
||||
|
||||
TableCache::~TableCache() {}
|
||||
TableCache::~TableCache() = default;
|
||||
|
||||
Status TableCache::GetTableReader(
|
||||
const ReadOptions& ro, const FileOptions& file_options,
|
||||
|
||||
@@ -98,8 +98,13 @@ class UserKeyTablePropertiesCollectorFactory
|
||||
TablePropertiesCollectorFactory::Context context;
|
||||
context.column_family_id = column_family_id;
|
||||
context.level_at_creation = level_at_creation;
|
||||
return new UserKeyTablePropertiesCollector(
|
||||
user_collector_factory_->CreateTablePropertiesCollector(context));
|
||||
TablePropertiesCollector* collector =
|
||||
user_collector_factory_->CreateTablePropertiesCollector(context);
|
||||
if (collector) {
|
||||
return new UserKeyTablePropertiesCollector(collector);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
|
||||
@@ -180,7 +180,6 @@ class RegularKeysStartWithAInternal : public IntTblPropCollector {
|
||||
uint64_t /* block_compressed_bytes_fast */,
|
||||
uint64_t /* block_compressed_bytes_slow */) override {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
UserCollectedProperties GetReadableProperties() const override {
|
||||
@@ -227,7 +226,7 @@ class FlushBlockEveryThreePolicy : public FlushBlockPolicy {
|
||||
|
||||
class FlushBlockEveryThreePolicyFactory : public FlushBlockPolicyFactory {
|
||||
public:
|
||||
explicit FlushBlockEveryThreePolicyFactory() {}
|
||||
explicit FlushBlockEveryThreePolicyFactory() = default;
|
||||
|
||||
const char* Name() const override {
|
||||
return "FlushBlockEveryThreePolicyFactory";
|
||||
|
||||
@@ -484,7 +484,6 @@ void VersionEdit::EncodeFileBoundaries(std::string* dst,
|
||||
StripTimestampFromInternalKey(&largest_buf, meta.largest.Encode(), ts_sz);
|
||||
PutLengthPrefixedSlice(dst, smallest_buf);
|
||||
PutLengthPrefixedSlice(dst, largest_buf);
|
||||
return;
|
||||
};
|
||||
|
||||
Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
|
||||
@@ -562,7 +562,9 @@ TEST_F(VersionEditTest, AddWalDebug) {
|
||||
std::stringstream ss;
|
||||
ss << "{\"LogNumber\": " << kLogNumbers[i] << ", "
|
||||
<< "\"SyncedSizeInBytes\": " << kSizeInBytes[i] << "}";
|
||||
if (i < n - 1) ss << ", ";
|
||||
if (i < n - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
expected_json += ss.str();
|
||||
}
|
||||
expected_json += "], \"ColumnFamily\": 0}";
|
||||
|
||||
+23
-18
@@ -122,7 +122,9 @@ Status OverlapWithIterator(const Comparator* ucmp,
|
||||
ParsedInternalKey seek_result;
|
||||
Status s = ParseInternalKey(iter->key(), &seek_result,
|
||||
false /* log_err_key */); // TODO
|
||||
if (!s.ok()) return s;
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (ucmp->CompareWithoutTimestamp(seek_result.user_key, largest_user_key) <=
|
||||
0) {
|
||||
@@ -835,9 +837,9 @@ Version::~Version() {
|
||||
assert(cfd_ != nullptr);
|
||||
uint32_t path_id = f->fd.GetPathId();
|
||||
assert(path_id < cfd_->ioptions()->cf_paths.size());
|
||||
vset_->obsolete_files_.push_back(
|
||||
ObsoleteFileInfo(f, cfd_->ioptions()->cf_paths[path_id].path,
|
||||
cfd_->GetFileMetadataCacheReservationManager()));
|
||||
vset_->obsolete_files_.emplace_back(
|
||||
f, cfd_->ioptions()->cf_paths[path_id].path,
|
||||
cfd_->GetFileMetadataCacheReservationManager());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2534,8 +2536,8 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
*status = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, user_key, MergeHelper::kNoBaseValue,
|
||||
merge_context->GetOperands(), info_log_, db_statistics_, clock_,
|
||||
/* update_num_ops_stats */ true, value ? value->GetSelf() : nullptr,
|
||||
columns, /* op_failure_scope */ nullptr);
|
||||
/* update_num_ops_stats */ true, /* op_failure_scope */ nullptr,
|
||||
value ? value->GetSelf() : nullptr, columns);
|
||||
if (status->ok()) {
|
||||
if (LIKELY(value != nullptr)) {
|
||||
value->PinSelf();
|
||||
@@ -2780,9 +2782,8 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
*status = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, user_key, MergeHelper::kNoBaseValue,
|
||||
iter->merge_context.GetOperands(), info_log_, db_statistics_, clock_,
|
||||
/* update_num_ops_stats */ true,
|
||||
iter->value ? iter->value->GetSelf() : nullptr, iter->columns,
|
||||
/* op_failure_scope */ nullptr);
|
||||
/* update_num_ops_stats */ true, /* op_failure_scope */ nullptr,
|
||||
iter->value ? iter->value->GetSelf() : nullptr, iter->columns);
|
||||
if (LIKELY(iter->value != nullptr)) {
|
||||
iter->value->PinSelf();
|
||||
range->AddValueSize(iter->value->size());
|
||||
@@ -3066,9 +3067,7 @@ void VersionStorageInfo::PrepareForVersionAppend(
|
||||
GenerateFileIndexer();
|
||||
GenerateLevelFilesBrief();
|
||||
GenerateLevel0NonOverlapping();
|
||||
if (!immutable_options.allow_ingest_behind) {
|
||||
GenerateBottommostFiles();
|
||||
}
|
||||
GenerateBottommostFiles();
|
||||
GenerateFileLocationIndex();
|
||||
}
|
||||
|
||||
@@ -3101,7 +3100,9 @@ bool Version::MaybeInitializeFileMetaData(const ReadOptions& read_options,
|
||||
file_meta->fd.GetNumber(), s.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
if (tp.get() == nullptr) return false;
|
||||
if (tp.get() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
file_meta->num_entries = tp->num_entries;
|
||||
file_meta->num_deletions = tp->num_deletions;
|
||||
file_meta->raw_value_size = tp->raw_value_size;
|
||||
@@ -4515,7 +4516,9 @@ const char* VersionStorageInfo::LevelSummary(
|
||||
for (int i = 0; i < num_levels(); i++) {
|
||||
int sz = sizeof(scratch->buffer) - len;
|
||||
int ret = snprintf(scratch->buffer + len, sz, "%d ", int(files_[i].size()));
|
||||
if (ret < 0 || ret >= sz) break;
|
||||
if (ret < 0 || ret >= sz) {
|
||||
break;
|
||||
}
|
||||
len += ret;
|
||||
}
|
||||
if (len > 0) {
|
||||
@@ -4545,7 +4548,9 @@ const char* VersionStorageInfo::LevelFileSummary(FileSummaryStorage* scratch,
|
||||
"#%" PRIu64 "(seq=%" PRIu64 ",sz=%s,%d) ",
|
||||
f->fd.GetNumber(), f->fd.smallest_seqno, sztxt,
|
||||
static_cast<int>(f->being_compacted));
|
||||
if (ret < 0 || ret >= sz) break;
|
||||
if (ret < 0 || ret >= sz) {
|
||||
break;
|
||||
}
|
||||
len += ret;
|
||||
}
|
||||
// overwrite the last space (only if files_[level].size() is non-zero)
|
||||
@@ -5384,9 +5389,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
for (const auto* cfd : *column_family_set_) {
|
||||
assert(curr_state.find(cfd->GetID()) == curr_state.end());
|
||||
curr_state.emplace(std::make_pair(
|
||||
curr_state.emplace(
|
||||
cfd->GetID(),
|
||||
MutableCFState(cfd->GetLogNumber(), cfd->GetFullHistoryTsLow())));
|
||||
MutableCFState(cfd->GetLogNumber(), cfd->GetFullHistoryTsLow()));
|
||||
}
|
||||
|
||||
for (const auto& wal : wals_.GetWals()) {
|
||||
@@ -7277,7 +7282,7 @@ ReactiveVersionSet::ReactiveVersionSet(
|
||||
/*db_session_id*/ "", /*daily_offpeak_time_utc*/ "",
|
||||
/*error_handler=*/nullptr) {}
|
||||
|
||||
ReactiveVersionSet::~ReactiveVersionSet() {}
|
||||
ReactiveVersionSet::~ReactiveVersionSet() = default;
|
||||
|
||||
Status ReactiveVersionSet::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
|
||||
@@ -490,6 +490,12 @@ class VersionStorageInfo {
|
||||
files_marked_for_periodic_compaction_.emplace_back(level, f);
|
||||
}
|
||||
|
||||
// REQUIRES: PrepareForVersionAppend has been called
|
||||
const autovector<std::pair<int, FileMetaData*>>& BottommostFiles() const {
|
||||
assert(finalized_);
|
||||
return bottommost_files_;
|
||||
}
|
||||
|
||||
// REQUIRES: ComputeCompactionScore has been called
|
||||
// REQUIRES: DB mutex held during access
|
||||
const autovector<std::pair<int, FileMetaData*>>&
|
||||
|
||||
+54
-5
@@ -34,7 +34,7 @@ class GenerateLevelFilesBriefTest : public testing::Test {
|
||||
LevelFilesBrief file_level_;
|
||||
Arena arena_;
|
||||
|
||||
GenerateLevelFilesBriefTest() {}
|
||||
GenerateLevelFilesBriefTest() = default;
|
||||
|
||||
~GenerateLevelFilesBriefTest() override {
|
||||
for (size_t i = 0; i < files_.size(); i++) {
|
||||
@@ -213,7 +213,7 @@ class VersionStorageInfoTest : public VersionStorageInfoTestBase {
|
||||
public:
|
||||
VersionStorageInfoTest() : VersionStorageInfoTestBase(BytewiseComparator()) {}
|
||||
|
||||
~VersionStorageInfoTest() override {}
|
||||
~VersionStorageInfoTest() override = default;
|
||||
};
|
||||
|
||||
TEST_F(VersionStorageInfoTest, MaxBytesForLevelStatic) {
|
||||
@@ -520,6 +520,55 @@ TEST_F(VersionStorageInfoTest, EstimateLiveDataSize2) {
|
||||
ASSERT_EQ(4U, vstorage_.EstimateLiveDataSize());
|
||||
}
|
||||
|
||||
TEST_F(VersionStorageInfoTest, SingleLevelBottommostData) {
|
||||
// In case of a single level, the oldest L0 file is bottommost. This could be
|
||||
// improved in case the L0 files cover disjoint key-ranges.
|
||||
Add(0 /* level */, 1U /* file_number */, "A" /* smallest */,
|
||||
"Z" /* largest */, 1U /* file_size */);
|
||||
Add(0 /* level */, 2U /* file_number */, "A" /* smallest */,
|
||||
"Z" /* largest */, 1U /* file_size */);
|
||||
Add(0 /* level */, 3U /* file_number */, "0" /* smallest */,
|
||||
"9" /* largest */, 1U /* file_size */);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(1, vstorage_.BottommostFiles().size());
|
||||
ASSERT_EQ(0, vstorage_.BottommostFiles()[0].first);
|
||||
ASSERT_EQ(3U, vstorage_.BottommostFiles()[0].second->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(VersionStorageInfoTest, MultiLevelBottommostData) {
|
||||
// In case of multiple levels, the oldest file for a key-range from each L1+
|
||||
// level is bottommost. This could be improved in case an L0 file contains the
|
||||
// oldest data for some range of keys.
|
||||
Add(0 /* level */, 1U /* file_number */, "A" /* smallest */,
|
||||
"Z" /* largest */, 1U /* file_size */);
|
||||
Add(0 /* level */, 2U /* file_number */, "0" /* smallest */,
|
||||
"9" /* largest */, 1U /* file_size */);
|
||||
Add(1 /* level */, 3U /* file_number */, "A" /* smallest */,
|
||||
"D" /* largest */, 1U /* file_size */);
|
||||
Add(2 /* level */, 4U /* file_number */, "E" /* smallest */,
|
||||
"H" /* largest */, 1U /* file_size */);
|
||||
Add(2 /* level */, 5U /* file_number */, "I" /* smallest */,
|
||||
"L" /* largest */, 1U /* file_size */);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
autovector<std::pair<int, FileMetaData*>> bottommost_files =
|
||||
vstorage_.BottommostFiles();
|
||||
std::sort(bottommost_files.begin(), bottommost_files.end(),
|
||||
[](const std::pair<int, FileMetaData*>& lhs,
|
||||
const std::pair<int, FileMetaData*>& rhs) {
|
||||
assert(lhs.second);
|
||||
assert(rhs.second);
|
||||
return lhs.second->fd.GetNumber() < rhs.second->fd.GetNumber();
|
||||
});
|
||||
ASSERT_EQ(3, bottommost_files.size());
|
||||
ASSERT_EQ(3U, bottommost_files[0].second->fd.GetNumber());
|
||||
ASSERT_EQ(4U, bottommost_files[1].second->fd.GetNumber());
|
||||
ASSERT_EQ(5U, bottommost_files[2].second->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(VersionStorageInfoTest, GetOverlappingInputs) {
|
||||
// Two files that overlap at the range deletion tombstone sentinel.
|
||||
Add(1, 1U, {"a", 0, kTypeValue},
|
||||
@@ -928,7 +977,7 @@ class VersionStorageInfoTimestampTest : public VersionStorageInfoTestBase {
|
||||
VersionStorageInfoTimestampTest()
|
||||
: VersionStorageInfoTestBase(test::BytewiseComparatorWithU64TsWrapper()) {
|
||||
}
|
||||
~VersionStorageInfoTimestampTest() override {}
|
||||
~VersionStorageInfoTimestampTest() override = default;
|
||||
std::string Timestamp(uint64_t ts) const {
|
||||
std::string ret;
|
||||
PutFixed64(&ret, ts);
|
||||
@@ -982,7 +1031,7 @@ class FindLevelFileTest : public testing::Test {
|
||||
|
||||
FindLevelFileTest() : disjoint_sorted_files_(true) {}
|
||||
|
||||
~FindLevelFileTest() override {}
|
||||
~FindLevelFileTest() override = default;
|
||||
|
||||
void LevelFileInit(size_t num = 0) {
|
||||
char* mem = arena_.AllocateAligned(num * sizeof(FdWithKeyRange));
|
||||
@@ -3662,7 +3711,7 @@ class ChargeFileMetadataTestWithParam
|
||||
: public ChargeFileMetadataTest,
|
||||
public testing::WithParamInterface<CacheEntryRoleOptions::Decision> {
|
||||
public:
|
||||
ChargeFileMetadataTestWithParam() {}
|
||||
ChargeFileMetadataTestWithParam() = default;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
|
||||
@@ -94,7 +94,7 @@ TEST_F(DBWideBasicTest, PutEntity) {
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
&keys[0], &values[0], &statuses[0]);
|
||||
keys.data(), values.data(), statuses.data());
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], first_value_of_default_column);
|
||||
@@ -114,7 +114,7 @@ TEST_F(DBWideBasicTest, PutEntity) {
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGetEntity(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
&keys[0], &results[0], &statuses[0]);
|
||||
keys.data(), results.data(), statuses.data());
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(results[0].columns(), first_columns);
|
||||
@@ -398,8 +398,8 @@ TEST_F(DBWideBasicTest, MultiCFMultiGetEntity) {
|
||||
std::array<PinnableWideColumns, num_keys> results;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGetEntity(ReadOptions(), num_keys, &column_families[0], &keys[0],
|
||||
&results[0], &statuses[0]);
|
||||
db_->MultiGetEntity(ReadOptions(), num_keys, column_families.data(),
|
||||
keys.data(), results.data(), statuses.data());
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(results[0].columns(), first_columns);
|
||||
@@ -642,7 +642,7 @@ TEST_F(DBWideBasicTest, MergePlainKeyValue) {
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGetEntity(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
&keys[0], &results[0], &statuses[0]);
|
||||
keys.data(), results.data(), statuses.data());
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(results[0].columns(), expected_first_columns);
|
||||
@@ -822,7 +822,7 @@ TEST_F(DBWideBasicTest, MergeEntity) {
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
&keys[0], &values[0], &statuses[0]);
|
||||
keys.data(), values.data(), statuses.data());
|
||||
|
||||
ASSERT_EQ(values[0], first_expected_default);
|
||||
ASSERT_OK(statuses[0]);
|
||||
@@ -839,7 +839,7 @@ TEST_F(DBWideBasicTest, MergeEntity) {
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGetEntity(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
&keys[0], &results[0], &statuses[0]);
|
||||
keys.data(), results.data(), statuses.data());
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(results[0].columns(), first_expected_columns);
|
||||
@@ -900,7 +900,7 @@ TEST_F(DBWideBasicTest, MergeEntity) {
|
||||
int number_of_operands = 0;
|
||||
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
first_key, &merge_operands[0],
|
||||
first_key, merge_operands.data(),
|
||||
&get_merge_opts, &number_of_operands));
|
||||
|
||||
ASSERT_EQ(number_of_operands, num_merge_operands);
|
||||
@@ -913,7 +913,7 @@ TEST_F(DBWideBasicTest, MergeEntity) {
|
||||
int number_of_operands = 0;
|
||||
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
second_key, &merge_operands[0],
|
||||
second_key, merge_operands.data(),
|
||||
&get_merge_opts, &number_of_operands));
|
||||
|
||||
ASSERT_EQ(number_of_operands, num_merge_operands);
|
||||
@@ -933,7 +933,7 @@ TEST_F(DBWideBasicTest, MergeEntity) {
|
||||
int number_of_operands = 0;
|
||||
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
first_key, &merge_operands[0],
|
||||
first_key, merge_operands.data(),
|
||||
&get_merge_opts, &number_of_operands));
|
||||
|
||||
ASSERT_EQ(number_of_operands, num_merge_operands);
|
||||
@@ -945,7 +945,7 @@ TEST_F(DBWideBasicTest, MergeEntity) {
|
||||
int number_of_operands = 0;
|
||||
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
second_key, &merge_operands[0],
|
||||
second_key, merge_operands.data(),
|
||||
&get_merge_opts, &number_of_operands));
|
||||
|
||||
ASSERT_EQ(number_of_operands, num_merge_operands);
|
||||
|
||||
+11
-11
@@ -233,9 +233,9 @@ WriteBatch& WriteBatch::operator=(WriteBatch&& src) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
WriteBatch::~WriteBatch() {}
|
||||
WriteBatch::~WriteBatch() = default;
|
||||
|
||||
WriteBatch::Handler::~Handler() {}
|
||||
WriteBatch::Handler::~Handler() = default;
|
||||
|
||||
void WriteBatch::Handler::LogData(const Slice& /*blob*/) {
|
||||
// If the user has not specified something to do with blobs, then we ignore
|
||||
@@ -741,7 +741,7 @@ SequenceNumber WriteBatchInternal::Sequence(const WriteBatch* b) {
|
||||
}
|
||||
|
||||
void WriteBatchInternal::SetSequence(WriteBatch* b, SequenceNumber seq) {
|
||||
EncodeFixed64(&b->rep_[0], seq);
|
||||
EncodeFixed64(b->rep_.data(), seq);
|
||||
}
|
||||
|
||||
size_t WriteBatchInternal::GetFirstOffset(WriteBatch* /*b*/) {
|
||||
@@ -1856,7 +1856,9 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
}
|
||||
|
||||
void DecrementProtectionInfoIdxForTryAgain() {
|
||||
if (prot_info_ != nullptr) --prot_info_idx_;
|
||||
if (prot_info_ != nullptr) {
|
||||
--prot_info_idx_;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetProtectionInfo() {
|
||||
@@ -2543,9 +2545,8 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
WideColumnsHelper::GetDefaultColumn(columns), {value},
|
||||
moptions->info_log, moptions->statistics,
|
||||
SystemClock::Default().get(),
|
||||
/* update_num_ops_stats */ false, &new_value,
|
||||
/* result_operand */ nullptr, &new_value_type,
|
||||
/* op_failure_scope */ nullptr);
|
||||
/* update_num_ops_stats */ false, /* op_failure_scope */ nullptr,
|
||||
&new_value, /* result_operand */ nullptr, &new_value_type);
|
||||
} else {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
@@ -2554,9 +2555,8 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
merge_operator, key, MergeHelper::kWideBaseValue, columns,
|
||||
{value}, moptions->info_log, moptions->statistics,
|
||||
SystemClock::Default().get(),
|
||||
/* update_num_ops_stats */ false, &new_value,
|
||||
/* result_operand */ nullptr, &new_value_type,
|
||||
/* op_failure_scope */ nullptr);
|
||||
/* update_num_ops_stats */ false, /* op_failure_scope */ nullptr,
|
||||
&new_value, /* result_operand */ nullptr, &new_value_type);
|
||||
}
|
||||
|
||||
if (!merge_status.ok()) {
|
||||
@@ -3018,7 +3018,7 @@ class ProtectionInfoUpdater : public WriteBatch::Handler {
|
||||
explicit ProtectionInfoUpdater(WriteBatch::ProtectionInfo* prot_info)
|
||||
: prot_info_(prot_info) {}
|
||||
|
||||
~ProtectionInfoUpdater() override {}
|
||||
~ProtectionInfoUpdater() override = default;
|
||||
|
||||
Status PutCF(uint32_t cf, const Slice& key, const Slice& val) override {
|
||||
return UpdateProtInfo(cf, key, val, kTypeValue);
|
||||
|
||||
@@ -64,7 +64,7 @@ class MockWriteCallback : public WriteCallback {
|
||||
bool allow_batching_ = false;
|
||||
std::atomic<bool> was_called_{false};
|
||||
|
||||
MockWriteCallback() {}
|
||||
MockWriteCallback() = default;
|
||||
|
||||
MockWriteCallback(const MockWriteCallback& other) {
|
||||
should_fail_ = other.should_fail_;
|
||||
@@ -111,7 +111,7 @@ TEST_P(WriteCallbackPTest, WriteWithCallbackTest) {
|
||||
WriteOP(bool should_fail = false) { callback_.should_fail_ = should_fail; }
|
||||
|
||||
void Put(const string& key, const string& val) {
|
||||
kvs_.push_back(std::make_pair(key, val));
|
||||
kvs_.emplace_back(key, val);
|
||||
ASSERT_OK(write_batch_.Put(key, val));
|
||||
}
|
||||
|
||||
@@ -176,8 +176,7 @@ TEST_P(WriteCallbackPTest, WriteWithCallbackTest) {
|
||||
DBOptions db_options(options);
|
||||
ColumnFamilyOptions cf_options(options);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options));
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options);
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
auto open_s = DBImpl::Open(db_options, dbname, column_families, &handles,
|
||||
&db, seq_per_batch_, true /* batch_per_txn */);
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class BatchedOpsStressTest : public StressTest {
|
||||
public:
|
||||
BatchedOpsStressTest() {}
|
||||
virtual ~BatchedOpsStressTest() {}
|
||||
BatchedOpsStressTest() = default;
|
||||
virtual ~BatchedOpsStressTest() = default;
|
||||
|
||||
bool IsStateTracked() const override { return false; }
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class CfConsistencyStressTest : public StressTest {
|
||||
public:
|
||||
CfConsistencyStressTest() : batch_id_(0) {}
|
||||
|
||||
~CfConsistencyStressTest() override {}
|
||||
~CfConsistencyStressTest() override = default;
|
||||
|
||||
bool IsStateTracked() const override { return false; }
|
||||
|
||||
@@ -232,7 +232,7 @@ class CfConsistencyStressTest : public StressTest {
|
||||
}
|
||||
db_->MultiGet(readoptionscopy, cfh, num_keys, keys.data(), values.data(),
|
||||
statuses.data());
|
||||
for (auto s : statuses) {
|
||||
for (const auto& s : statuses) {
|
||||
if (s.ok()) {
|
||||
// found case
|
||||
thread->stats.AddGets(1, 1);
|
||||
|
||||
@@ -210,9 +210,6 @@ void CompressedCacheSetCapacityThread(void* v) {
|
||||
(double)thread->rand.Uniform(
|
||||
FLAGS_compressed_secondary_cache_ratio * 100) /
|
||||
100;
|
||||
if (new_comp_cache_ratio == 0.0) {
|
||||
new_comp_cache_ratio = 0.05;
|
||||
}
|
||||
fprintf(stderr, "New comp cache ratio = %f\n", new_comp_cache_ratio);
|
||||
|
||||
s = UpdateTieredCache(block_cache, /*capacity*/ -1,
|
||||
|
||||
@@ -313,6 +313,7 @@ DECLARE_uint32(memtable_protection_bytes_per_key);
|
||||
DECLARE_uint32(block_protection_bytes_per_key);
|
||||
|
||||
DECLARE_uint64(user_timestamp_size);
|
||||
DECLARE_bool(persist_user_defined_timestamps);
|
||||
DECLARE_string(secondary_cache_uri);
|
||||
DECLARE_int32(secondary_cache_fault_one_in);
|
||||
|
||||
|
||||
@@ -1025,6 +1025,10 @@ DEFINE_uint64(user_timestamp_size, 0,
|
||||
"Number of bytes for a user-defined timestamp. Currently, only "
|
||||
"8-byte is supported");
|
||||
|
||||
DEFINE_bool(persist_user_defined_timestamps, true,
|
||||
"Flag to indicate whether user-defined timestamps will be persisted"
|
||||
" during Flush");
|
||||
|
||||
DEFINE_int32(open_metadata_write_fault_one_in, 0,
|
||||
"On non-zero, enables fault injection on file metadata write "
|
||||
"during DB reopen.");
|
||||
|
||||
@@ -67,7 +67,7 @@ UniqueIdVerifier::UniqueIdVerifier(const std::string& db_name, Env* env)
|
||||
std::string id(24U, '\0');
|
||||
Slice result;
|
||||
for (;;) {
|
||||
s = reader->Read(id.size(), opts, &result, &id[0], /*dbg*/ nullptr);
|
||||
s = reader->Read(id.size(), opts, &result, id.data(), /*dbg*/ nullptr);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Error reading unique id file: %s\n",
|
||||
s.ToString().c_str());
|
||||
|
||||
@@ -430,6 +430,13 @@ Status StressTest::AssertSame(DB* db, ColumnFamilyHandle* cf,
|
||||
PinnableSlice v;
|
||||
s = db->Get(ropt, cf, snap_state.key, &v);
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
// When `persist_user_defined_timestamps` is false, a repeated read with
|
||||
// both a read timestamp and an explicitly taken snapshot cannot guarantee
|
||||
// consistent result all the time. When it cannot return consistent result,
|
||||
// it will return an `InvalidArgument` status.
|
||||
if (s.IsInvalidArgument() && !FLAGS_persist_user_defined_timestamps) {
|
||||
return Status::OK();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (snap_state.status != s) {
|
||||
@@ -1784,7 +1791,7 @@ Status StressTest::TestBackupRestore(
|
||||
// `ListColumnFamilies` to get names because it won't necessarily give
|
||||
// the same order as `column_family_names_`.
|
||||
assert(FLAGS_clear_column_family_one_in == 0);
|
||||
for (auto name : column_family_names_) {
|
||||
for (const auto& name : column_family_names_) {
|
||||
cf_descriptors.emplace_back(name, ColumnFamilyOptions(restore_options));
|
||||
}
|
||||
if (inplace_not_restore) {
|
||||
@@ -2668,6 +2675,8 @@ void StressTest::PrintEnv() const {
|
||||
static_cast<int>(FLAGS_fail_if_options_file_error));
|
||||
fprintf(stdout, "User timestamp size bytes : %d\n",
|
||||
static_cast<int>(FLAGS_user_timestamp_size));
|
||||
fprintf(stdout, "Persist user defined timestamps : %d\n",
|
||||
FLAGS_persist_user_defined_timestamps);
|
||||
fprintf(stdout, "WAL compression : %s\n",
|
||||
FLAGS_wal_compression.c_str());
|
||||
fprintf(stdout, "Try verify sst unique id : %d\n",
|
||||
@@ -2762,12 +2771,12 @@ void StressTest::Open(SharedState* shared, bool reopen) {
|
||||
if (sorted_cfn != existing_column_families) {
|
||||
fprintf(stderr, "Expected column families differ from the existing:\n");
|
||||
fprintf(stderr, "Expected: {");
|
||||
for (auto cf : sorted_cfn) {
|
||||
for (const auto& cf : sorted_cfn) {
|
||||
fprintf(stderr, "%s ", cf.c_str());
|
||||
}
|
||||
fprintf(stderr, "}\n");
|
||||
fprintf(stderr, "Existing: {");
|
||||
for (auto cf : existing_column_families) {
|
||||
for (const auto& cf : existing_column_families) {
|
||||
fprintf(stderr, "%s ", cf.c_str());
|
||||
}
|
||||
fprintf(stderr, "}\n");
|
||||
@@ -2775,7 +2784,7 @@ void StressTest::Open(SharedState* shared, bool reopen) {
|
||||
assert(sorted_cfn == existing_column_families);
|
||||
}
|
||||
std::vector<ColumnFamilyDescriptor> cf_descriptors;
|
||||
for (auto name : column_family_names_) {
|
||||
for (const auto& name : column_family_names_) {
|
||||
if (name != kDefaultColumnFamilyName) {
|
||||
new_column_family_name_ =
|
||||
std::max(new_column_family_name_.load(), std::stoi(name) + 1);
|
||||
@@ -3087,6 +3096,11 @@ bool StressTest::MaybeUseOlderTimestampForPointLookup(ThreadState* thread,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FLAGS_persist_user_defined_timestamps) {
|
||||
// Not read with older timestamps to avoid get InvalidArgument.
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(thread);
|
||||
if (!thread->rand.OneInOpt(3)) {
|
||||
return false;
|
||||
@@ -3116,6 +3130,11 @@ void StressTest::MaybeUseOlderTimestampForRangeScan(ThreadState* thread,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FLAGS_persist_user_defined_timestamps) {
|
||||
// Not read with older timestamps to avoid get InvalidArgument.
|
||||
return;
|
||||
}
|
||||
|
||||
assert(thread);
|
||||
if (!thread->rand.OneInOpt(3)) {
|
||||
return;
|
||||
@@ -3175,6 +3194,8 @@ void CheckAndSetOptionsForUserTimestamp(Options& options) {
|
||||
exit(1);
|
||||
}
|
||||
options.comparator = cmp;
|
||||
options.persist_user_defined_timestamps =
|
||||
FLAGS_persist_user_defined_timestamps;
|
||||
}
|
||||
|
||||
bool InitializeOptionsFromFile(Options& options) {
|
||||
|
||||
@@ -185,7 +185,7 @@ ExpectedStateManager::ExpectedStateManager(size_t max_key,
|
||||
num_column_families_(num_column_families),
|
||||
latest_(nullptr) {}
|
||||
|
||||
ExpectedStateManager::~ExpectedStateManager() {}
|
||||
ExpectedStateManager::~ExpectedStateManager() = default;
|
||||
|
||||
const std::string FileExpectedStateManager::kLatestBasename = "LATEST";
|
||||
const std::string FileExpectedStateManager::kStateFilenameSuffix = ".state";
|
||||
|
||||
@@ -150,7 +150,7 @@ std::string MultiOpsTxnsStressTest::Record::EncodePrimaryKey(uint32_t a) {
|
||||
PutFixed32(&ret, kPrimaryIndexId);
|
||||
PutFixed32(&ret, a);
|
||||
|
||||
char* const buf = &ret[0];
|
||||
char* const buf = ret.data();
|
||||
std::reverse(buf, buf + sizeof(kPrimaryIndexId));
|
||||
std::reverse(buf + sizeof(kPrimaryIndexId),
|
||||
buf + sizeof(kPrimaryIndexId) + sizeof(a));
|
||||
@@ -162,7 +162,7 @@ std::string MultiOpsTxnsStressTest::Record::EncodeSecondaryKey(uint32_t c) {
|
||||
PutFixed32(&ret, kSecondaryIndexId);
|
||||
PutFixed32(&ret, c);
|
||||
|
||||
char* const buf = &ret[0];
|
||||
char* const buf = ret.data();
|
||||
std::reverse(buf, buf + sizeof(kSecondaryIndexId));
|
||||
std::reverse(buf + sizeof(kSecondaryIndexId),
|
||||
buf + sizeof(kSecondaryIndexId) + sizeof(c));
|
||||
@@ -176,7 +176,7 @@ std::string MultiOpsTxnsStressTest::Record::EncodeSecondaryKey(uint32_t c,
|
||||
PutFixed32(&ret, c);
|
||||
PutFixed32(&ret, a);
|
||||
|
||||
char* const buf = &ret[0];
|
||||
char* const buf = ret.data();
|
||||
std::reverse(buf, buf + sizeof(kSecondaryIndexId));
|
||||
std::reverse(buf + sizeof(kSecondaryIndexId),
|
||||
buf + sizeof(kSecondaryIndexId) + sizeof(c));
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class NonBatchedOpsStressTest : public StressTest {
|
||||
public:
|
||||
NonBatchedOpsStressTest() {}
|
||||
NonBatchedOpsStressTest() = default;
|
||||
|
||||
virtual ~NonBatchedOpsStressTest() {}
|
||||
virtual ~NonBatchedOpsStressTest() = default;
|
||||
|
||||
void VerifyDb(ThreadState* thread) const override {
|
||||
// This `ReadOptions` is for validation purposes. Ignore
|
||||
@@ -624,7 +624,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
if (!shared->AllowsOverwrite(rand_key) &&
|
||||
shared->Exists(column_family, rand_key)) {
|
||||
// Just do read your write checks for keys that allow overwrites.
|
||||
ryw_expected_values.push_back(std::nullopt);
|
||||
ryw_expected_values.emplace_back(std::nullopt);
|
||||
continue;
|
||||
}
|
||||
// With a 1 in 10 probability, insert the just added key in the batch
|
||||
@@ -667,7 +667,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
thread->shared->SafeTerminate();
|
||||
}
|
||||
} else {
|
||||
ryw_expected_values.push_back(std::nullopt);
|
||||
ryw_expected_values.emplace_back(std::nullopt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -504,7 +504,7 @@ EnvWrapper::EnvWrapper(const std::shared_ptr<Env>& t) : target_(t) {
|
||||
RegisterOptions("", &target_, &env_wrapper_type_info);
|
||||
}
|
||||
|
||||
EnvWrapper::~EnvWrapper() {}
|
||||
EnvWrapper::~EnvWrapper() = default;
|
||||
|
||||
Status EnvWrapper::PrepareOptions(const ConfigOptions& options) {
|
||||
target_.Prepare();
|
||||
|
||||
Vendored
+9
-9
@@ -359,7 +359,7 @@ class LegacyFileSystemWrapper : public FileSystem {
|
||||
public:
|
||||
// Initialize an EnvWrapper that delegates all calls to *t
|
||||
explicit LegacyFileSystemWrapper(Env* t) : target_(t) {}
|
||||
~LegacyFileSystemWrapper() override {}
|
||||
~LegacyFileSystemWrapper() override = default;
|
||||
|
||||
static const char* kClassName() { return "LegacyFileSystem"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
@@ -624,7 +624,7 @@ Env::Env(const std::shared_ptr<FileSystem>& fs,
|
||||
const std::shared_ptr<SystemClock>& clock)
|
||||
: thread_status_updater_(nullptr), file_system_(fs), system_clock_(clock) {}
|
||||
|
||||
Env::~Env() {}
|
||||
Env::~Env() = default;
|
||||
|
||||
Status Env::NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) {
|
||||
@@ -797,7 +797,7 @@ std::string Env::GenerateUniqueId() {
|
||||
|
||||
// Use 36 character format of RFC 4122
|
||||
result.resize(36U);
|
||||
char* buf = &result[0];
|
||||
char* buf = result.data();
|
||||
PutBaseChars<16>(&buf, 8, upper >> 32, /*!uppercase*/ false);
|
||||
*(buf++) = '-';
|
||||
PutBaseChars<16>(&buf, 4, upper >> 16, /*!uppercase*/ false);
|
||||
@@ -817,15 +817,15 @@ std::string Env::GenerateUniqueId() {
|
||||
return result;
|
||||
}
|
||||
|
||||
SequentialFile::~SequentialFile() {}
|
||||
SequentialFile::~SequentialFile() = default;
|
||||
|
||||
RandomAccessFile::~RandomAccessFile() {}
|
||||
RandomAccessFile::~RandomAccessFile() = default;
|
||||
|
||||
WritableFile::~WritableFile() {}
|
||||
WritableFile::~WritableFile() = default;
|
||||
|
||||
MemoryMappedFileBuffer::~MemoryMappedFileBuffer() {}
|
||||
MemoryMappedFileBuffer::~MemoryMappedFileBuffer() = default;
|
||||
|
||||
Logger::~Logger() {}
|
||||
Logger::~Logger() = default;
|
||||
|
||||
Status Logger::Close() {
|
||||
if (!closed_) {
|
||||
@@ -838,7 +838,7 @@ Status Logger::Close() {
|
||||
|
||||
Status Logger::CloseImpl() { return Status::NotSupported(); }
|
||||
|
||||
FileLock::~FileLock() {}
|
||||
FileLock::~FileLock() = default;
|
||||
|
||||
void LogFlush(Logger* info_log) {
|
||||
if (info_log) {
|
||||
|
||||
Vendored
+1
-1
@@ -342,7 +342,7 @@ TEST_P(EnvMoreTestWithParam, GetChildren) {
|
||||
ASSERT_OK(env_->GetChildrenFileAttributes(test_dir_, &childAttr));
|
||||
ASSERT_EQ(3U, children.size());
|
||||
ASSERT_EQ(3U, childAttr.size());
|
||||
for (auto each : children) {
|
||||
for (const auto& each : children) {
|
||||
env_->DeleteDir(test_dir_ + "/" + each).PermitUncheckedError();
|
||||
} // necessary for default POSIX env
|
||||
|
||||
|
||||
Vendored
+3
-2
@@ -7,10 +7,11 @@
|
||||
|
||||
#include "env/env_chroot.h"
|
||||
|
||||
#include <errno.h> // errno
|
||||
#include <stdlib.h> // realpath, free
|
||||
#include <unistd.h> // geteuid
|
||||
|
||||
#include <cerrno> // errno
|
||||
#include <cstdlib> // realpath, free
|
||||
|
||||
#include "env/composite_env_wrapper.h"
|
||||
#include "env/fs_remap.h"
|
||||
#include "rocksdb/utilities/options_type.h"
|
||||
|
||||
Vendored
+2
-2
@@ -827,7 +827,7 @@ Status BlockAccessCipherStream::Encrypt(uint64_t fileOffset, char* data,
|
||||
AllocateScratch(scratch);
|
||||
|
||||
// Encrypt individual blocks.
|
||||
while (1) {
|
||||
while (true) {
|
||||
char* block = data;
|
||||
size_t n = std::min(dataSize, blockSize - blockOffset);
|
||||
if (n != blockSize) {
|
||||
@@ -871,7 +871,7 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char* data,
|
||||
AllocateScratch(scratch);
|
||||
|
||||
// Decrypt individual blocks.
|
||||
while (1) {
|
||||
while (true) {
|
||||
char* block = data;
|
||||
size_t n = std::min(dataSize, blockSize - blockOffset);
|
||||
if (n != blockSize) {
|
||||
|
||||
Vendored
+9
-7
@@ -14,19 +14,21 @@
|
||||
#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
#if defined(ROCKSDB_IOURING_PRESENT)
|
||||
#include <liburing.h>
|
||||
#endif
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#if defined(OS_LINUX) || defined(OS_SOLARIS) || defined(OS_ANDROID)
|
||||
#include <sys/statfs.h>
|
||||
#endif
|
||||
@@ -36,10 +38,10 @@
|
||||
#if defined(ROCKSDB_IOURING_PRESENT)
|
||||
#include <sys/uio.h>
|
||||
#endif
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
// Get nano time includes
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_GNU_KFREEBSD)
|
||||
#elif defined(__MACH__)
|
||||
@@ -199,7 +201,7 @@ class PosixClock : public SystemClock {
|
||||
std::string dummy;
|
||||
dummy.reserve(maxsize);
|
||||
dummy.resize(maxsize);
|
||||
char* p = &dummy[0];
|
||||
char* p = dummy.data();
|
||||
port::LocalTimeR(&seconds, &t);
|
||||
snprintf(p, maxsize, "%04d/%02d/%02d-%02d:%02d:%02d ", t.tm_year + 1900,
|
||||
t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
|
||||
|
||||
Vendored
+4
-3
@@ -26,13 +26,14 @@
|
||||
#ifdef OS_LINUX
|
||||
#include <fcntl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#ifdef ROCKSDB_FALLOCATE_PRESENT
|
||||
#include <errno.h>
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
@@ -2955,7 +2956,7 @@ struct NoDuplicateMiniStressTest {
|
||||
|
||||
NoDuplicateMiniStressTest() { env = Env::Default(); }
|
||||
|
||||
virtual ~NoDuplicateMiniStressTest() {}
|
||||
virtual ~NoDuplicateMiniStressTest() = default;
|
||||
|
||||
void Run() {
|
||||
std::array<std::thread, kThreads> threads;
|
||||
|
||||
Vendored
+2
-2
@@ -22,9 +22,9 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
FileSystem::FileSystem() {}
|
||||
FileSystem::FileSystem() = default;
|
||||
|
||||
FileSystem::~FileSystem() {}
|
||||
FileSystem::~FileSystem() = default;
|
||||
|
||||
static int RegisterBuiltinFileSystems(ObjectLibrary& library,
|
||||
const std::string& /*arg*/) {
|
||||
|
||||
Vendored
+8
-7
@@ -13,16 +13,17 @@
|
||||
#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#if defined(OS_LINUX) || defined(OS_SOLARIS) || defined(OS_ANDROID)
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/sysmacros.h>
|
||||
@@ -30,9 +31,9 @@
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
// Get nano time includes
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD)
|
||||
#elif defined(__MACH__)
|
||||
@@ -143,7 +144,7 @@ class PosixFileSystem : public FileSystem {
|
||||
const char* Name() const override { return kClassName(); }
|
||||
const char* NickName() const override { return kDefaultName(); }
|
||||
|
||||
~PosixFileSystem() override {}
|
||||
~PosixFileSystem() override = default;
|
||||
bool IsInstanceOf(const std::string& name) const override {
|
||||
if (name == "posix") {
|
||||
return true;
|
||||
|
||||
Vendored
+9
-6
@@ -10,23 +10,24 @@
|
||||
#ifdef ROCKSDB_LIB_IO_POSIX
|
||||
#include "env/io_posix.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#if defined(OS_LINUX)
|
||||
#include <linux/fs.h>
|
||||
#ifndef FALLOC_FL_KEEP_SIZE
|
||||
#include <linux/falloc.h>
|
||||
#endif
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#ifdef OS_LINUX
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/sysmacros.h>
|
||||
@@ -437,7 +438,7 @@ void LogicalBlockSizeCache::UnrefAndTryRemoveCachedLogicalBlockSize(
|
||||
|
||||
size_t LogicalBlockSizeCache::GetLogicalBlockSize(const std::string& fname,
|
||||
int fd) {
|
||||
std::string dir = fname.substr(0, fname.find_last_of("/"));
|
||||
std::string dir = fname.substr(0, fname.find_last_of('/'));
|
||||
if (dir.empty()) {
|
||||
dir = "/";
|
||||
}
|
||||
@@ -654,7 +655,9 @@ IOStatus PosixRandomAccessFile::MultiRead(FSReadRequest* reqs, size_t num_reqs,
|
||||
size_t this_reqs = (num_reqs - reqs_off) + incomplete_rq_list.size();
|
||||
|
||||
// If requests exceed depth, split it into batches
|
||||
if (this_reqs > kIoUringDepth) this_reqs = kIoUringDepth;
|
||||
if (this_reqs > kIoUringDepth) {
|
||||
this_reqs = kIoUringDepth;
|
||||
}
|
||||
|
||||
assert(incomplete_rq_list.size() <= this_reqs);
|
||||
for (size_t i = 0; i < this_reqs; i++) {
|
||||
|
||||
Vendored
+1
-1
@@ -483,7 +483,7 @@ class TestMemLogger : public Logger {
|
||||
options_(options),
|
||||
dbg_(dbg),
|
||||
flush_pending_(false) {}
|
||||
~TestMemLogger() override {}
|
||||
~TestMemLogger() override = default;
|
||||
|
||||
void Flush() override {
|
||||
if (flush_pending_) {
|
||||
|
||||
Vendored
+6
-6
@@ -37,28 +37,28 @@ TEST_F(MockEnvTest, Corrupt) {
|
||||
Slice result;
|
||||
std::unique_ptr<RandomAccessFile> rand_file;
|
||||
ASSERT_OK(env_->NewRandomAccessFile(kFileName, &rand_file, soptions_));
|
||||
ASSERT_OK(rand_file->Read(0, kGood.size(), &result, &(scratch[0])));
|
||||
ASSERT_OK(rand_file->Read(0, kGood.size(), &result, scratch.data()));
|
||||
ASSERT_EQ(result.compare(kGood), 0);
|
||||
|
||||
// Sync + corrupt => no change
|
||||
ASSERT_OK(writable_file->Fsync());
|
||||
ASSERT_OK(dynamic_cast<MockEnv*>(env_)->CorruptBuffer(kFileName));
|
||||
result.clear();
|
||||
ASSERT_OK(rand_file->Read(0, kGood.size(), &result, &(scratch[0])));
|
||||
ASSERT_OK(rand_file->Read(0, kGood.size(), &result, scratch.data()));
|
||||
ASSERT_EQ(result.compare(kGood), 0);
|
||||
|
||||
// Add new data and corrupt it
|
||||
ASSERT_OK(writable_file->Append(kCorrupted));
|
||||
ASSERT_TRUE(writable_file->GetFileSize() == kGood.size() + kCorrupted.size());
|
||||
result.clear();
|
||||
ASSERT_OK(
|
||||
rand_file->Read(kGood.size(), kCorrupted.size(), &result, &(scratch[0])));
|
||||
ASSERT_OK(rand_file->Read(kGood.size(), kCorrupted.size(), &result,
|
||||
scratch.data()));
|
||||
ASSERT_EQ(result.compare(kCorrupted), 0);
|
||||
// Corrupted
|
||||
ASSERT_OK(dynamic_cast<MockEnv*>(env_)->CorruptBuffer(kFileName));
|
||||
result.clear();
|
||||
ASSERT_OK(
|
||||
rand_file->Read(kGood.size(), kCorrupted.size(), &result, &(scratch[0])));
|
||||
ASSERT_OK(rand_file->Read(kGood.size(), kCorrupted.size(), &result,
|
||||
scratch.data()));
|
||||
ASSERT_NE(result.compare(kCorrupted), 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ Status DeleteScheduler::CleanupDirectory(Env* env, SstFileManagerImpl* sfm,
|
||||
Status DeleteScheduler::MarkAsTrash(const std::string& file_path,
|
||||
std::string* trash_file) {
|
||||
// Sanity check of the path
|
||||
size_t idx = file_path.rfind("/");
|
||||
size_t idx = file_path.rfind('/');
|
||||
if (idx == std::string::npos || idx == file_path.size() - 1) {
|
||||
return Status::InvalidArgument("file_path is corrupted");
|
||||
}
|
||||
|
||||
+233
-169
@@ -82,9 +82,9 @@ void FilePrefetchBuffer::CalculateOffsetAndLen(size_t alignment,
|
||||
Status FilePrefetchBuffer::Read(const IOOptions& opts,
|
||||
RandomAccessFileReader* reader,
|
||||
uint64_t read_len, uint64_t chunk_len,
|
||||
uint64_t rounddown_start, uint32_t index) {
|
||||
uint64_t start_offset, uint32_t index) {
|
||||
Slice result;
|
||||
Status s = reader->Read(opts, rounddown_start + chunk_len, read_len, &result,
|
||||
Status s = reader->Read(opts, start_offset + chunk_len, read_len, &result,
|
||||
bufs_[index].buffer_.BufferStart() + chunk_len,
|
||||
/*aligned_buf=*/nullptr);
|
||||
#ifndef NDEBUG
|
||||
@@ -102,15 +102,15 @@ Status FilePrefetchBuffer::Read(const IOOptions& opts,
|
||||
RecordTick(stats_, PREFETCH_BYTES, read_len);
|
||||
}
|
||||
// Update the buffer offset and size.
|
||||
bufs_[index].offset_ = rounddown_start;
|
||||
bufs_[index].offset_ = start_offset;
|
||||
bufs_[index].buffer_.Size(static_cast<size_t>(chunk_len) + result.size());
|
||||
return s;
|
||||
}
|
||||
|
||||
Status FilePrefetchBuffer::ReadAsync(const IOOptions& opts,
|
||||
RandomAccessFileReader* reader,
|
||||
uint64_t read_len,
|
||||
uint64_t rounddown_start, uint32_t index) {
|
||||
uint64_t read_len, uint64_t start_offset,
|
||||
uint32_t index) {
|
||||
TEST_SYNC_POINT("FilePrefetchBuffer::ReadAsync");
|
||||
// callback for async read request.
|
||||
auto fp = std::bind(&FilePrefetchBuffer::PrefetchAsyncCallback, this,
|
||||
@@ -118,7 +118,7 @@ Status FilePrefetchBuffer::ReadAsync(const IOOptions& opts,
|
||||
FSReadRequest req;
|
||||
Slice result;
|
||||
req.len = read_len;
|
||||
req.offset = rounddown_start;
|
||||
req.offset = start_offset;
|
||||
req.result = result;
|
||||
req.scratch = bufs_[index].buffer_.BufferStart();
|
||||
bufs_[index].async_req_len_ = req.len;
|
||||
@@ -129,6 +129,7 @@ Status FilePrefetchBuffer::ReadAsync(const IOOptions& opts,
|
||||
/*aligned_buf=*/nullptr);
|
||||
req.status.PermitUncheckedError();
|
||||
if (s.ok()) {
|
||||
RecordTick(stats_, PREFETCH_BYTES, read_len);
|
||||
bufs_[index].async_read_in_progress_ = true;
|
||||
}
|
||||
return s;
|
||||
@@ -149,19 +150,17 @@ Status FilePrefetchBuffer::Prefetch(const IOOptions& opts,
|
||||
}
|
||||
|
||||
size_t alignment = reader->file()->GetRequiredBufferAlignment();
|
||||
size_t offset_ = static_cast<size_t>(offset);
|
||||
uint64_t rounddown_offset = Rounddown(offset_, alignment);
|
||||
uint64_t roundup_end = Roundup(offset_ + n, alignment);
|
||||
uint64_t roundup_len = roundup_end - rounddown_offset;
|
||||
assert(roundup_len >= alignment);
|
||||
assert(roundup_len % alignment == 0);
|
||||
uint64_t rounddown_offset = offset, roundup_end = 0, chunk_len = 0;
|
||||
size_t read_len = 0;
|
||||
|
||||
uint64_t chunk_len = 0;
|
||||
CalculateOffsetAndLen(alignment, offset, roundup_len, curr_,
|
||||
true /*refit_tail*/, chunk_len);
|
||||
size_t read_len = static_cast<size_t>(roundup_len - chunk_len);
|
||||
ReadAheadSizeTuning(/*read_curr_block=*/true, /*refit_tail=*/true,
|
||||
rounddown_offset, curr_, alignment, 0, n,
|
||||
rounddown_offset, roundup_end, read_len, chunk_len);
|
||||
|
||||
Status s = Read(opts, reader, read_len, chunk_len, rounddown_offset, curr_);
|
||||
Status s;
|
||||
if (read_len > 0) {
|
||||
s = Read(opts, reader, read_len, chunk_len, rounddown_offset, curr_);
|
||||
}
|
||||
|
||||
if (usage_ == FilePrefetchBufferUsage::kTableOpenPrefetchTail && s.ok()) {
|
||||
RecordInHistogram(stats_, TABLE_OPEN_PREFETCH_TAIL_READ_BYTES, read_len);
|
||||
@@ -196,7 +195,7 @@ void FilePrefetchBuffer::CopyDataToBuffer(uint32_t src, uint64_t& offset,
|
||||
// length > 0 indicates it has consumed all data from the src buffer and it
|
||||
// still needs to read more other buffer.
|
||||
if (length > 0) {
|
||||
bufs_[src].buffer_.Clear();
|
||||
bufs_[src].ClearBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,28 +265,35 @@ void FilePrefetchBuffer::AbortAllIOs() {
|
||||
// Clear the buffers if it contains outdated data. Outdated data can be
|
||||
// because previous sequential reads were read from the cache instead of these
|
||||
// buffer.
|
||||
void FilePrefetchBuffer::UpdateBuffersIfNeeded(uint64_t offset) {
|
||||
void FilePrefetchBuffer::UpdateBuffersIfNeeded(uint64_t offset, size_t length) {
|
||||
uint32_t second = curr_ ^ 1;
|
||||
|
||||
if (IsBufferOutdated(offset, curr_)) {
|
||||
bufs_[curr_].buffer_.Clear();
|
||||
bufs_[curr_].ClearBuffer();
|
||||
}
|
||||
if (IsBufferOutdated(offset, second)) {
|
||||
bufs_[second].buffer_.Clear();
|
||||
bufs_[second].ClearBuffer();
|
||||
}
|
||||
|
||||
{
|
||||
// In case buffers do not align, reset second buffer. This can happen in
|
||||
// case readahead_size is set.
|
||||
// In case buffers do not align, reset second buffer if requested data needs
|
||||
// to be read in second buffer.
|
||||
if (!bufs_[second].async_read_in_progress_ &&
|
||||
!bufs_[curr_].async_read_in_progress_) {
|
||||
if (DoesBufferContainData(curr_)) {
|
||||
if (bufs_[curr_].offset_ + bufs_[curr_].buffer_.CurrentSize() !=
|
||||
bufs_[second].offset_) {
|
||||
bufs_[second].buffer_.Clear();
|
||||
if (DoesBufferContainData(second) &&
|
||||
IsOffsetInBuffer(offset, curr_) &&
|
||||
(offset + length >
|
||||
bufs_[curr_].offset_ + bufs_[curr_].buffer_.CurrentSize())) {
|
||||
bufs_[second].ClearBuffer();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!IsOffsetInBuffer(offset, second)) {
|
||||
bufs_[second].buffer_.Clear();
|
||||
if (DoesBufferContainData(second) &&
|
||||
!IsOffsetInBuffer(offset, second)) {
|
||||
bufs_[second].ClearBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,7 +314,8 @@ void FilePrefetchBuffer::UpdateBuffersIfNeeded(uint64_t offset) {
|
||||
}
|
||||
}
|
||||
|
||||
void FilePrefetchBuffer::PollAndUpdateBuffersIfNeeded(uint64_t offset) {
|
||||
void FilePrefetchBuffer::PollAndUpdateBuffersIfNeeded(uint64_t offset,
|
||||
size_t length) {
|
||||
if (bufs_[curr_].async_read_in_progress_ && fs_ != nullptr) {
|
||||
if (bufs_[curr_].io_handle_ != nullptr) {
|
||||
// Wait for prefetch data to complete.
|
||||
@@ -324,7 +331,87 @@ void FilePrefetchBuffer::PollAndUpdateBuffersIfNeeded(uint64_t offset) {
|
||||
// completed.
|
||||
DestroyAndClearIOHandle(curr_);
|
||||
}
|
||||
UpdateBuffersIfNeeded(offset);
|
||||
UpdateBuffersIfNeeded(offset, length);
|
||||
}
|
||||
|
||||
// ReadAheadSizeTuning API calls readaheadsize_cb_
|
||||
// (BlockBasedTableIterator::BlockCacheLookupForReadAheadSize) to lookup in the
|
||||
// cache and tune the start and end offsets based on cache hits/misses.
|
||||
//
|
||||
// Arguments -
|
||||
// read_curr_block : True if this call was due to miss in the cache and
|
||||
// FilePrefetchBuffer wants to read that block
|
||||
// synchronously.
|
||||
// False if current call is to prefetch additional data in
|
||||
// extra buffers through ReadAsync API.
|
||||
// prev_buf_end_offset : End offset of the previous buffer. It's used in case
|
||||
// of ReadAsync to make sure it doesn't read anything from
|
||||
// previous buffer which is already prefetched.
|
||||
void FilePrefetchBuffer::ReadAheadSizeTuning(
|
||||
bool read_curr_block, bool refit_tail, uint64_t prev_buf_end_offset,
|
||||
uint32_t index, size_t alignment, size_t length, size_t readahead_size,
|
||||
uint64_t& start_offset, uint64_t& end_offset, size_t& read_len,
|
||||
uint64_t& chunk_len) {
|
||||
uint64_t updated_start_offset = Rounddown(start_offset, alignment);
|
||||
uint64_t updated_end_offset =
|
||||
Roundup(start_offset + length + readahead_size, alignment);
|
||||
uint64_t initial_end_offset = updated_end_offset;
|
||||
uint64_t initial_start_offset = updated_start_offset;
|
||||
|
||||
// Callback to tune the start and end offsets.
|
||||
if (readaheadsize_cb_ != nullptr && readahead_size > 0) {
|
||||
readaheadsize_cb_(read_curr_block, updated_start_offset,
|
||||
updated_end_offset);
|
||||
}
|
||||
|
||||
// read_len will be 0 and there is nothing to read/prefetch.
|
||||
if (updated_start_offset == updated_end_offset) {
|
||||
UpdateReadAheadTrimmedStat((initial_end_offset - initial_start_offset),
|
||||
(updated_end_offset - updated_start_offset));
|
||||
return;
|
||||
}
|
||||
|
||||
assert(updated_start_offset < updated_end_offset);
|
||||
|
||||
if (!read_curr_block) {
|
||||
// Handle the case when callback added block handles which are already
|
||||
// prefetched and nothing new needs to be prefetched. In that case end
|
||||
// offset updated by callback will be less than prev_buf_end_offset which
|
||||
// means data has been already prefetched.
|
||||
if (updated_end_offset <= prev_buf_end_offset) {
|
||||
start_offset = end_offset = prev_buf_end_offset;
|
||||
UpdateReadAheadTrimmedStat((initial_end_offset - initial_start_offset),
|
||||
(end_offset - start_offset));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Realign if start and end offsets are not aligned after tuning.
|
||||
start_offset = Rounddown(updated_start_offset, alignment);
|
||||
end_offset = Roundup(updated_end_offset, alignment);
|
||||
|
||||
if (!read_curr_block && start_offset < prev_buf_end_offset) {
|
||||
// Previous buffer already contains the data till prev_buf_end_offset
|
||||
// because of alignment. Update the start offset after that to avoid
|
||||
// prefetching it again.
|
||||
start_offset = prev_buf_end_offset;
|
||||
}
|
||||
|
||||
uint64_t roundup_len = end_offset - start_offset;
|
||||
|
||||
CalculateOffsetAndLen(alignment, start_offset, roundup_len, index, refit_tail,
|
||||
chunk_len);
|
||||
assert(roundup_len >= chunk_len);
|
||||
|
||||
// Update the buffer offset.
|
||||
bufs_[index].offset_ = start_offset;
|
||||
// Update the initial end offset of this buffer which will be the starting
|
||||
// offset of next prefetch.
|
||||
bufs_[index].initial_end_offset_ = initial_end_offset;
|
||||
read_len = static_cast<size_t>(roundup_len - chunk_len);
|
||||
|
||||
UpdateReadAheadTrimmedStat((initial_end_offset - initial_start_offset),
|
||||
(end_offset - start_offset));
|
||||
}
|
||||
|
||||
Status FilePrefetchBuffer::HandleOverlappingData(
|
||||
@@ -340,7 +427,7 @@ Status FilePrefetchBuffer::HandleOverlappingData(
|
||||
// by Seek, but the next access is at another offset.
|
||||
if (bufs_[curr_].async_read_in_progress_ &&
|
||||
IsOffsetInBufferWithAsyncProgress(offset, curr_)) {
|
||||
PollAndUpdateBuffersIfNeeded(offset);
|
||||
PollAndUpdateBuffersIfNeeded(offset, length);
|
||||
}
|
||||
second = curr_ ^ 1;
|
||||
|
||||
@@ -354,7 +441,7 @@ Status FilePrefetchBuffer::HandleOverlappingData(
|
||||
(bufs_[second].async_read_in_progress_ ||
|
||||
DoesBufferContainData(second)))) {
|
||||
// Allocate new buffer to third buffer;
|
||||
bufs_[2].buffer_.Clear();
|
||||
bufs_[2].ClearBuffer();
|
||||
bufs_[2].buffer_.Alignment(alignment);
|
||||
bufs_[2].buffer_.AllocateNewBuffer(length);
|
||||
bufs_[2].offset_ = offset;
|
||||
@@ -363,31 +450,28 @@ Status FilePrefetchBuffer::HandleOverlappingData(
|
||||
CopyDataToBuffer(curr_, tmp_offset, tmp_length);
|
||||
|
||||
// Call async prefetching on curr_ since data has been consumed in curr_
|
||||
// only if data lies within second buffer.
|
||||
// only if requested data lies within second buffer.
|
||||
size_t second_size = bufs_[second].async_read_in_progress_
|
||||
? bufs_[second].async_req_len_
|
||||
: bufs_[second].buffer_.CurrentSize();
|
||||
uint64_t rounddown_start = bufs_[second].offset_ + second_size;
|
||||
uint64_t start_offset = bufs_[second].initial_end_offset_;
|
||||
// Second buffer might be out of bound if first buffer already prefetched
|
||||
// that data.
|
||||
if (tmp_offset + tmp_length <= bufs_[second].offset_ + second_size &&
|
||||
!IsOffsetOutOfBound(rounddown_start)) {
|
||||
uint64_t roundup_end =
|
||||
Roundup(rounddown_start + readahead_size, alignment);
|
||||
uint64_t roundup_len = roundup_end - rounddown_start;
|
||||
uint64_t chunk_len = 0;
|
||||
CalculateOffsetAndLen(alignment, rounddown_start, roundup_len, curr_,
|
||||
false, chunk_len);
|
||||
assert(chunk_len == 0);
|
||||
assert(roundup_len >= chunk_len);
|
||||
if (tmp_offset + tmp_length <= bufs_[second].offset_ + second_size) {
|
||||
size_t read_len = 0;
|
||||
uint64_t end_offset = start_offset, chunk_len = 0;
|
||||
|
||||
bufs_[curr_].offset_ = rounddown_start;
|
||||
uint64_t read_len = static_cast<size_t>(roundup_len - chunk_len);
|
||||
s = ReadAsync(opts, reader, read_len, rounddown_start, curr_);
|
||||
if (!s.ok()) {
|
||||
DestroyAndClearIOHandle(curr_);
|
||||
bufs_[curr_].buffer_.Clear();
|
||||
return s;
|
||||
ReadAheadSizeTuning(/*read_curr_block=*/false, /*refit_tail=*/false,
|
||||
bufs_[second].offset_ + second_size, curr_, alignment,
|
||||
/*length=*/0, readahead_size, start_offset,
|
||||
end_offset, read_len, chunk_len);
|
||||
if (read_len > 0) {
|
||||
s = ReadAsync(opts, reader, read_len, start_offset, curr_);
|
||||
if (!s.ok()) {
|
||||
DestroyAndClearIOHandle(curr_);
|
||||
bufs_[curr_].ClearBuffer();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
curr_ = curr_ ^ 1;
|
||||
@@ -430,13 +514,14 @@ Status FilePrefetchBuffer::PrefetchAsyncInternal(const IOOptions& opts,
|
||||
Status s;
|
||||
uint64_t tmp_offset = offset;
|
||||
size_t tmp_length = length;
|
||||
size_t original_length = length;
|
||||
|
||||
// 1. Abort IO and swap buffers if needed to point curr_ to first buffer with
|
||||
// data.
|
||||
if (!explicit_prefetch_submitted_) {
|
||||
AbortIOIfNeeded(offset);
|
||||
}
|
||||
UpdateBuffersIfNeeded(offset);
|
||||
UpdateBuffersIfNeeded(offset, length);
|
||||
|
||||
// 2. Handle overlapping data over two buffers. If data is overlapping then
|
||||
// during this call:
|
||||
@@ -458,14 +543,15 @@ Status FilePrefetchBuffer::PrefetchAsyncInternal(const IOOptions& opts,
|
||||
if (!bufs_[curr_].async_read_in_progress_ && DoesBufferContainData(curr_) &&
|
||||
IsDataBlockInBuffer(offset, length, curr_)) {
|
||||
// Whole data is in curr_.
|
||||
UpdateBuffersIfNeeded(offset);
|
||||
UpdateBuffersIfNeeded(offset, length);
|
||||
if (!IsSecondBuffEligibleForPrefetching()) {
|
||||
UpdateStats(/*found_in_buffer=*/true, original_length);
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
// After poll request, curr_ might be empty because of IOError in
|
||||
// callback while reading or may contain required data.
|
||||
PollAndUpdateBuffersIfNeeded(offset);
|
||||
PollAndUpdateBuffersIfNeeded(offset, length);
|
||||
}
|
||||
|
||||
if (copy_to_third_buffer) {
|
||||
@@ -488,6 +574,7 @@ Status FilePrefetchBuffer::PrefetchAsyncInternal(const IOOptions& opts,
|
||||
return s;
|
||||
}
|
||||
if (!IsSecondBuffEligibleForPrefetching()) {
|
||||
UpdateStats(/*found_in_buffer=*/true, original_length);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -509,7 +596,7 @@ Status FilePrefetchBuffer::PrefetchAsyncInternal(const IOOptions& opts,
|
||||
}
|
||||
}
|
||||
DestroyAndClearIOHandle(second);
|
||||
bufs_[second].buffer_.Clear();
|
||||
bufs_[second].ClearBuffer();
|
||||
}
|
||||
|
||||
// 5. Data is overlapping i.e. some of the data has been copied to third
|
||||
@@ -524,73 +611,57 @@ Status FilePrefetchBuffer::PrefetchAsyncInternal(const IOOptions& opts,
|
||||
// and sync prefetching and copy the remaining data to third buffer in the
|
||||
// end.
|
||||
if (length == 0) {
|
||||
UpdateStats(/*found_in_buffer=*/true, original_length);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Go for ReadAsync and Read (if needed).
|
||||
size_t prefetch_size = length + readahead_size;
|
||||
size_t _offset = static_cast<size_t>(offset);
|
||||
|
||||
// offset and size alignment for curr_ buffer with synchronous prefetching
|
||||
uint64_t rounddown_start1 = Rounddown(_offset, alignment);
|
||||
uint64_t roundup_end1 = Roundup(_offset + prefetch_size, alignment);
|
||||
uint64_t roundup_len1 = roundup_end1 - rounddown_start1;
|
||||
assert(roundup_len1 >= alignment);
|
||||
assert(roundup_len1 % alignment == 0);
|
||||
uint64_t chunk_len1 = 0;
|
||||
uint64_t read_len1 = 0;
|
||||
|
||||
assert(!bufs_[second].async_read_in_progress_ &&
|
||||
!DoesBufferContainData(second));
|
||||
|
||||
// offset and size alignment for curr_ buffer with synchronous prefetching
|
||||
uint64_t start_offset1 = offset, end_offset1 = 0, chunk_len1 = 0;
|
||||
size_t read_len1 = 0;
|
||||
|
||||
// For length == 0, skip the synchronous prefetching. read_len1 will be 0.
|
||||
if (length > 0) {
|
||||
CalculateOffsetAndLen(alignment, offset, roundup_len1, curr_,
|
||||
false /*refit_tail*/, chunk_len1);
|
||||
assert(roundup_len1 >= chunk_len1);
|
||||
read_len1 = static_cast<size_t>(roundup_len1 - chunk_len1);
|
||||
ReadAheadSizeTuning(/*read_curr_block=*/true, /*refit_tail=*/false,
|
||||
start_offset1, curr_, alignment, length, readahead_size,
|
||||
start_offset1, end_offset1, read_len1, chunk_len1);
|
||||
UpdateStats(/*found_in_buffer=*/false,
|
||||
/*length_found=*/original_length - length);
|
||||
} else {
|
||||
end_offset1 = bufs_[curr_].offset_ + bufs_[curr_].buffer_.CurrentSize();
|
||||
UpdateStats(/*found_in_buffer=*/true, original_length);
|
||||
}
|
||||
|
||||
// Prefetch in second buffer only if readahead_size_ > 0.
|
||||
if (readahead_size_ > 0) {
|
||||
// Prefetch in second buffer only if readahead_size > 0.
|
||||
if (readahead_size > 0) {
|
||||
// offset and size alignment for second buffer for asynchronous
|
||||
// prefetching
|
||||
uint64_t rounddown_start2 = roundup_end1;
|
||||
uint64_t roundup_end2 =
|
||||
Roundup(rounddown_start2 + readahead_size, alignment);
|
||||
// prefetching.
|
||||
uint64_t start_offset2 = bufs_[curr_].initial_end_offset_;
|
||||
|
||||
// For length == 0, do the asynchronous prefetching in second instead of
|
||||
// synchronous prefetching in curr_.
|
||||
if (length == 0) {
|
||||
rounddown_start2 =
|
||||
bufs_[curr_].offset_ + bufs_[curr_].buffer_.CurrentSize();
|
||||
roundup_end2 = Roundup(rounddown_start2 + prefetch_size, alignment);
|
||||
}
|
||||
|
||||
// Second buffer might be out of bound if first buffer already prefetched
|
||||
// that data.
|
||||
if (!IsOffsetOutOfBound(rounddown_start2)) {
|
||||
uint64_t roundup_len2 = roundup_end2 - rounddown_start2;
|
||||
uint64_t chunk_len2 = 0;
|
||||
CalculateOffsetAndLen(alignment, rounddown_start2, roundup_len2, second,
|
||||
false /*refit_tail*/, chunk_len2);
|
||||
assert(chunk_len2 == 0);
|
||||
// Update the buffer offset.
|
||||
bufs_[second].offset_ = rounddown_start2;
|
||||
assert(roundup_len2 >= chunk_len2);
|
||||
uint64_t read_len2 = static_cast<size_t>(roundup_len2 - chunk_len2);
|
||||
s = ReadAsync(opts, reader, read_len2, rounddown_start2, second);
|
||||
if (!s.ok()) {
|
||||
DestroyAndClearIOHandle(second);
|
||||
bufs_[second].buffer_.Clear();
|
||||
return s;
|
||||
}
|
||||
// Find updated readahead size after tuning
|
||||
size_t read_len2 = 0;
|
||||
uint64_t end_offset2 = start_offset2, chunk_len2 = 0;
|
||||
ReadAheadSizeTuning(/*read_curr_block=*/false, /*refit_tail=*/false,
|
||||
/*prev_buf_end_offset=*/end_offset1, second,
|
||||
alignment,
|
||||
/*length=*/0, readahead_size, start_offset2,
|
||||
end_offset2, read_len2, chunk_len2);
|
||||
if (read_len2 > 0) {
|
||||
s = ReadAsync(opts, reader, read_len2, start_offset2, second);
|
||||
if (!s.ok()) {
|
||||
DestroyAndClearIOHandle(second);
|
||||
bufs_[second].ClearBuffer();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (read_len1 > 0) {
|
||||
s = Read(opts, reader, read_len1, chunk_len1, rounddown_start1, curr_);
|
||||
s = Read(opts, reader, read_len1, chunk_len1, start_offset1, curr_);
|
||||
if (!s.ok()) {
|
||||
if (bufs_[second].io_handle_ != nullptr) {
|
||||
std::vector<void*> handles;
|
||||
@@ -602,11 +673,12 @@ Status FilePrefetchBuffer::PrefetchAsyncInternal(const IOOptions& opts,
|
||||
}
|
||||
}
|
||||
DestroyAndClearIOHandle(second);
|
||||
bufs_[second].buffer_.Clear();
|
||||
bufs_[curr_].buffer_.Clear();
|
||||
bufs_[second].ClearBuffer();
|
||||
bufs_[curr_].ClearBuffer();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy remaining requested bytes to third_buffer.
|
||||
if (copy_to_third_buffer && length > 0) {
|
||||
CopyDataToBuffer(curr_, offset, length);
|
||||
@@ -668,8 +740,7 @@ bool FilePrefetchBuffer::TryReadFromCacheUntracked(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
size_t current_readahead_size = ReadAheadSizeTuning(offset, n);
|
||||
s = Prefetch(opts, reader, offset, n + current_readahead_size);
|
||||
s = Prefetch(opts, reader, offset, n + readahead_size_);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
if (status) {
|
||||
@@ -729,8 +800,8 @@ bool FilePrefetchBuffer::TryReadFromCacheAsyncUntracked(
|
||||
// Random offset called. So abort the IOs.
|
||||
if (prev_offset_ != offset) {
|
||||
AbortAllIOs();
|
||||
bufs_[curr_].buffer_.Clear();
|
||||
bufs_[curr_ ^ 1].buffer_.Clear();
|
||||
bufs_[curr_].ClearBuffer();
|
||||
bufs_[curr_ ^ 1].ClearBuffer();
|
||||
explicit_prefetch_submitted_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -768,8 +839,6 @@ bool FilePrefetchBuffer::TryReadFromCacheAsyncUntracked(
|
||||
}
|
||||
}
|
||||
|
||||
UpdateReadAheadSizeForUpperBound(offset, n);
|
||||
|
||||
// Prefetch n + readahead_size_/2 synchronously as remaining
|
||||
// readahead_size_/2 will be prefetched asynchronously.
|
||||
s = PrefetchAsyncInternal(opts, reader, offset, n, readahead_size_ / 2,
|
||||
@@ -788,6 +857,8 @@ bool FilePrefetchBuffer::TryReadFromCacheAsyncUntracked(
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
UpdateStats(/*found_in_buffer=*/true, n);
|
||||
}
|
||||
|
||||
UpdateReadPattern(offset, n, false /*decrease_readaheadsize*/);
|
||||
@@ -848,7 +919,6 @@ Status FilePrefetchBuffer::PrefetchAsync(const IOOptions& opts,
|
||||
explicit_prefetch_submitted_ = false;
|
||||
bool is_eligible_for_prefetching = false;
|
||||
|
||||
UpdateReadAheadSizeForUpperBound(offset, n);
|
||||
if (readahead_size_ > 0 &&
|
||||
(!implicit_auto_readahead_ ||
|
||||
num_file_reads_ >= num_file_reads_for_auto_readahead_)) {
|
||||
@@ -860,14 +930,19 @@ Status FilePrefetchBuffer::PrefetchAsync(const IOOptions& opts,
|
||||
AbortAllIOs();
|
||||
|
||||
// 2. Clear outdated data.
|
||||
UpdateBuffersIfNeeded(offset);
|
||||
UpdateBuffersIfNeeded(offset, n);
|
||||
uint32_t second = curr_ ^ 1;
|
||||
// Since PrefetchAsync can be called on non sequential reads. So offset can
|
||||
// be less than curr_ buffers' offset. In that case also it clears both
|
||||
// buffers.
|
||||
if (DoesBufferContainData(curr_) && !IsOffsetInBuffer(offset, curr_)) {
|
||||
bufs_[curr_].buffer_.Clear();
|
||||
bufs_[second].buffer_.Clear();
|
||||
|
||||
// - Since PrefetchAsync can be called on non sequential reads. So offset can
|
||||
// be less than curr_ buffers' offset. In that case it clears both
|
||||
// buffers.
|
||||
// - In case of tuning of readahead_size, on Reseek, we have to clear both
|
||||
// buffers otherwise, we may end up with inconsistent BlockHandles in queue
|
||||
// and data in buffer.
|
||||
if (readaheadsize_cb_ != nullptr ||
|
||||
(DoesBufferContainData(curr_) && !IsOffsetInBuffer(offset, curr_))) {
|
||||
bufs_[curr_].ClearBuffer();
|
||||
bufs_[second].ClearBuffer();
|
||||
}
|
||||
|
||||
UpdateReadPattern(offset, n, /*decrease_readaheadsize=*/false);
|
||||
@@ -879,6 +954,8 @@ Status FilePrefetchBuffer::PrefetchAsync(const IOOptions& opts,
|
||||
uint64_t offset_in_buffer = offset - bufs_[curr_].offset_;
|
||||
*result = Slice(bufs_[curr_].buffer_.BufferStart() + offset_in_buffer, n);
|
||||
data_found = true;
|
||||
UpdateStats(/*found_in_buffer=*/true, n);
|
||||
|
||||
// Update num_file_reads_ as TryReadFromCacheAsync won't be called for
|
||||
// poll and update num_file_reads_ if data is found.
|
||||
num_file_reads_++;
|
||||
@@ -890,91 +967,78 @@ Status FilePrefetchBuffer::PrefetchAsync(const IOOptions& opts,
|
||||
}
|
||||
} else {
|
||||
// Partial data in curr_.
|
||||
bufs_[curr_].buffer_.Clear();
|
||||
bufs_[curr_].ClearBuffer();
|
||||
}
|
||||
bufs_[second].buffer_.Clear();
|
||||
bufs_[second].ClearBuffer();
|
||||
|
||||
std::string msg;
|
||||
|
||||
Status s;
|
||||
size_t alignment = reader->file()->GetRequiredBufferAlignment();
|
||||
size_t prefetch_size = is_eligible_for_prefetching ? readahead_size_ / 2 : 0;
|
||||
size_t readahead_size = is_eligible_for_prefetching ? readahead_size_ / 2 : 0;
|
||||
size_t offset_to_read = static_cast<size_t>(offset);
|
||||
uint64_t rounddown_start1 = 0;
|
||||
uint64_t roundup_end1 = 0;
|
||||
uint64_t rounddown_start2 = 0;
|
||||
uint64_t roundup_end2 = 0;
|
||||
uint64_t chunk_len1 = 0;
|
||||
uint64_t chunk_len2 = 0;
|
||||
size_t read_len1 = 0;
|
||||
size_t read_len2 = 0;
|
||||
uint64_t start_offset1 = offset, end_offset1 = 0, start_offset2 = 0,
|
||||
chunk_len1 = 0;
|
||||
size_t read_len1 = 0, read_len2 = 0;
|
||||
|
||||
// - If curr_ is empty.
|
||||
// - Call async read for full data + prefetch_size on curr_.
|
||||
// - Call async read for prefetch_size on second if eligible.
|
||||
// - Call async read for full data + readahead_size on curr_.
|
||||
// - Call async read for readahead_size on second if eligible.
|
||||
// - If curr_ is filled.
|
||||
// - prefetch_size on second.
|
||||
// - readahead_size on second.
|
||||
// Calculate length and offsets for reading.
|
||||
if (!DoesBufferContainData(curr_)) {
|
||||
uint64_t roundup_len1;
|
||||
// Prefetch full data + prefetch_size in curr_.
|
||||
// Prefetch full data + readahead_size in curr_.
|
||||
if (is_eligible_for_prefetching || reader->use_direct_io()) {
|
||||
rounddown_start1 = Rounddown(offset_to_read, alignment);
|
||||
roundup_end1 = Roundup(offset_to_read + n + prefetch_size, alignment);
|
||||
roundup_len1 = roundup_end1 - rounddown_start1;
|
||||
assert(roundup_len1 >= alignment);
|
||||
assert(roundup_len1 % alignment == 0);
|
||||
ReadAheadSizeTuning(/*read_curr_block=*/true, /*refit_tail=*/false,
|
||||
/*prev_buf_end_offset=*/start_offset1, curr_,
|
||||
alignment, n, readahead_size, start_offset1,
|
||||
end_offset1, read_len1, chunk_len1);
|
||||
} else {
|
||||
rounddown_start1 = offset_to_read;
|
||||
roundup_end1 = offset_to_read + n;
|
||||
roundup_len1 = roundup_end1 - rounddown_start1;
|
||||
// No alignment or extra prefetching.
|
||||
start_offset1 = offset_to_read;
|
||||
end_offset1 = offset_to_read + n;
|
||||
roundup_len1 = end_offset1 - start_offset1;
|
||||
CalculateOffsetAndLen(alignment, start_offset1, roundup_len1, curr_,
|
||||
false, chunk_len1);
|
||||
assert(chunk_len1 == 0);
|
||||
assert(roundup_len1 >= chunk_len1);
|
||||
read_len1 = static_cast<size_t>(roundup_len1);
|
||||
bufs_[curr_].offset_ = start_offset1;
|
||||
}
|
||||
CalculateOffsetAndLen(alignment, rounddown_start1, roundup_len1, curr_,
|
||||
false, chunk_len1);
|
||||
assert(chunk_len1 == 0);
|
||||
assert(roundup_len1 >= chunk_len1);
|
||||
read_len1 = static_cast<size_t>(roundup_len1);
|
||||
bufs_[curr_].offset_ = rounddown_start1;
|
||||
}
|
||||
|
||||
if (is_eligible_for_prefetching) {
|
||||
if (DoesBufferContainData(curr_)) {
|
||||
rounddown_start2 =
|
||||
bufs_[curr_].offset_ + bufs_[curr_].buffer_.CurrentSize();
|
||||
} else {
|
||||
rounddown_start2 = roundup_end1;
|
||||
}
|
||||
|
||||
start_offset2 = bufs_[curr_].initial_end_offset_;
|
||||
// Second buffer might be out of bound if first buffer already prefetched
|
||||
// that data.
|
||||
if (!IsOffsetOutOfBound(rounddown_start2)) {
|
||||
roundup_end2 = Roundup(rounddown_start2 + prefetch_size, alignment);
|
||||
uint64_t roundup_len2 = roundup_end2 - rounddown_start2;
|
||||
|
||||
CalculateOffsetAndLen(alignment, rounddown_start2, roundup_len2, second,
|
||||
false, chunk_len2);
|
||||
assert(chunk_len2 == 0);
|
||||
assert(roundup_len2 >= chunk_len2);
|
||||
read_len2 = static_cast<size_t>(roundup_len2 - chunk_len2);
|
||||
// Update the buffer offset.
|
||||
bufs_[second].offset_ = rounddown_start2;
|
||||
}
|
||||
uint64_t end_offset2 = start_offset2, chunk_len2 = 0;
|
||||
ReadAheadSizeTuning(/*read_curr_block=*/false, /*refit_tail=*/false,
|
||||
/*prev_buf_end_offset=*/end_offset1, second,
|
||||
alignment,
|
||||
/*length=*/0, readahead_size, start_offset2,
|
||||
end_offset2, read_len2, chunk_len2);
|
||||
}
|
||||
|
||||
if (read_len1) {
|
||||
s = ReadAsync(opts, reader, read_len1, rounddown_start1, curr_);
|
||||
s = ReadAsync(opts, reader, read_len1, start_offset1, curr_);
|
||||
if (!s.ok()) {
|
||||
DestroyAndClearIOHandle(curr_);
|
||||
bufs_[curr_].buffer_.Clear();
|
||||
bufs_[curr_].ClearBuffer();
|
||||
return s;
|
||||
}
|
||||
explicit_prefetch_submitted_ = true;
|
||||
prev_len_ = 0;
|
||||
}
|
||||
|
||||
if (read_len2) {
|
||||
TEST_SYNC_POINT("FilePrefetchBuffer::PrefetchAsync:ExtraPrefetching");
|
||||
s = ReadAsync(opts, reader, read_len2, rounddown_start2, second);
|
||||
s = ReadAsync(opts, reader, read_len2, start_offset2, second);
|
||||
if (!s.ok()) {
|
||||
DestroyAndClearIOHandle(second);
|
||||
bufs_[second].buffer_.Clear();
|
||||
bufs_[second].ClearBuffer();
|
||||
return s;
|
||||
}
|
||||
readahead_size_ = std::min(max_readahead_size_, readahead_size_ * 2);
|
||||
|
||||
+46
-48
@@ -32,6 +32,11 @@ struct IOOptions;
|
||||
class RandomAccessFileReader;
|
||||
|
||||
struct BufferInfo {
|
||||
void ClearBuffer() {
|
||||
buffer_.Clear();
|
||||
initial_end_offset_ = 0;
|
||||
}
|
||||
|
||||
AlignedBuffer buffer_;
|
||||
|
||||
uint64_t offset_ = 0;
|
||||
@@ -52,6 +57,18 @@ struct BufferInfo {
|
||||
|
||||
// pos represents the index of this buffer in vector of BufferInfo.
|
||||
uint32_t pos_ = 0;
|
||||
|
||||
// initial_end_offset is used to keep track of the end offset of the buffer
|
||||
// that was originally called. It's helpful in case of autotuning of readahead
|
||||
// size when callback is made to BlockBasedTableIterator.
|
||||
// initial end offset of this buffer which will be the starting
|
||||
// offset of next prefetch.
|
||||
//
|
||||
// For example - if end offset of previous buffer was 100 and because of
|
||||
// readahead_size optimization, end_offset was trimmed to 60. Then for next
|
||||
// prefetch call, start_offset should be intialized to 100 i.e start_offset =
|
||||
// buf->initial_end_offset_.
|
||||
uint64_t initial_end_offset_ = 0;
|
||||
};
|
||||
|
||||
enum class FilePrefetchBufferUsage {
|
||||
@@ -88,10 +105,9 @@ class FilePrefetchBuffer {
|
||||
size_t readahead_size = 0, size_t max_readahead_size = 0,
|
||||
bool enable = true, bool track_min_offset = false,
|
||||
bool implicit_auto_readahead = false, uint64_t num_file_reads = 0,
|
||||
uint64_t num_file_reads_for_auto_readahead = 0,
|
||||
uint64_t upper_bound_offset = 0, FileSystem* fs = nullptr,
|
||||
uint64_t num_file_reads_for_auto_readahead = 0, FileSystem* fs = nullptr,
|
||||
SystemClock* clock = nullptr, Statistics* stats = nullptr,
|
||||
const std::function<void(uint64_t, size_t, size_t&)>& cb = nullptr,
|
||||
const std::function<void(bool, uint64_t&, uint64_t&)>& cb = nullptr,
|
||||
FilePrefetchBufferUsage usage = FilePrefetchBufferUsage::kUnknown)
|
||||
: curr_(0),
|
||||
readahead_size_(readahead_size),
|
||||
@@ -110,7 +126,6 @@ class FilePrefetchBuffer {
|
||||
clock_(clock),
|
||||
stats_(stats),
|
||||
usage_(usage),
|
||||
upper_bound_offset_(upper_bound_offset),
|
||||
readaheadsize_cb_(cb) {
|
||||
assert((num_file_reads_ >= num_file_reads_for_auto_readahead_ + 1) ||
|
||||
(num_file_reads_ == 0));
|
||||
@@ -239,9 +254,6 @@ class FilePrefetchBuffer {
|
||||
void UpdateReadPattern(const uint64_t& offset, const size_t& len,
|
||||
bool decrease_readaheadsize) {
|
||||
if (decrease_readaheadsize) {
|
||||
// Since this block was eligible for prefetch but it was found in
|
||||
// cache, so check and decrease the readahead_size by 8KB (default)
|
||||
// if eligible.
|
||||
DecreaseReadAheadIfEligible(offset, len);
|
||||
}
|
||||
prev_offset_ = offset;
|
||||
@@ -282,9 +294,10 @@ class FilePrefetchBuffer {
|
||||
// Callback function passed to underlying FS in case of asynchronous reads.
|
||||
void PrefetchAsyncCallback(const FSReadRequest& req, void* cb_arg);
|
||||
|
||||
void ResetUpperBoundOffset(uint64_t upper_bound_offset) {
|
||||
upper_bound_offset_ = upper_bound_offset;
|
||||
readahead_size_ = initial_auto_readahead_size_;
|
||||
void TEST_GetBufferOffsetandSize(uint32_t index, uint64_t& offset,
|
||||
size_t& len) {
|
||||
offset = bufs_[index].offset_;
|
||||
len = bufs_[index].buffer_.CurrentSize();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -299,12 +312,12 @@ class FilePrefetchBuffer {
|
||||
|
||||
void AbortAllIOs();
|
||||
|
||||
void UpdateBuffersIfNeeded(uint64_t offset);
|
||||
void UpdateBuffersIfNeeded(uint64_t offset, size_t len);
|
||||
|
||||
// It calls Poll API if any there is any pending asynchronous request. It then
|
||||
// checks if data is in any buffer. It clears the outdated data and swaps the
|
||||
// buffers if required.
|
||||
void PollAndUpdateBuffersIfNeeded(uint64_t offset);
|
||||
void PollAndUpdateBuffersIfNeeded(uint64_t offset, size_t len);
|
||||
|
||||
Status PrefetchAsyncInternal(const IOOptions& opts,
|
||||
RandomAccessFileReader* reader, uint64_t offset,
|
||||
@@ -312,11 +325,11 @@ class FilePrefetchBuffer {
|
||||
bool& copy_to_third_buffer);
|
||||
|
||||
Status Read(const IOOptions& opts, RandomAccessFileReader* reader,
|
||||
uint64_t read_len, uint64_t chunk_len, uint64_t rounddown_start,
|
||||
uint64_t read_len, uint64_t chunk_len, uint64_t start_offset,
|
||||
uint32_t index);
|
||||
|
||||
Status ReadAsync(const IOOptions& opts, RandomAccessFileReader* reader,
|
||||
uint64_t read_len, uint64_t rounddown_start, uint32_t index);
|
||||
uint64_t read_len, uint64_t start_offset, uint32_t index);
|
||||
|
||||
// Copy the data from src to third buffer.
|
||||
void CopyDataToBuffer(uint32_t src, uint64_t& offset, size_t& length);
|
||||
@@ -402,7 +415,7 @@ class FilePrefetchBuffer {
|
||||
return false;
|
||||
}
|
||||
|
||||
bufs_[second].buffer_.Clear();
|
||||
bufs_[second].ClearBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -432,38 +445,27 @@ class FilePrefetchBuffer {
|
||||
uint64_t offset, size_t n, Slice* result,
|
||||
Status* status);
|
||||
|
||||
void UpdateReadAheadSizeForUpperBound(uint64_t offset, size_t n) {
|
||||
// Adjust readhahead_size till upper_bound if upper_bound_offset_ is
|
||||
// set.
|
||||
if (readahead_size_ > 0 && upper_bound_offset_ > 0 &&
|
||||
upper_bound_offset_ > offset) {
|
||||
if (upper_bound_offset_ < offset + n + readahead_size_) {
|
||||
readahead_size_ = (upper_bound_offset_ - offset) - n;
|
||||
RecordTick(stats_, READAHEAD_TRIMMED);
|
||||
}
|
||||
void ReadAheadSizeTuning(bool read_curr_block, bool refit_tail,
|
||||
uint64_t prev_buf_end_offset, uint32_t index,
|
||||
size_t alignment, size_t length,
|
||||
size_t readahead_size, uint64_t& offset,
|
||||
uint64_t& end_offset, size_t& read_len,
|
||||
uint64_t& chunk_len);
|
||||
|
||||
void UpdateStats(bool found_in_buffer, size_t length_found) {
|
||||
if (found_in_buffer) {
|
||||
RecordTick(stats_, PREFETCH_HITS);
|
||||
}
|
||||
if (length_found > 0) {
|
||||
RecordTick(stats_, PREFETCH_BYTES_USEFUL, length_found);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool IsOffsetOutOfBound(uint64_t offset) {
|
||||
if (upper_bound_offset_ > 0) {
|
||||
return (offset >= upper_bound_offset_);
|
||||
void UpdateReadAheadTrimmedStat(size_t initial_length,
|
||||
size_t updated_length) {
|
||||
if (initial_length != updated_length) {
|
||||
RecordTick(stats_, READAHEAD_TRIMMED);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Performs tuning to calculate readahead_size.
|
||||
size_t ReadAheadSizeTuning(uint64_t offset, size_t n) {
|
||||
UpdateReadAheadSizeForUpperBound(offset, n);
|
||||
|
||||
if (readaheadsize_cb_ != nullptr && readahead_size_ > 0) {
|
||||
size_t updated_readahead_size = 0;
|
||||
readaheadsize_cb_(offset, readahead_size_, updated_readahead_size);
|
||||
if (readahead_size_ != updated_readahead_size) {
|
||||
RecordTick(stats_, READAHEAD_TRIMMED);
|
||||
}
|
||||
return updated_readahead_size;
|
||||
}
|
||||
return readahead_size_;
|
||||
}
|
||||
|
||||
std::vector<BufferInfo> bufs_;
|
||||
@@ -508,10 +510,6 @@ class FilePrefetchBuffer {
|
||||
|
||||
FilePrefetchBufferUsage usage_;
|
||||
|
||||
// upper_bound_offset_ is set when ReadOptions.iterate_upper_bound and
|
||||
// ReadOptions.auto_readahead_size are set to trim readahead_size upto
|
||||
// upper_bound_offset_ during prefetching.
|
||||
uint64_t upper_bound_offset_ = 0;
|
||||
std::function<void(uint64_t, size_t, size_t&)> readaheadsize_cb_;
|
||||
std::function<void(bool, uint64_t&, uint64_t&)> readaheadsize_cb_;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+2
-3
@@ -8,10 +8,9 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#include "file/filename.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
#include "file/writable_file_writer.h"
|
||||
|
||||
+9
-381
@@ -1325,6 +1325,10 @@ TEST_P(PrefetchTest, PrefetchWithBlockLookupAutoTuneTest) {
|
||||
ropts.readahead_size = cmp_ro.readahead_size = 32768;
|
||||
}
|
||||
|
||||
if (std::get<1>(GetParam())) {
|
||||
ropts.async_io = true;
|
||||
}
|
||||
|
||||
// With and without tuning readahead_size.
|
||||
{
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
@@ -1351,10 +1355,6 @@ TEST_P(PrefetchTest, PrefetchWithBlockLookupAutoTuneTest) {
|
||||
cmp_iter->Next();
|
||||
}
|
||||
|
||||
uint64_t readahead_trimmed =
|
||||
options.statistics->getAndResetTickerCount(READAHEAD_TRIMMED);
|
||||
ASSERT_GT(readahead_trimmed, 0);
|
||||
|
||||
ASSERT_OK(cmp_iter->status());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
@@ -1381,10 +1381,6 @@ TEST_P(PrefetchTest, PrefetchWithBlockLookupAutoTuneTest) {
|
||||
cmp_iter->Next();
|
||||
}
|
||||
|
||||
uint64_t readahead_trimmed =
|
||||
options.statistics->getAndResetTickerCount(READAHEAD_TRIMMED);
|
||||
ASSERT_GT(readahead_trimmed, 0);
|
||||
|
||||
ASSERT_OK(cmp_iter->status());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
@@ -2353,314 +2349,6 @@ TEST_P(PrefetchTest1, SeekParallelizationTest) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// This test checks if readahead_size is trimmed when upper_bound is reached.
|
||||
// It tests with different combinations of async_io disabled/enabled,
|
||||
// readahead_size (implicit and explicit), and num_file_reads_for_auto_readahead
|
||||
// from 0 to 2.
|
||||
TEST_P(PrefetchTest, IterReadAheadSizeWithUpperBound) {
|
||||
if (mem_env_ || encrypted_env_) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment");
|
||||
return;
|
||||
}
|
||||
|
||||
// First param is if the mockFS support_prefetch or not
|
||||
std::shared_ptr<MockFS> fs =
|
||||
std::make_shared<MockFS>(FileSystem::Default(), false);
|
||||
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, fs));
|
||||
Options options;
|
||||
SetGenericOptions(env.get(), /*use_direct_io=*/false, options);
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
SetBlockBasedTableOptions(table_options);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_OK(s);
|
||||
|
||||
Random rnd(309);
|
||||
WriteBatch batch;
|
||||
|
||||
for (int i = 0; i < 26; i++) {
|
||||
std::string key = "my_key_";
|
||||
|
||||
for (int j = 0; j < 10; j++) {
|
||||
key += char('a' + i);
|
||||
ASSERT_OK(batch.Put(key, rnd.RandomString(1000)));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
|
||||
std::string start_key = "my_key_a";
|
||||
|
||||
std::string end_key = "my_key_";
|
||||
for (int j = 0; j < 10; j++) {
|
||||
end_key += char('a' + 25);
|
||||
}
|
||||
|
||||
Slice least(start_key.data(), start_key.size());
|
||||
Slice greatest(end_key.data(), end_key.size());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest));
|
||||
|
||||
int buff_prefetch_count = 0;
|
||||
|
||||
// Try with different num_file_reads_for_auto_readahead from 0 to 3.
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
table_options.num_file_reads_for_auto_readahead = i;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
s = TryReopen(options);
|
||||
ASSERT_OK(s);
|
||||
|
||||
int buff_count_with_tuning = 0, buff_count_without_tuning = 0;
|
||||
int keys_with_tuning = 0, keys_without_tuning = 0;
|
||||
int reseek_keys_with_tuning = 0, reseek_keys_without_tuning = 0;
|
||||
buff_prefetch_count = 0;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FilePrefetchBuffer::Prefetch:Start",
|
||||
[&](void*) { buff_prefetch_count++; });
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FilePrefetchBuffer::PrefetchAsyncInternal:Start",
|
||||
[&](void*) { buff_prefetch_count++; });
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ReadOptions ropts;
|
||||
if (std::get<0>(GetParam())) {
|
||||
ropts.readahead_size = 32768;
|
||||
}
|
||||
if (std::get<1>(GetParam())) {
|
||||
ropts.async_io = true;
|
||||
}
|
||||
|
||||
// With tuning readahead_size.
|
||||
{
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
Slice ub = Slice("my_key_uuu");
|
||||
Slice* ub_ptr = &ub;
|
||||
ropts.iterate_upper_bound = ub_ptr;
|
||||
ropts.auto_readahead_size = true;
|
||||
|
||||
auto iter = std::unique_ptr<Iterator>(db_->NewIterator(ropts));
|
||||
|
||||
// Seek.
|
||||
{
|
||||
Slice seek_key = Slice("my_key_aaa");
|
||||
iter->Seek(seek_key);
|
||||
|
||||
while (iter->Valid()) {
|
||||
keys_with_tuning++;
|
||||
iter->Next();
|
||||
}
|
||||
|
||||
uint64_t readahead_trimmed =
|
||||
options.statistics->getAndResetTickerCount(READAHEAD_TRIMMED);
|
||||
ASSERT_GT(readahead_trimmed, 0);
|
||||
buff_count_with_tuning = buff_prefetch_count;
|
||||
}
|
||||
|
||||
// Reseek with new upper_bound_iterator.
|
||||
{
|
||||
ub = Slice("my_key_y");
|
||||
Slice reseek_key = Slice("my_key_v");
|
||||
iter->Seek(reseek_key);
|
||||
|
||||
while (iter->Valid()) {
|
||||
iter->Next();
|
||||
reseek_keys_with_tuning++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
uint64_t readahead_trimmed =
|
||||
options.statistics->getAndResetTickerCount(READAHEAD_TRIMMED);
|
||||
ASSERT_GT(readahead_trimmed, 0);
|
||||
ASSERT_GT(reseek_keys_with_tuning, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Without tuning readahead_size
|
||||
{
|
||||
Slice ub = Slice("my_key_uuu");
|
||||
Slice* ub_ptr = &ub;
|
||||
ropts.iterate_upper_bound = ub_ptr;
|
||||
buff_prefetch_count = 0;
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
ropts.auto_readahead_size = false;
|
||||
|
||||
auto iter = std::unique_ptr<Iterator>(db_->NewIterator(ropts));
|
||||
|
||||
// Seek.
|
||||
{
|
||||
Slice seek_key = Slice("my_key_aaa");
|
||||
iter->Seek(seek_key);
|
||||
|
||||
while (iter->Valid()) {
|
||||
keys_without_tuning++;
|
||||
iter->Next();
|
||||
}
|
||||
buff_count_without_tuning = buff_prefetch_count;
|
||||
uint64_t readahead_trimmed =
|
||||
options.statistics->getAndResetTickerCount(READAHEAD_TRIMMED);
|
||||
ASSERT_EQ(readahead_trimmed, 0);
|
||||
}
|
||||
|
||||
// Reseek with new upper_bound_iterator.
|
||||
{
|
||||
ub = Slice("my_key_y");
|
||||
Slice reseek_key = Slice("my_key_v");
|
||||
iter->Seek(reseek_key);
|
||||
|
||||
while (iter->Valid()) {
|
||||
iter->Next();
|
||||
reseek_keys_without_tuning++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
uint64_t readahead_trimmed =
|
||||
options.statistics->getAndResetTickerCount(READAHEAD_TRIMMED);
|
||||
ASSERT_EQ(readahead_trimmed, 0);
|
||||
ASSERT_GT(reseek_keys_without_tuning, 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Verify results with and without tuning.
|
||||
if (std::get<1>(GetParam())) {
|
||||
// In case of async_io.
|
||||
ASSERT_GE(buff_count_with_tuning, buff_count_without_tuning);
|
||||
} else {
|
||||
ASSERT_EQ(buff_count_without_tuning, buff_count_with_tuning);
|
||||
}
|
||||
// Prefetching should happen.
|
||||
ASSERT_GT(buff_count_without_tuning, 0);
|
||||
ASSERT_GT(buff_count_with_tuning, 0);
|
||||
// No of keys should be equal.
|
||||
ASSERT_EQ(keys_without_tuning, keys_with_tuning);
|
||||
// No of keys after reseek with new upper bound should be equal.
|
||||
ASSERT_EQ(reseek_keys_without_tuning, reseek_keys_with_tuning);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks if readahead_size is trimmed when upper_bound is reached
|
||||
// during Seek in async_io and it goes for polling without any extra
|
||||
// prefetching.
|
||||
TEST_P(PrefetchTest, IterReadAheadSizeWithUpperBoundSeekOnly) {
|
||||
if (mem_env_ || encrypted_env_) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment");
|
||||
return;
|
||||
}
|
||||
|
||||
// First param is if the mockFS support_prefetch or not
|
||||
std::shared_ptr<MockFS> fs =
|
||||
std::make_shared<MockFS>(FileSystem::Default(), false);
|
||||
|
||||
bool use_direct_io = false;
|
||||
if (std::get<0>(GetParam())) {
|
||||
use_direct_io = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, fs));
|
||||
Options options;
|
||||
SetGenericOptions(env.get(), use_direct_io, options);
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
SetBlockBasedTableOptions(table_options);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
Status s = TryReopen(options);
|
||||
if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) {
|
||||
// If direct IO is not supported, skip the test
|
||||
return;
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
Random rnd(309);
|
||||
WriteBatch batch;
|
||||
|
||||
for (int i = 0; i < 26; i++) {
|
||||
std::string key = "my_key_";
|
||||
|
||||
for (int j = 0; j < 10; j++) {
|
||||
key += char('a' + i);
|
||||
ASSERT_OK(batch.Put(key, rnd.RandomString(1000)));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
|
||||
std::string start_key = "my_key_a";
|
||||
|
||||
std::string end_key = "my_key_";
|
||||
for (int j = 0; j < 10; j++) {
|
||||
end_key += char('a' + 25);
|
||||
}
|
||||
|
||||
Slice least(start_key.data(), start_key.size());
|
||||
Slice greatest(end_key.data(), end_key.size());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest));
|
||||
|
||||
s = TryReopen(options);
|
||||
ASSERT_OK(s);
|
||||
|
||||
int buff_count_with_tuning = 0;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FilePrefetchBuffer::PrefetchAsyncInternal:Start",
|
||||
[&](void*) { buff_count_with_tuning++; });
|
||||
|
||||
bool read_async_called = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"UpdateResults::io_uring_result",
|
||||
[&](void* /*arg*/) { read_async_called = true; });
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ReadOptions ropts;
|
||||
if (std::get<1>(GetParam())) {
|
||||
ropts.readahead_size = 32768;
|
||||
}
|
||||
ropts.async_io = true;
|
||||
|
||||
Slice ub = Slice("my_key_aaa");
|
||||
ropts.iterate_upper_bound = &ub;
|
||||
Slice seek_key = Slice("my_key_aaa");
|
||||
|
||||
// With tuning readahead_size.
|
||||
{
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
ropts.auto_readahead_size = true;
|
||||
|
||||
auto iter = std::unique_ptr<Iterator>(db_->NewIterator(ropts));
|
||||
|
||||
iter->Seek(seek_key);
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Verify results.
|
||||
uint64_t readhahead_trimmed =
|
||||
options.statistics->getAndResetTickerCount(READAHEAD_TRIMMED);
|
||||
// Readahead got trimmed.
|
||||
if (read_async_called) {
|
||||
ASSERT_GT(readhahead_trimmed, 0);
|
||||
// Seek called PrefetchAsync to poll the data.
|
||||
ASSERT_EQ(1, buff_count_with_tuning);
|
||||
} else {
|
||||
// async_io disabled.
|
||||
ASSERT_GE(readhahead_trimmed, 0);
|
||||
ASSERT_EQ(0, buff_count_with_tuning);
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
namespace {
|
||||
#ifdef GFLAGS
|
||||
const int kMaxArgCount = 100;
|
||||
@@ -3239,7 +2927,7 @@ TEST_F(FilePrefetchBufferTest, SeekWithBlockCacheHit) {
|
||||
std::unique_ptr<RandomAccessFileReader> r;
|
||||
Read(fname, opts, &r);
|
||||
|
||||
FilePrefetchBuffer fpb(16384, 16384, true, false, false, 0, 0, 0, fs());
|
||||
FilePrefetchBuffer fpb(16384, 16384, true, false, false, 0, 0, fs());
|
||||
Slice result;
|
||||
// Simulate a seek of 4096 bytes at offset 0. Due to the readahead settings,
|
||||
// it will do two reads of 4096+8192 and 8192
|
||||
@@ -3288,8 +2976,7 @@ TEST_F(FilePrefetchBufferTest, SeekWithoutAlignment) {
|
||||
FilePrefetchBuffer fpb(
|
||||
/*readahead_size=*/8192, /*max_readahead_size=*/16384, /*enable=*/true,
|
||||
/*track_min_offset=*/false, /*implicit_auto_readahead=*/true,
|
||||
/*num_file_reads=*/0, /*num_file_reads_for_auto_readahead=*/2,
|
||||
/*upper_bound_offset=*/0, fs());
|
||||
/*num_file_reads=*/0, /*num_file_reads_for_auto_readahead=*/2, fs());
|
||||
|
||||
Slice result;
|
||||
// Simulate a seek of half of alignment bytes at offset n. Due to the
|
||||
@@ -3320,8 +3007,7 @@ TEST_F(FilePrefetchBufferTest, SeekWithoutAlignment) {
|
||||
FilePrefetchBuffer fpb(
|
||||
/*readahead_size=*/16384, /*max_readahead_size=*/16384, /*enable=*/true,
|
||||
/*track_min_offset=*/false, /*implicit_auto_readahead=*/false,
|
||||
/*num_file_reads=*/0, /*num_file_reads_for_auto_readahead=*/2,
|
||||
/*upper_bound_offset=*/0, fs());
|
||||
/*num_file_reads=*/0, /*num_file_reads_for_auto_readahead=*/2, fs());
|
||||
|
||||
Slice result;
|
||||
// Simulate a seek of half of alignment bytes at offset n.
|
||||
@@ -3357,8 +3043,7 @@ TEST_F(FilePrefetchBufferTest, NoSyncWithAsyncIO) {
|
||||
FilePrefetchBuffer fpb(
|
||||
/*readahead_size=*/8192, /*max_readahead_size=*/16384, /*enable=*/true,
|
||||
/*track_min_offset=*/false, /*implicit_auto_readahead=*/false,
|
||||
/*num_file_reads=*/0, /*num_file_reads_for_auto_readahead=*/0,
|
||||
/*upper_bound_offset=*/0, fs());
|
||||
/*num_file_reads=*/0, /*num_file_reads_for_auto_readahead=*/0, fs());
|
||||
|
||||
int read_async_called = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -3396,63 +3081,6 @@ TEST_F(FilePrefetchBufferTest, NoSyncWithAsyncIO) {
|
||||
ASSERT_EQ(result, async_result);
|
||||
}
|
||||
|
||||
// This test checks if during seek in async_io, if first buffer already
|
||||
// prefetched the data till upper_bound offset, second buffer shouldn't go for
|
||||
// prefetching.
|
||||
TEST_F(FilePrefetchBufferTest, IterateUpperBoundTest1) {
|
||||
std::string fname = "iterate-upperbound-test1";
|
||||
Random rand(0);
|
||||
std::string content = rand.RandomString(32768);
|
||||
Write(fname, content);
|
||||
|
||||
FileOptions opts;
|
||||
std::unique_ptr<RandomAccessFileReader> r;
|
||||
Read(fname, opts, &r);
|
||||
|
||||
FilePrefetchBuffer fpb(
|
||||
/*readahead_size=*/8192, /*max_readahead_size=*/16384, /*enable=*/true,
|
||||
/*track_min_offset=*/false, /*implicit_auto_readahead=*/false,
|
||||
/*num_file_reads=*/0, /*num_file_reads_for_auto_readahead=*/0,
|
||||
/*upper_bound_offset=*/8000, fs());
|
||||
|
||||
int read_async_called = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FilePrefetchBuffer::ReadAsync",
|
||||
[&](void* /*arg*/) { read_async_called++; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Slice async_result;
|
||||
// Simulate a seek of 4000 bytes at offset 3000. Due to the readahead
|
||||
// settings, it will do 1 read of 4000+1000 (till 8000 - upper bound).
|
||||
Status s = fpb.PrefetchAsync(IOOptions(), r.get(), 3000, 4000, &async_result);
|
||||
|
||||
// Platforms that don't have IO uring may not support async IO
|
||||
if (s.IsNotSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s.IsTryAgain());
|
||||
IOOptions io_opts;
|
||||
io_opts.rate_limiter_priority = Env::IOPriority::IO_LOW;
|
||||
ASSERT_TRUE(fpb.TryReadFromCacheAsync(io_opts, r.get(), /*offset=*/3000,
|
||||
/*length=*/4000, &async_result, &s));
|
||||
// No sync call should be made.
|
||||
HistogramData sst_read_micros;
|
||||
stats()->histogramData(SST_READ_MICROS, &sst_read_micros);
|
||||
ASSERT_EQ(sst_read_micros.count, 0);
|
||||
|
||||
// Number of async calls should be 1.
|
||||
// No Prefetching should happen in second buffer as first buffer has already
|
||||
// prefetched till offset.
|
||||
ASSERT_EQ(read_async_called, 1);
|
||||
// Length should be 4000.
|
||||
ASSERT_EQ(async_result.size(), 4000);
|
||||
// Data correctness.
|
||||
Slice result(&content[3000], 4000);
|
||||
ASSERT_EQ(result.size(), 4000);
|
||||
ASSERT_EQ(result, async_result);
|
||||
}
|
||||
|
||||
TEST_F(FilePrefetchBufferTest, SyncReadaheadStats) {
|
||||
std::string fname = "seek-with-block-cache-hit";
|
||||
Random rand(0);
|
||||
@@ -3464,7 +3092,7 @@ TEST_F(FilePrefetchBufferTest, SyncReadaheadStats) {
|
||||
Read(fname, opts, &r);
|
||||
|
||||
std::shared_ptr<Statistics> stats = CreateDBStatistics();
|
||||
FilePrefetchBuffer fpb(8192, 8192, true, false, false, 0, 0, 0, fs(), nullptr,
|
||||
FilePrefetchBuffer fpb(8192, 8192, true, false, false, 0, 0, fs(), nullptr,
|
||||
stats.get());
|
||||
Slice result;
|
||||
// Simulate a seek of 4096 bytes at offset 0. Due to the readahead settings,
|
||||
|
||||
@@ -492,8 +492,9 @@ IOStatus RandomAccessFileReader::ReadAsync(
|
||||
auto read_async_callback =
|
||||
std::bind(&RandomAccessFileReader::ReadAsyncCallback, this,
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
ReadAsyncInfo* read_async_info =
|
||||
new ReadAsyncInfo(cb, cb_arg, clock_->NowMicros());
|
||||
|
||||
ReadAsyncInfo* read_async_info = new ReadAsyncInfo(
|
||||
cb, cb_arg, (clock_ != nullptr ? clock_->NowMicros() : 0));
|
||||
|
||||
if (ShouldNotifyListeners()) {
|
||||
read_async_info->fs_start_ts_ = FileOperationInfo::StartNow();
|
||||
|
||||
@@ -425,7 +425,9 @@ TEST(FSReadRequest, TryMerge) {
|
||||
src.scratch = nullptr;
|
||||
ASSERT_OK(src.status);
|
||||
|
||||
if (reverse) std::swap(dest, src);
|
||||
if (reverse) {
|
||||
std::swap(dest, src);
|
||||
}
|
||||
ASSERT_TRUE(TryMerge(&dest, src));
|
||||
ASSERT_EQ(dest.offset, 0);
|
||||
ASSERT_EQ(dest.len, 10);
|
||||
@@ -448,7 +450,9 @@ TEST(FSReadRequest, TryMerge) {
|
||||
src.scratch = nullptr;
|
||||
ASSERT_OK(src.status);
|
||||
|
||||
if (reverse) std::swap(dest, src);
|
||||
if (reverse) {
|
||||
std::swap(dest, src);
|
||||
}
|
||||
ASSERT_TRUE(TryMerge(&dest, src));
|
||||
ASSERT_EQ(dest.offset, 0);
|
||||
ASSERT_EQ(dest.len, 10);
|
||||
@@ -471,7 +475,9 @@ TEST(FSReadRequest, TryMerge) {
|
||||
src.scratch = nullptr;
|
||||
ASSERT_OK(src.status);
|
||||
|
||||
if (reverse) std::swap(dest, src);
|
||||
if (reverse) {
|
||||
std::swap(dest, src);
|
||||
}
|
||||
ASSERT_TRUE(TryMerge(&dest, src));
|
||||
ASSERT_EQ(dest.offset, 0);
|
||||
ASSERT_EQ(dest.len, 10);
|
||||
|
||||
+41
-3
@@ -380,9 +380,6 @@ inline std::shared_ptr<SecondaryCache> NewCompressedSecondaryCache(
|
||||
// to find the appropriate balance automatically.
|
||||
// * Cache priorities are less aggressively enforced, which could cause
|
||||
// cache dilution from long range scans (unless they use fill_cache=false).
|
||||
// * Can be worse for small caches, because if almost all of a cache shard is
|
||||
// pinned (more likely with non-partitioned filters), then CLOCK eviction
|
||||
// becomes very CPU intensive.
|
||||
//
|
||||
// See internal cache/clock_cache.h for full description.
|
||||
struct HyperClockCacheOptions : public ShardedCacheOptions {
|
||||
@@ -441,6 +438,43 @@ struct HyperClockCacheOptions : public ShardedCacheOptions {
|
||||
// load factor for efficient Lookup, Insert, etc.
|
||||
size_t min_avg_entry_charge = 450;
|
||||
|
||||
// A tuning parameter to cap eviction CPU usage in a "thrashing" situation
|
||||
// by allowing the memory capacity to be exceeded slightly as needed. The
|
||||
// default setting should offer balanced protection against excessive CPU
|
||||
// and memory usage under extreme stress conditions, with no effect on
|
||||
// normal operation. Such stress conditions are proportionally more likely
|
||||
// with small caches (10s of MB or less) vs. large caches (GB-scale).
|
||||
// (NOTE: With the unusual setting of strict_capacity_limit=true, this
|
||||
// parameter is ignored.)
|
||||
//
|
||||
// BACKGROUND: Without some kind of limiter, inserting into a CLOCK-based
|
||||
// cache with no evictable entries (all "pinned") requires scanning the
|
||||
// entire cache to determine that nothing can be evicted. (By contrast,
|
||||
// LRU caches can determine no entries are evictable in O(1) time, but
|
||||
// require more synchronization/coordination on that eviction metadata.)
|
||||
// This aspect of a CLOCK cache can make a stressed situation worse by
|
||||
// bogging down the CPU with repeated scans of the cache. And with
|
||||
// strict_capacity_limit=false (normal setting), finding something evictable
|
||||
// doesn't change the outcome of insertion: the entry is inserted anyway
|
||||
// and the cache is allowed to exceed its target capacity if necessary.
|
||||
//
|
||||
// SOLUTION: Eviction is aborted upon seeing some number of pinned
|
||||
// entries before evicting anything, or if the ratio of pinned to evicted
|
||||
// is too high. This setting `eviction_effort_cap` essentially controls both
|
||||
// that allowed initial number of pinned entries and the maximum allowed
|
||||
// ratio. As the pinned size approaches the target cache capacity, roughly
|
||||
// 1/eviction_effort_cap additional portion of the capacity might be kept
|
||||
// in memory and evictable in order to keep CLOCK eviction reasonably
|
||||
// performant. Under the default setting and high stress conditions, this
|
||||
// memory overhead is around 3-5%. Under normal or even moderate stress
|
||||
// conditions, the memory overhead is negligible to zero.
|
||||
//
|
||||
// A large value like 1000 offers some protection with essentially no
|
||||
// memory overhead, while the minimum value of 1 could be useful for a
|
||||
// small cache where roughly doubling in size under stress could be OK to
|
||||
// keep operations very fast.
|
||||
int eviction_effort_cap = 30;
|
||||
|
||||
HyperClockCacheOptions(
|
||||
size_t _capacity, size_t _estimated_entry_charge,
|
||||
int _num_shard_bits = -1, bool _strict_capacity_limit = false,
|
||||
@@ -499,6 +533,10 @@ enum TieredAdmissionPolicy {
|
||||
// allocations costed to the block cache, will be distributed
|
||||
// proportionally across both the primary and secondary.
|
||||
struct TieredCacheOptions {
|
||||
// This should point to an instance of either LRUCacheOptions or
|
||||
// HyperClockCacheOptions, depending on the cache_type. In either
|
||||
// case, the capacity and secondary_cache fields in those options
|
||||
// should not be set. If set, they will be ignored by NewTieredCache.
|
||||
ShardedCacheOptions* cache_opts = nullptr;
|
||||
PrimaryCacheType cache_type = PrimaryCacheType::kCacheTypeLRU;
|
||||
TieredAdmissionPolicy adm_policy = TieredAdmissionPolicy::kAdmPolicyAuto;
|
||||
|
||||
@@ -1737,23 +1737,17 @@ struct ReadOptions {
|
||||
// Default: empty (every table will be scanned)
|
||||
std::function<bool(const TableProperties&)> table_filter;
|
||||
|
||||
// Experimental
|
||||
//
|
||||
// If auto_readahead_size is set to true, it will auto tune the readahead_size
|
||||
// during scans internally.
|
||||
// For this feature to enabled, iterate_upper_bound must also be specified.
|
||||
//
|
||||
// NOTE: - Recommended for forward Scans only.
|
||||
// - In case of backward scans like Prev or SeekForPrev, the
|
||||
// cost of these backward operations might increase and affect the
|
||||
// performace. So this option should not be enabled if workload
|
||||
// contains backward scans.
|
||||
// - If there is a backward scans, this option will be
|
||||
// disabled internally and won't be reset if forward scan is done
|
||||
// again.
|
||||
// disabled internally and won't be enabled again if the forward scan
|
||||
// is issued again.
|
||||
//
|
||||
// Default: false
|
||||
bool auto_readahead_size = false;
|
||||
// Default: true
|
||||
bool auto_readahead_size = true;
|
||||
|
||||
// *** END options only relevant to iterators or scans ***
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ class SecondaryCache : public Customizable {
|
||||
virtual std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool wait, bool advise_erase,
|
||||
bool& kept_in_sec_cache) = 0;
|
||||
Statistics* stats, bool& kept_in_sec_cache) = 0;
|
||||
|
||||
// Indicate whether a handle can be erased in this secondary cache.
|
||||
[[nodiscard]] virtual bool SupportForceErase() const = 0;
|
||||
@@ -176,9 +176,9 @@ class SecondaryCacheWrapper : public SecondaryCache {
|
||||
virtual std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool wait, bool advise_erase,
|
||||
bool& kept_in_sec_cache) override {
|
||||
Statistics* stats, bool& kept_in_sec_cache) override {
|
||||
return target()->Lookup(key, helper, create_context, wait, advise_erase,
|
||||
kept_in_sec_cache);
|
||||
stats, kept_in_sec_cache);
|
||||
}
|
||||
|
||||
virtual bool SupportForceErase() const override {
|
||||
|
||||
@@ -531,6 +531,12 @@ enum Tickers : uint32_t {
|
||||
// Number of FS reads avoided due to scan prefetching
|
||||
PREFETCH_HITS,
|
||||
|
||||
// Compressed secondary cache related stats
|
||||
COMPRESSED_SECONDARY_CACHE_DUMMY_HITS,
|
||||
COMPRESSED_SECONDARY_CACHE_HITS,
|
||||
COMPRESSED_SECONDARY_CACHE_PROMOTIONS,
|
||||
COMPRESSED_SECONDARY_CACHE_PROMOTION_SKIPS,
|
||||
|
||||
TICKER_ENUM_MAX
|
||||
};
|
||||
|
||||
|
||||
@@ -140,8 +140,7 @@ class TablePropertiesCollector {
|
||||
virtual bool NeedCompact() const { return false; }
|
||||
};
|
||||
|
||||
// Constructs TablePropertiesCollector. Internals create a new
|
||||
// TablePropertiesCollector for each new table
|
||||
// Constructs TablePropertiesCollector instances for each table file creation.
|
||||
//
|
||||
// Exceptions MUST NOT propagate out of overridden functions into RocksDB,
|
||||
// because RocksDB is not exception-safe. This could cause undefined behavior
|
||||
@@ -163,7 +162,12 @@ class TablePropertiesCollectorFactory : public Customizable {
|
||||
const ConfigOptions& options, const std::string& value,
|
||||
std::shared_ptr<TablePropertiesCollectorFactory>* result);
|
||||
|
||||
// has to be thread-safe
|
||||
// To collect properties of a table with the given context, returns
|
||||
// a new object inheriting from TablePropertiesCollector. The caller
|
||||
// is responsible for deleting the object returned. Alternatively,
|
||||
// nullptr may be returned to decline collecting properties for the
|
||||
// file (and reduce callback overheads).
|
||||
// MUST be thread-safe.
|
||||
virtual TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollectorFactory::Context context) = 0;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
// inserted.
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -30,6 +29,7 @@ class DB;
|
||||
class ReadCallback;
|
||||
struct ReadOptions;
|
||||
struct DBOptions;
|
||||
class MergeContext;
|
||||
|
||||
enum WriteType {
|
||||
kPutRecord,
|
||||
@@ -39,11 +39,12 @@ enum WriteType {
|
||||
kDeleteRangeRecord,
|
||||
kLogDataRecord,
|
||||
kXIDRecord,
|
||||
kPutEntityRecord,
|
||||
kUnknownRecord,
|
||||
};
|
||||
|
||||
// an entry for Put, Merge, Delete, or SingleDelete entry for write batches.
|
||||
// Used in WBWIIterator.
|
||||
// An entry for Put, PutEntity, Merge, Delete, or SingleDelete for write
|
||||
// batches. Used in WBWIIterator.
|
||||
struct WriteEntry {
|
||||
WriteType type = kUnknownRecord;
|
||||
Slice key;
|
||||
@@ -77,12 +78,11 @@ class WBWIIterator {
|
||||
};
|
||||
|
||||
// A WriteBatchWithIndex with a binary searchable index built for all the keys
|
||||
// inserted.
|
||||
// In Put(), Merge() Delete(), or SingleDelete(), the same function of the
|
||||
// wrapped will be called. At the same time, indexes will be built.
|
||||
// By calling GetWriteBatch(), a user will get the WriteBatch for the data
|
||||
// they inserted, which can be used for DB::Write().
|
||||
// A user can call NewIterator() to create an iterator.
|
||||
// inserted. In Put(), PutEntity(), Merge(), Delete(), or SingleDelete(), the
|
||||
// corresponding function of the wrapped WriteBatch will be called. At the same
|
||||
// time, indexes will be built. By calling GetWriteBatch(), a user will get the
|
||||
// WriteBatch for the data they inserted, which can be used for DB::Write(). A
|
||||
// user can call NewIterator() to create an iterator.
|
||||
class WriteBatchWithIndex : public WriteBatchBase {
|
||||
public:
|
||||
// backup_index_comparator: the backup comparator used to compare keys
|
||||
@@ -121,14 +121,7 @@ class WriteBatchWithIndex : public WriteBatchBase {
|
||||
}
|
||||
|
||||
Status PutEntity(ColumnFamilyHandle* column_family, const Slice& /* key */,
|
||||
const WideColumns& /* columns */) override {
|
||||
if (!column_family) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call this method without a column family handle");
|
||||
}
|
||||
return Status::NotSupported(
|
||||
"PutEntity not supported by WriteBatchWithIndex");
|
||||
}
|
||||
const WideColumns& /* columns */) override;
|
||||
|
||||
Status PutEntity(const Slice& /* key */,
|
||||
const AttributeGroups& attribute_groups) override {
|
||||
@@ -236,6 +229,8 @@ class WriteBatchWithIndex : public WriteBatchBase {
|
||||
return GetFromBatch(nullptr, options, key, value);
|
||||
}
|
||||
|
||||
// TODO: implement GetEntityFromBatch
|
||||
|
||||
// Similar to DB::Get() but will also read writes from this batch.
|
||||
//
|
||||
// This function will query both this batch and the DB and then merge
|
||||
@@ -262,21 +257,24 @@ class WriteBatchWithIndex : public WriteBatchBase {
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value);
|
||||
|
||||
// TODO: implement GetEntityFromBatchAndDB
|
||||
|
||||
void MultiGetFromBatchAndDB(DB* db, const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
bool sorted_input);
|
||||
|
||||
// TODO: implement MultiGetEntityFromBatchAndDB
|
||||
|
||||
// Records the state of the batch for future calls to RollbackToSavePoint().
|
||||
// May be called multiple times to set multiple save points.
|
||||
void SetSavePoint() override;
|
||||
|
||||
// Remove all entries in this batch (Put, Merge, Delete, SingleDelete,
|
||||
// PutLogData) since the most recent call to SetSavePoint() and removes the
|
||||
// most recent save point.
|
||||
// If there is no previous call to SetSavePoint(), behaves the same as
|
||||
// Clear().
|
||||
// Remove all entries in this batch (Put, PutEntity, Merge, Delete,
|
||||
// SingleDelete, PutLogData) since the most recent call to SetSavePoint() and
|
||||
// removes the most recent save point. If there is no previous call to
|
||||
// SetSavePoint(), behaves the same as Clear().
|
||||
//
|
||||
// Calling RollbackToSavePoint invalidates any open iterators on this batch.
|
||||
//
|
||||
@@ -305,6 +303,11 @@ class WriteBatchWithIndex : public WriteBatchBase {
|
||||
// last sub-batch.
|
||||
size_t SubBatchCnt();
|
||||
|
||||
void MergeAcrossBatchAndDB(ColumnFamilyHandle* column_family,
|
||||
const Slice& key,
|
||||
const PinnableWideColumns& existing,
|
||||
const MergeContext& merge_context,
|
||||
PinnableSlice* value, Status* status);
|
||||
Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, ReadCallback* callback);
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
// NOTE: in 'main' development branch, this should be the *next*
|
||||
// minor or major version number planned for release.
|
||||
#define ROCKSDB_MAJOR 8
|
||||
#define ROCKSDB_MINOR 9
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 10
|
||||
#define ROCKSDB_PATCH 2
|
||||
|
||||
// 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
|
||||
|
||||
@@ -168,6 +168,7 @@ set(JAVA_MAIN_CLASSES
|
||||
src/main/java/org/rocksdb/FlushJobInfo.java
|
||||
src/main/java/org/rocksdb/FlushReason.java
|
||||
src/main/java/org/rocksdb/FlushOptions.java
|
||||
src/main/java/org/rocksdb/GetStatus.java
|
||||
src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java
|
||||
src/main/java/org/rocksdb/HashSkipListMemTableConfig.java
|
||||
src/main/java/org/rocksdb/HistogramData.java
|
||||
@@ -285,6 +286,7 @@ set(JAVA_MAIN_CLASSES
|
||||
src/main/java/org/rocksdb/WriteBufferManager.java
|
||||
src/main/java/org/rocksdb/WriteStallCondition.java
|
||||
src/main/java/org/rocksdb/WriteStallInfo.java
|
||||
src/main/java/org/rocksdb/util/BufferUtil.java
|
||||
src/main/java/org/rocksdb/util/ByteUtil.java
|
||||
src/main/java/org/rocksdb/util/BytewiseComparator.java
|
||||
src/main/java/org/rocksdb/util/Environment.java
|
||||
|
||||
@@ -8,16 +8,16 @@ Mac
|
||||
```
|
||||
make clean jclean
|
||||
DEBUG_LEVEL=0 make -j12 rocksdbjava
|
||||
(cd java/target; cp rocksdbjni-7.9.0-osx.jar rocksdbjni-7.9.0-SNAPSHOT-osx.jar)
|
||||
mvn install:install-file -Dfile=./java/target/rocksdbjni-7.9.0-SNAPSHOT-osx.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=7.9.0-SNAPSHOT -Dpackaging=jar
|
||||
(cd java/target; cp rocksdbjni-7.10.0-osx.jar rocksdbjni-7.10.0-SNAPSHOT-osx.jar)
|
||||
mvn install:install-file -Dfile=./java/target/rocksdbjni-7.10.0-SNAPSHOT-osx.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=7.10.0-SNAPSHOT -Dpackaging=jar
|
||||
```
|
||||
|
||||
Linux
|
||||
```
|
||||
make clean jclean
|
||||
DEBUG_LEVEL=0 make -j12 rocksdbjava
|
||||
(cd java/target; cp rocksdbjni-7.9.0-linux64.jar rocksdbjni-7.9.0-SNAPSHOT-linux64.jar)
|
||||
mvn install:install-file -Dfile=./java/target/rocksdbjni-7.9.0-SNAPSHOT-linux64.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=7.9.0-SNAPSHOT -Dpackaging=jar
|
||||
(cd java/target; cp rocksdbjni-7.10.0-linux64.jar rocksdbjni-7.10.0-SNAPSHOT-linux64.jar)
|
||||
mvn install:install-file -Dfile=./java/target/rocksdbjni-7.10.0-SNAPSHOT-linux64.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=7.10.0-SNAPSHOT -Dpackaging=jar
|
||||
```
|
||||
|
||||
Build jmh test package, on either platform
|
||||
@@ -35,31 +35,10 @@ The long performance run (as big as we can make it on our Ubuntu box without fil
|
||||
java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000,50000 -p keySize=128 -p valueSize=1024,16384 -p columnFamilyTestType="1_column_family","20_column_families" GetBenchmarks.get GetBenchmarks.preallocatedByteBufferGet GetBenchmarks.preallocatedGet
|
||||
```
|
||||
|
||||
## Results (small runs, Mac)
|
||||
|
||||
These are run on a 10-core M1 with 64GB of memory and 2TB of SSD.
|
||||
They probably reflect the absolute best case for this optimization, hitting in-memory buffers and completely eliminating a buffer copy.
|
||||
|
||||
### Before
|
||||
Benchmark (columnFamilyTestType) (keyCount) (keySize) (multiGetSize) (valueSize) Mode Cnt Score Error Units
|
||||
GetBenchmarks.get no_column_family 1000 128 N/A 32768 thrpt 25 43496.578 ± 5743.090 ops/s
|
||||
GetBenchmarks.preallocatedByteBufferGet no_column_family 1000 128 N/A 32768 thrpt 25 70765.578 ± 697.548 ops/s
|
||||
GetBenchmarks.preallocatedGet no_column_family 1000 128 N/A 32768 thrpt 25 69883.554 ± 944.184 ops/s
|
||||
|
||||
### After fixing byte[] (.get and .preallocatedGet)
|
||||
|
||||
Benchmark (columnFamilyTestType) (keyCount) (keySize) (multiGetSize) (valueSize) Mode Cnt Score Error Units
|
||||
GetBenchmarks.get no_column_family 1000 128 N/A 32768 thrpt 25 149207.681 ± 2261.671 ops/s
|
||||
GetBenchmarks.preallocatedByteBufferGet no_column_family 1000 128 N/A 32768 thrpt 25 68920.489 ± 1574.664 ops/s
|
||||
GetBenchmarks.preallocatedGet no_column_family 1000 128 N/A 32768 thrpt 25 177399.022 ± 2107.375 ops/s
|
||||
|
||||
### After fixing ByteBuffer (.preallocatedByteBufferGet)
|
||||
|
||||
Benchmark (columnFamilyTestType) (keyCount) (keySize) (multiGetSize) (valueSize) Mode Cnt Score Error Units
|
||||
GetBenchmarks.get no_column_family 1000 128 N/A 32768 thrpt 25 150389.259 ± 1371.473 ops/s
|
||||
GetBenchmarks.preallocatedByteBufferGet no_column_family 1000 128 N/A 32768 thrpt 25 179919.468 ± 1670.714 ops/s
|
||||
GetBenchmarks.preallocatedGet no_column_family 1000 128 N/A 32768 thrpt 25 178261.938 ± 2630.571 ops/s
|
||||
## Results (Ubuntu, big runs)
|
||||
|
||||
NB - we have removed some test results we initially observed on Mac which were not later reproducible.
|
||||
|
||||
These take 3-4 hours
|
||||
```
|
||||
java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000,50000 -p keySize=128 -p valueSize=1024,16384 -p columnFamilyTestType="1_column_family","20_column_families" GetBenchmarks.get GetBenchmarks.preallocatedByteBufferGet GetBenchmarks.preallocatedGet
|
||||
@@ -67,6 +46,13 @@ java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000,500
|
||||
It's clear that all `get()` variants have noticeably improved performance, though not the spectacular gains of the M1.
|
||||
### With fixes for all of the `get()` instances
|
||||
|
||||
The tests which use methods which have had performance improvements applied are:
|
||||
```java
|
||||
get()
|
||||
preallocatedGet()
|
||||
preallocatedByteBufferGet()
|
||||
```
|
||||
|
||||
Benchmark (columnFamilyTestType) (keyCount) (keySize) (valueSize) Mode Cnt Score Error Units
|
||||
GetBenchmarks.get 1_column_family 1000 128 1024 thrpt 25 935648.793 ± 22879.910 ops/s
|
||||
GetBenchmarks.get 1_column_family 1000 128 16384 thrpt 25 204366.301 ± 1326.570 ops/s
|
||||
@@ -159,3 +145,60 @@ GetBenchmarks.preallocatedGet no_column_families 1000
|
||||
|
||||
The performance improvement is real.
|
||||
|
||||
# Put Performance Benchmarks
|
||||
|
||||
Results associated with [Java API consistency between RocksDB.put() , .merge() and Transaction.put() , .merge()](https://github.com/facebook/rocksdb/pull/11019)
|
||||
|
||||
This work was not designed specifically as a performance optimization, but we want to confirm that it has not regressed what it has changed, and to provide
|
||||
a baseline for future possible performance work.
|
||||
|
||||
## Build/Run
|
||||
|
||||
Building is as above. Running is a different invocation of the same JMH jar.
|
||||
```
|
||||
java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000,50000 -p keySize=128 -p valueSize=1024,32768 -p columnFamilyTestType="no_column_family" PutBenchmarks
|
||||
```
|
||||
|
||||
## Before Changes
|
||||
|
||||
These results were generated in a private branch with the `PutBenchmarks` from the PR backported onto the current *main*.
|
||||
|
||||
Benchmark (bufferListSize) (columnFamilyTestType) (keyCount) (keySize) (valueSize) Mode Cnt Score Error Units
|
||||
PutBenchmarks.put 16 no_column_family 1000 128 1024 thrpt 25 76670.200 ± 2555.248 ops/s
|
||||
PutBenchmarks.put 16 no_column_family 1000 128 32768 thrpt 25 3913.692 ± 225.690 ops/s
|
||||
PutBenchmarks.put 16 no_column_family 50000 128 1024 thrpt 25 74479.589 ± 988.361 ops/s
|
||||
PutBenchmarks.put 16 no_column_family 50000 128 32768 thrpt 25 4070.800 ± 194.838 ops/s
|
||||
PutBenchmarks.putByteArrays 16 no_column_family 1000 128 1024 thrpt 25 72150.853 ± 1744.216 ops/s
|
||||
PutBenchmarks.putByteArrays 16 no_column_family 1000 128 32768 thrpt 25 3896.646 ± 188.629 ops/s
|
||||
PutBenchmarks.putByteArrays 16 no_column_family 50000 128 1024 thrpt 25 71753.287 ± 1053.904 ops/s
|
||||
PutBenchmarks.putByteArrays 16 no_column_family 50000 128 32768 thrpt 25 3928.503 ± 264.443 ops/s
|
||||
PutBenchmarks.putByteBuffers 16 no_column_family 1000 128 1024 thrpt 25 72595.105 ± 1027.258 ops/s
|
||||
PutBenchmarks.putByteBuffers 16 no_column_family 1000 128 32768 thrpt 25 3890.100 ± 199.131 ops/s
|
||||
PutBenchmarks.putByteBuffers 16 no_column_family 50000 128 1024 thrpt 25 70878.133 ± 1181.601 ops/s
|
||||
PutBenchmarks.putByteBuffers 16 no_column_family 50000 128 32768 thrpt 25 3863.181 ± 215.888 ops/s
|
||||
|
||||
## After Changes
|
||||
|
||||
These results were generated on the PR branch.
|
||||
|
||||
Benchmark (bufferListSize) (columnFamilyTestType) (keyCount) (keySize) (valueSize) Mode Cnt Score Error Units
|
||||
PutBenchmarks.put 16 no_column_family 1000 128 1024 thrpt 25 75178.751 ± 2644.775 ops/s
|
||||
PutBenchmarks.put 16 no_column_family 1000 128 32768 thrpt 25 3937.175 ± 257.039 ops/s
|
||||
PutBenchmarks.put 16 no_column_family 50000 128 1024 thrpt 25 74375.519 ± 1776.654 ops/s
|
||||
PutBenchmarks.put 16 no_column_family 50000 128 32768 thrpt 25 4013.413 ± 257.706 ops/s
|
||||
PutBenchmarks.putByteArrays 16 no_column_family 1000 128 1024 thrpt 25 71418.303 ± 1610.977 ops/s
|
||||
PutBenchmarks.putByteArrays 16 no_column_family 1000 128 32768 thrpt 25 4027.581 ± 227.900 ops/s
|
||||
PutBenchmarks.putByteArrays 16 no_column_family 50000 128 1024 thrpt 25 71229.107 ± 2720.083 ops/s
|
||||
PutBenchmarks.putByteArrays 16 no_column_family 50000 128 32768 thrpt 25 4022.635 ± 212.540 ops/s
|
||||
PutBenchmarks.putByteBuffers 16 no_column_family 1000 128 1024 thrpt 25 71718.501 ± 787.537 ops/s
|
||||
PutBenchmarks.putByteBuffers 16 no_column_family 1000 128 32768 thrpt 25 4078.050 ± 176.331 ops/s
|
||||
PutBenchmarks.putByteBuffers 16 no_column_family 50000 128 1024 thrpt 25 72736.754 ± 828.971 ops/s
|
||||
PutBenchmarks.putByteBuffers 16 no_column_family 50000 128 32768 thrpt 25 3987.232 ± 205.577 ops/s
|
||||
|
||||
## Discussion
|
||||
|
||||
The changes don't appear to have had a material effect on performance. We are happy with this.
|
||||
|
||||
* We would obviously advise running future changes before and after to confirm they have no adverse effects.
|
||||
|
||||
|
||||
@@ -150,7 +150,9 @@ JAVA_TESTS = \
|
||||
org.rocksdb.LRUCacheTest\
|
||||
org.rocksdb.MemoryUtilTest\
|
||||
org.rocksdb.MemTableTest\
|
||||
org.rocksdb.MergeCFVariantsTest\
|
||||
org.rocksdb.MergeTest\
|
||||
org.rocksdb.MergeVariantsTest\
|
||||
org.rocksdb.MultiColumnRegressionTest \
|
||||
org.rocksdb.MultiGetManyKeysTest\
|
||||
org.rocksdb.MultiGetTest\
|
||||
@@ -167,6 +169,8 @@ JAVA_TESTS = \
|
||||
org.rocksdb.OptionsTest\
|
||||
org.rocksdb.PerfLevelTest \
|
||||
org.rocksdb.PerfContextTest \
|
||||
org.rocksdb.PutCFVariantsTest\
|
||||
org.rocksdb.PutVariantsTest\
|
||||
org.rocksdb.PlainTableConfigTest\
|
||||
org.rocksdb.RateLimiterTest\
|
||||
org.rocksdb.ReadOnlyTest\
|
||||
|
||||
@@ -6,18 +6,19 @@
|
||||
*/
|
||||
package org.rocksdb.jmh;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.rocksdb.*;
|
||||
import org.rocksdb.util.FileUtils;
|
||||
import static org.rocksdb.util.KVUtils.ba;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.rocksdb.util.KVUtils.ba;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.rocksdb.*;
|
||||
import org.rocksdb.util.FileUtils;
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public class PutBenchmarks {
|
||||
@@ -30,12 +31,24 @@ public class PutBenchmarks {
|
||||
})
|
||||
String columnFamilyTestType;
|
||||
|
||||
@Param({"1000", "100000"}) int keyCount;
|
||||
|
||||
@Param({"12", "64", "128"}) int keySize;
|
||||
|
||||
@Param({"64", "1024", "65536"}) int valueSize;
|
||||
|
||||
@Param({"16"}) int bufferListSize;
|
||||
|
||||
Path dbDir;
|
||||
DBOptions options;
|
||||
int cfs = 0; // number of column families
|
||||
private AtomicInteger cfHandlesIdx;
|
||||
ColumnFamilyHandle[] cfHandles;
|
||||
RocksDB db;
|
||||
List<byte[]> keyBuffers = new ArrayList<>(bufferListSize);
|
||||
List<byte[]> valueBuffers = new ArrayList<>(bufferListSize);
|
||||
List<ByteBuffer> keyBuffersBB = new ArrayList<>(bufferListSize);
|
||||
List<ByteBuffer> valueBuffersBB = new ArrayList<>(bufferListSize);
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() throws IOException, RocksDBException {
|
||||
@@ -68,6 +81,34 @@ public class PutBenchmarks {
|
||||
final List<ColumnFamilyHandle> cfHandlesList = new ArrayList<>(cfDescriptors.size());
|
||||
db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList);
|
||||
cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]);
|
||||
|
||||
for (int i = 0; i < bufferListSize; i++) {
|
||||
final byte[] keyArr = new byte[keySize];
|
||||
Arrays.fill(keyArr, (byte) 0x30);
|
||||
keyBuffers.add(keyArr);
|
||||
}
|
||||
|
||||
for (int i = 0; i < bufferListSize; i++) {
|
||||
final byte[] valueArr = new byte[valueSize];
|
||||
Arrays.fill(valueArr, (byte) 0x30);
|
||||
valueBuffers.add(valueArr);
|
||||
}
|
||||
|
||||
for (int i = 0; i < bufferListSize; i++) {
|
||||
final ByteBuffer keyBB = ByteBuffer.allocateDirect(keySize);
|
||||
byte[] keyArr = new byte[keySize];
|
||||
Arrays.fill(keyArr, (byte) 0x30);
|
||||
keyBB.put(keyArr);
|
||||
keyBuffersBB.add(keyBB);
|
||||
}
|
||||
|
||||
for (int i = 0; i < bufferListSize; i++) {
|
||||
final ByteBuffer valueBB = ByteBuffer.allocateDirect(valueSize);
|
||||
byte[] valueArr = new byte[valueSize];
|
||||
Arrays.fill(valueArr, (byte) 0x30);
|
||||
valueBB.put(valueArr);
|
||||
valueBuffersBB.add(valueBB);
|
||||
}
|
||||
}
|
||||
|
||||
@TearDown(Level.Trial)
|
||||
@@ -104,9 +145,79 @@ public class PutBenchmarks {
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T borrow(final List<T> buffers) {
|
||||
synchronized (buffers) {
|
||||
while (true) {
|
||||
if (buffers.isEmpty()) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ie) {
|
||||
return null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return buffers.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void repay(final List<T> buffers, final T buffer) {
|
||||
synchronized (buffers) {
|
||||
buffers.add(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void put(final ComparatorBenchmarks.Counter counter) throws RocksDBException {
|
||||
public void put(final Counter counter) throws RocksDBException {
|
||||
byte[] keyBuf = borrow(keyBuffers);
|
||||
byte[] valueBuf = borrow(valueBuffers);
|
||||
|
||||
final int i = counter.next();
|
||||
db.put(getColumnFamily(), ba("key" + i), ba("value" + i));
|
||||
final byte[] keyPrefix = ba("key" + i);
|
||||
final byte[] valuePrefix = ba("value" + i);
|
||||
System.arraycopy(keyPrefix, 0, keyBuf, 0, keyPrefix.length);
|
||||
System.arraycopy(valuePrefix, 0, valueBuf, 0, valuePrefix.length);
|
||||
db.put(getColumnFamily(), keyBuf, valueBuf);
|
||||
|
||||
repay(keyBuffers, keyBuf);
|
||||
repay(valueBuffers, valueBuf);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void putByteArrays(final Counter counter) throws RocksDBException {
|
||||
byte[] keyBuf = borrow(keyBuffers);
|
||||
byte[] valueBuf = borrow(valueBuffers);
|
||||
|
||||
final int i = counter.next();
|
||||
final byte[] keyPrefix = ba("key" + i);
|
||||
final byte[] valuePrefix = ba("value" + i);
|
||||
System.arraycopy(keyPrefix, 0, keyBuf, 0, keyPrefix.length);
|
||||
System.arraycopy(valuePrefix, 0, valueBuf, 0, valuePrefix.length);
|
||||
db.put(getColumnFamily(), new WriteOptions(), keyBuf, valueBuf);
|
||||
|
||||
repay(keyBuffers, keyBuf);
|
||||
repay(valueBuffers, valueBuf);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void putByteBuffers(final Counter counter) throws RocksDBException {
|
||||
ByteBuffer keyBuf = borrow(keyBuffersBB);
|
||||
keyBuf.clear();
|
||||
ByteBuffer valueBuf = borrow(valueBuffersBB);
|
||||
valueBuf.clear();
|
||||
|
||||
final int i = counter.next();
|
||||
final byte[] keyPrefix = ba("key" + i);
|
||||
final byte[] valuePrefix = ba("value" + i);
|
||||
keyBuf.put(keyPrefix, 0, keyPrefix.length);
|
||||
keyBuf.position(keySize);
|
||||
keyBuf.flip();
|
||||
valueBuf.put(valuePrefix, 0, valuePrefix.length);
|
||||
valueBuf.position(valueSize);
|
||||
valueBuf.flip();
|
||||
db.put(getColumnFamily(), new WriteOptions(), keyBuf, valueBuf);
|
||||
|
||||
repay(keyBuffersBB, keyBuf);
|
||||
repay(valueBuffersBB, valueBuf);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
<exclude name="FieldNamingConventions"/>
|
||||
<exclude name="FormalParameterNamingConventions"/>
|
||||
<exclude name="LinguisticNaming"/>
|
||||
<exclude name="ConfusingTernary"/>
|
||||
<!-- These could be fixed if we take the time to do it -->
|
||||
<exclude name="CommentDefaultAccessModifier"/>
|
||||
<exclude name="FieldDeclarationsShouldBeAtStartOfClass"/>
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
// 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).
|
||||
//
|
||||
// This file defines helper methods for Java API write methods
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksjni/portal.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
/**
|
||||
* @brief Exception class used to make the flow of key/value (Put(), Get(),
|
||||
* Merge(), ...) calls clearer.
|
||||
*
|
||||
* This class is used by Java API JNI methods in try { save/fetch } catch { ...
|
||||
* } style.
|
||||
*
|
||||
*/
|
||||
class KVException : public std::exception {
|
||||
public:
|
||||
// These values are expected on Java API calls to represent the result of a
|
||||
// Get() which has failed; a negative length is returned to indicate an error.
|
||||
static const int kNotFound = -1; // the key was not found in RocksDB
|
||||
static const int kStatusError =
|
||||
-2; // there was some other error fetching the value for the key
|
||||
|
||||
/**
|
||||
* @brief Throw a KVException (and potentially a Java exception) if the
|
||||
* RocksDB status is "bad"
|
||||
*
|
||||
* @param env JNI environment needed to create a Java exception
|
||||
* @param status RocksDB status to examine
|
||||
*/
|
||||
static void ThrowOnError(JNIEnv* env, const Status& status) {
|
||||
if (status.ok()) {
|
||||
return;
|
||||
}
|
||||
if (status.IsNotFound()) {
|
||||
// IsNotFound does not generate a Java Exception, any other bad status
|
||||
// does..
|
||||
throw KVException(kNotFound);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status);
|
||||
throw KVException(kStatusError);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Throw a KVException and a Java exception
|
||||
*
|
||||
* @param env JNI environment needed to create a Java exception
|
||||
* @param message content of the exception we will throw
|
||||
*/
|
||||
static void ThrowNew(JNIEnv* env, const std::string& message) {
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, message);
|
||||
throw KVException(kStatusError);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Throw a KVException if there is already a Java exception in the JNI
|
||||
* enviroment
|
||||
*
|
||||
* @param env
|
||||
*/
|
||||
static void ThrowOnError(JNIEnv* env) {
|
||||
if (env->ExceptionCheck()) {
|
||||
throw KVException(kStatusError);
|
||||
}
|
||||
}
|
||||
|
||||
KVException(jint code) : kCode_(code){};
|
||||
|
||||
virtual const char* what() const throw() {
|
||||
return "Exception raised by JNI. There may be a Java exception in the "
|
||||
"JNIEnv. Please check!";
|
||||
}
|
||||
|
||||
jint Code() const { return kCode_; }
|
||||
|
||||
private:
|
||||
jint kCode_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Construct a slice with the contents of a Java byte array
|
||||
*
|
||||
* The slice refers to an array into which the Java byte array's whole region is
|
||||
* copied
|
||||
*/
|
||||
class JByteArraySlice {
|
||||
public:
|
||||
JByteArraySlice(JNIEnv* env, const jbyteArray& jarr, const jint jarr_off,
|
||||
const jint jarr_len)
|
||||
: arr_(new jbyte[jarr_len]),
|
||||
slice_(reinterpret_cast<char*>(arr_), jarr_len) {
|
||||
env->GetByteArrayRegion(jarr, jarr_off, jarr_len, arr_);
|
||||
KVException::ThrowOnError(env);
|
||||
};
|
||||
|
||||
~JByteArraySlice() {
|
||||
slice_.clear();
|
||||
delete[] arr_;
|
||||
};
|
||||
|
||||
Slice& slice() { return slice_; }
|
||||
|
||||
private:
|
||||
jbyte* arr_;
|
||||
Slice slice_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Construct a slice with the contents of a direct Java ByterBuffer
|
||||
*
|
||||
* The slice refers directly to the contents of the buffer, no copy is made.
|
||||
*
|
||||
*/
|
||||
class JDirectBufferSlice {
|
||||
public:
|
||||
JDirectBufferSlice(JNIEnv* env, const jobject& jbuffer,
|
||||
const jint jbuffer_off, const jint jbuffer_len)
|
||||
: slice_(static_cast<char*>(env->GetDirectBufferAddress(jbuffer)) +
|
||||
jbuffer_off,
|
||||
jbuffer_len) {
|
||||
KVException::ThrowOnError(env);
|
||||
jlong capacity = env->GetDirectBufferCapacity(jbuffer);
|
||||
if (capacity < jbuffer_off + jbuffer_len) {
|
||||
auto message = "Direct buffer offset " + std::to_string(jbuffer_off) +
|
||||
" + length " + std::to_string(jbuffer_len) +
|
||||
" exceeds capacity " + std::to_string(capacity);
|
||||
KVException::ThrowNew(env, message);
|
||||
slice_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
~JDirectBufferSlice() { slice_.clear(); };
|
||||
|
||||
Slice& slice() { return slice_; }
|
||||
|
||||
private:
|
||||
Slice slice_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wrap a pinnable slice with a method to retrieve the contents back into
|
||||
* Java
|
||||
*
|
||||
* The Java Byte Array version sets the byte array's region from the slice
|
||||
*/
|
||||
class JByteArrayPinnableSlice {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new JByteArrayPinnableSlice object referring to an
|
||||
* existing java byte buffer
|
||||
*
|
||||
* @param env
|
||||
* @param jbuffer
|
||||
* @param jbuffer_off
|
||||
* @param jbuffer_len
|
||||
*/
|
||||
JByteArrayPinnableSlice(JNIEnv* env, const jbyteArray& jbuffer,
|
||||
const jint jbuffer_off, const jint jbuffer_len)
|
||||
: env_(env),
|
||||
jbuffer_(jbuffer),
|
||||
jbuffer_off_(jbuffer_off),
|
||||
jbuffer_len_(jbuffer_len){};
|
||||
|
||||
/**
|
||||
* @brief Construct an empty new JByteArrayPinnableSlice object
|
||||
*
|
||||
*/
|
||||
JByteArrayPinnableSlice(JNIEnv* env) : env_(env){};
|
||||
|
||||
PinnableSlice& pinnable_slice() { return pinnable_slice_; }
|
||||
|
||||
~JByteArrayPinnableSlice() { pinnable_slice_.Reset(); };
|
||||
|
||||
/**
|
||||
* @brief copy back contents of the pinnable slice into the Java ByteBuffer
|
||||
*
|
||||
* @return jint min of size of buffer and number of bytes in value for
|
||||
* requested key
|
||||
*/
|
||||
jint Fetch() {
|
||||
const jint pinnable_len = static_cast<jint>(pinnable_slice_.size());
|
||||
const jint result_len = std::min(jbuffer_len_, pinnable_len);
|
||||
env_->SetByteArrayRegion(
|
||||
jbuffer_, jbuffer_off_, result_len,
|
||||
reinterpret_cast<const jbyte*>(pinnable_slice_.data()));
|
||||
KVException::ThrowOnError(
|
||||
env_); // exception thrown: ArrayIndexOutOfBoundsException
|
||||
|
||||
return pinnable_len;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief create a new Java buffer and copy the result into it
|
||||
*
|
||||
* @return jbyteArray the java buffer holding the result
|
||||
*/
|
||||
jbyteArray NewByteArray() {
|
||||
const jint pinnable_len = static_cast<jint>(pinnable_slice_.size());
|
||||
jbyteArray jbuffer = env_->NewByteArray(static_cast<jsize>(pinnable_len));
|
||||
KVException::ThrowOnError(env_); // OutOfMemoryError
|
||||
|
||||
env_->SetByteArrayRegion(
|
||||
jbuffer, 0, pinnable_len,
|
||||
reinterpret_cast<const jbyte*>(pinnable_slice_.data()));
|
||||
KVException::ThrowOnError(env_); // ArrayIndexOutOfBoundsException
|
||||
|
||||
return jbuffer;
|
||||
}
|
||||
|
||||
private:
|
||||
JNIEnv* env_;
|
||||
jbyteArray jbuffer_;
|
||||
jint jbuffer_off_;
|
||||
jint jbuffer_len_;
|
||||
PinnableSlice pinnable_slice_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wrap a pinnable slice with a method to retrieve the contents back into
|
||||
* Java
|
||||
*
|
||||
* The Java Direct Buffer version copies the memory of the buffer from the slice
|
||||
*/
|
||||
class JDirectBufferPinnableSlice {
|
||||
public:
|
||||
JDirectBufferPinnableSlice(JNIEnv* env, const jobject& jbuffer,
|
||||
const jint jbuffer_off, const jint jbuffer_len)
|
||||
: buffer_(static_cast<char*>(env->GetDirectBufferAddress(jbuffer)) +
|
||||
jbuffer_off),
|
||||
jbuffer_len_(jbuffer_len) {
|
||||
jlong capacity = env->GetDirectBufferCapacity(jbuffer);
|
||||
if (capacity < jbuffer_off + jbuffer_len) {
|
||||
auto message =
|
||||
"Invalid value argument. Capacity is less than requested region. "
|
||||
"offset " +
|
||||
std::to_string(jbuffer_off) + " + length " +
|
||||
std::to_string(jbuffer_len) + " exceeds capacity " +
|
||||
std::to_string(capacity);
|
||||
KVException::ThrowNew(env, message);
|
||||
}
|
||||
}
|
||||
|
||||
PinnableSlice& pinnable_slice() { return pinnable_slice_; }
|
||||
|
||||
~JDirectBufferPinnableSlice() { pinnable_slice_.Reset(); };
|
||||
|
||||
/**
|
||||
* @brief copy back contents of the pinnable slice into the Java DirectBuffer
|
||||
*
|
||||
* @return jint min of size of buffer and number of bytes in value for
|
||||
* requested key
|
||||
*/
|
||||
jint Fetch() {
|
||||
const jint pinnable_len = static_cast<jint>(pinnable_slice_.size());
|
||||
const jint result_len = std::min(jbuffer_len_, pinnable_len);
|
||||
|
||||
memcpy(buffer_, pinnable_slice_.data(), result_len);
|
||||
return pinnable_len;
|
||||
};
|
||||
|
||||
private:
|
||||
char* buffer_;
|
||||
jint jbuffer_len_;
|
||||
PinnableSlice pinnable_slice_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -5175,6 +5175,15 @@ class TickerTypeJni {
|
||||
return -0x41;
|
||||
case ROCKSDB_NAMESPACE::Tickers::PREFETCH_HITS:
|
||||
return -0x42;
|
||||
case ROCKSDB_NAMESPACE::Tickers::COMPRESSED_SECONDARY_CACHE_DUMMY_HITS:
|
||||
return -0x43;
|
||||
case ROCKSDB_NAMESPACE::Tickers::COMPRESSED_SECONDARY_CACHE_HITS:
|
||||
return -0x44;
|
||||
case ROCKSDB_NAMESPACE::Tickers::COMPRESSED_SECONDARY_CACHE_PROMOTIONS:
|
||||
return -0x45;
|
||||
case ROCKSDB_NAMESPACE::Tickers::
|
||||
COMPRESSED_SECONDARY_CACHE_PROMOTION_SKIPS:
|
||||
return -0x46;
|
||||
case ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX:
|
||||
// 0x5F was the max value in the initial copy of tickers to Java.
|
||||
// Since these values are exposed directly to Java clients, we keep
|
||||
@@ -5550,6 +5559,17 @@ class TickerTypeJni {
|
||||
return ROCKSDB_NAMESPACE::Tickers::PREFETCH_BYTES_USEFUL;
|
||||
case -0x42:
|
||||
return ROCKSDB_NAMESPACE::Tickers::PREFETCH_HITS;
|
||||
case -0x43:
|
||||
return ROCKSDB_NAMESPACE::Tickers::
|
||||
COMPRESSED_SECONDARY_CACHE_DUMMY_HITS;
|
||||
case -0x44:
|
||||
return ROCKSDB_NAMESPACE::Tickers::COMPRESSED_SECONDARY_CACHE_HITS;
|
||||
case -0x45:
|
||||
return ROCKSDB_NAMESPACE::Tickers::
|
||||
COMPRESSED_SECONDARY_CACHE_PROMOTIONS;
|
||||
case -0x46:
|
||||
return ROCKSDB_NAMESPACE::Tickers::
|
||||
COMPRESSED_SECONDARY_CACHE_PROMOTION_SKIPS;
|
||||
case 0x5F:
|
||||
// 0x5F was the max value in the initial copy of tickers to Java.
|
||||
// Since these values are exposed directly to Java clients, we keep
|
||||
|
||||
+109
-168
@@ -26,6 +26,7 @@
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/version.h"
|
||||
#include "rocksjni/cplusplus_to_java_convert.h"
|
||||
#include "rocksjni/kv_helper.h"
|
||||
#include "rocksjni/portal.h"
|
||||
|
||||
#ifdef min
|
||||
@@ -600,55 +601,6 @@ void Java_org_rocksdb_RocksDB_dropColumnFamilies(
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// ROCKSDB_NAMESPACE::DB::Put
|
||||
|
||||
/**
|
||||
* @return true if the put succeeded, false if a Java Exception was thrown
|
||||
*/
|
||||
bool rocksdb_put_helper(JNIEnv* env, ROCKSDB_NAMESPACE::DB* db,
|
||||
const ROCKSDB_NAMESPACE::WriteOptions& write_options,
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle,
|
||||
jbyteArray jkey, jint jkey_off, jint jkey_len,
|
||||
jbyteArray jval, jint jval_off, jint jval_len) {
|
||||
jbyte* key = new jbyte[jkey_len];
|
||||
env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key);
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
delete[] key;
|
||||
return false;
|
||||
}
|
||||
|
||||
jbyte* value = new jbyte[jval_len];
|
||||
env->GetByteArrayRegion(jval, jval_off, jval_len, value);
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
delete[] value;
|
||||
delete[] key;
|
||||
return false;
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(key), jkey_len);
|
||||
ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast<char*>(value),
|
||||
jval_len);
|
||||
|
||||
ROCKSDB_NAMESPACE::Status s;
|
||||
if (cf_handle != nullptr) {
|
||||
s = db->Put(write_options, cf_handle, key_slice, value_slice);
|
||||
} else {
|
||||
// backwards compatibility
|
||||
s = db->Put(write_options, key_slice, value_slice);
|
||||
}
|
||||
|
||||
// cleanup
|
||||
delete[] value;
|
||||
delete[] key;
|
||||
|
||||
if (s.ok()) {
|
||||
return true;
|
||||
} else {
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: put
|
||||
@@ -662,8 +614,14 @@ void Java_org_rocksdb_RocksDB_put__J_3BII_3BII(JNIEnv* env, jobject,
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
|
||||
static const ROCKSDB_NAMESPACE::WriteOptions default_write_options =
|
||||
ROCKSDB_NAMESPACE::WriteOptions();
|
||||
rocksdb_put_helper(env, db, default_write_options, nullptr, jkey, jkey_off,
|
||||
jkey_len, jval, jval_off, jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Put(default_write_options, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -682,13 +640,21 @@ void Java_org_rocksdb_RocksDB_put__J_3BII_3BIIJ(JNIEnv* env, jobject,
|
||||
ROCKSDB_NAMESPACE::WriteOptions();
|
||||
auto* cf_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
|
||||
if (cf_handle != nullptr) {
|
||||
rocksdb_put_helper(env, db, default_write_options, cf_handle, jkey,
|
||||
jkey_off, jkey_len, jval, jval_off, jval_len);
|
||||
} else {
|
||||
if (cf_handle == nullptr) {
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(
|
||||
env, ROCKSDB_NAMESPACE::Status::InvalidArgument(
|
||||
"Invalid ColumnFamilyHandle."));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
db->Put(default_write_options, cf_handle, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,8 +672,15 @@ void Java_org_rocksdb_RocksDB_put__JJ_3BII_3BII(JNIEnv* env, jobject,
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
|
||||
auto* write_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
|
||||
rocksdb_put_helper(env, db, *write_options, nullptr, jkey, jkey_off, jkey_len,
|
||||
jval, jval_off, jval_len);
|
||||
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Put(*write_options, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -724,13 +697,19 @@ void Java_org_rocksdb_RocksDB_put__JJ_3BII_3BIIJ(
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
|
||||
auto* cf_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
|
||||
if (cf_handle != nullptr) {
|
||||
rocksdb_put_helper(env, db, *write_options, cf_handle, jkey, jkey_off,
|
||||
jkey_len, jval, jval_off, jval_len);
|
||||
} else {
|
||||
if (cf_handle == nullptr) {
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(
|
||||
env, ROCKSDB_NAMESPACE::Status::InvalidArgument(
|
||||
"Invalid ColumnFamilyHandle."));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Put(*write_options, cf_handle, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1309,53 +1288,6 @@ jint Java_org_rocksdb_RocksDB_getDirect(JNIEnv* env, jobject /*jdb*/,
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// ROCKSDB_NAMESPACE::DB::Merge
|
||||
|
||||
/**
|
||||
* @return true if the merge succeeded, false if a Java Exception was thrown
|
||||
*/
|
||||
bool rocksdb_merge_helper(JNIEnv* env, ROCKSDB_NAMESPACE::DB* db,
|
||||
const ROCKSDB_NAMESPACE::WriteOptions& write_options,
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle,
|
||||
jbyteArray jkey, jint jkey_off, jint jkey_len,
|
||||
jbyteArray jval, jint jval_off, jint jval_len) {
|
||||
jbyte* key = new jbyte[jkey_len];
|
||||
env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key);
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
delete[] key;
|
||||
return false;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(key), jkey_len);
|
||||
|
||||
jbyte* value = new jbyte[jval_len];
|
||||
env->GetByteArrayRegion(jval, jval_off, jval_len, value);
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
delete[] value;
|
||||
delete[] key;
|
||||
return false;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast<char*>(value),
|
||||
jval_len);
|
||||
|
||||
ROCKSDB_NAMESPACE::Status s;
|
||||
if (cf_handle != nullptr) {
|
||||
s = db->Merge(write_options, cf_handle, key_slice, value_slice);
|
||||
} else {
|
||||
s = db->Merge(write_options, key_slice, value_slice);
|
||||
}
|
||||
|
||||
// cleanup
|
||||
delete[] value;
|
||||
delete[] key;
|
||||
|
||||
if (s.ok()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: merge
|
||||
@@ -1369,8 +1301,14 @@ void Java_org_rocksdb_RocksDB_merge__J_3BII_3BII(JNIEnv* env, jobject,
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
|
||||
static const ROCKSDB_NAMESPACE::WriteOptions default_write_options =
|
||||
ROCKSDB_NAMESPACE::WriteOptions();
|
||||
rocksdb_merge_helper(env, db, default_write_options, nullptr, jkey, jkey_off,
|
||||
jkey_len, jval, jval_off, jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Merge(default_write_options, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1388,8 +1326,15 @@ void Java_org_rocksdb_RocksDB_merge__J_3BII_3BIIJ(
|
||||
auto* cf_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
|
||||
if (cf_handle != nullptr) {
|
||||
rocksdb_merge_helper(env, db, default_write_options, cf_handle, jkey,
|
||||
jkey_off, jkey_len, jval, jval_off, jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Merge(default_write_options, cf_handle, key.slice(),
|
||||
value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(
|
||||
env, ROCKSDB_NAMESPACE::Status::InvalidArgument(
|
||||
@@ -1409,8 +1354,14 @@ void Java_org_rocksdb_RocksDB_merge__JJ_3BII_3BII(
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
|
||||
auto* write_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
|
||||
rocksdb_merge_helper(env, db, *write_options, nullptr, jkey, jkey_off,
|
||||
jkey_len, jval, jval_off, jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Merge(*write_options, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1428,8 +1379,15 @@ void Java_org_rocksdb_RocksDB_merge__JJ_3BII_3BIIJ(
|
||||
auto* cf_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
|
||||
if (cf_handle != nullptr) {
|
||||
rocksdb_merge_helper(env, db, *write_options, cf_handle, jkey, jkey_off,
|
||||
jkey_len, jval, jval_off, jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
db->Merge(*write_options, cf_handle, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(
|
||||
env, ROCKSDB_NAMESPACE::Status::InvalidArgument(
|
||||
@@ -1437,16 +1395,37 @@ void Java_org_rocksdb_RocksDB_merge__JJ_3BII_3BIIJ(
|
||||
}
|
||||
}
|
||||
|
||||
jlong rocksdb_iterator_helper(
|
||||
ROCKSDB_NAMESPACE::DB* db, ROCKSDB_NAMESPACE::ReadOptions read_options,
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle) {
|
||||
ROCKSDB_NAMESPACE::Iterator* iterator = nullptr;
|
||||
if (cf_handle != nullptr) {
|
||||
iterator = db->NewIterator(read_options, cf_handle);
|
||||
} else {
|
||||
iterator = db->NewIterator(read_options);
|
||||
}
|
||||
return GET_CPLUSPLUS_POINTER(iterator);
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: mergeDirect
|
||||
* Signature: (JJLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_RocksDB_mergeDirect(
|
||||
JNIEnv* env, jobject /*jdb*/, jlong jdb_handle, jlong jwrite_options_handle,
|
||||
jobject jkey, jint jkey_off, jint jkey_len, jobject jval, jint jval_off,
|
||||
jint jval_len, jlong jcf_handle) {
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
|
||||
auto* write_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
|
||||
auto* cf_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
|
||||
|
||||
auto merge = [&env, &db, &cf_handle, &write_options](
|
||||
ROCKSDB_NAMESPACE::Slice& key,
|
||||
ROCKSDB_NAMESPACE::Slice& value) {
|
||||
ROCKSDB_NAMESPACE::Status s;
|
||||
if (cf_handle == nullptr) {
|
||||
s = db->Merge(*write_options, key, value);
|
||||
} else {
|
||||
s = db->Merge(*write_options, cf_handle, key, value);
|
||||
}
|
||||
if (s.ok()) {
|
||||
return;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
};
|
||||
ROCKSDB_NAMESPACE::JniUtil::kv_op_direct(merge, env, jkey, jkey_off, jkey_len,
|
||||
jval, jval_off, jval_len);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2644,55 +2623,17 @@ jobjectArray Java_org_rocksdb_RocksDB_keyMayExistFoundValue(
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: iterator
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RocksDB_iterator__J(JNIEnv*, jobject, jlong db_handle) {
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(db_handle);
|
||||
return rocksdb_iterator_helper(db, ROCKSDB_NAMESPACE::ReadOptions(), nullptr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: iterator
|
||||
* Signature: (JJ)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RocksDB_iterator__JJ(JNIEnv*, jobject, jlong db_handle,
|
||||
jlong jread_options_handle) {
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(db_handle);
|
||||
auto& read_options =
|
||||
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
return rocksdb_iterator_helper(db, read_options, nullptr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: iteratorCF
|
||||
* Signature: (JJ)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RocksDB_iteratorCF__JJ(JNIEnv*, jobject, jlong db_handle,
|
||||
jlong jcf_handle) {
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(db_handle);
|
||||
auto* cf_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
|
||||
return rocksdb_iterator_helper(db, ROCKSDB_NAMESPACE::ReadOptions(),
|
||||
cf_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: iteratorCF
|
||||
* Signature: (JJJ)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RocksDB_iteratorCF__JJJ(JNIEnv*, jobject,
|
||||
jlong db_handle,
|
||||
jlong jcf_handle,
|
||||
jlong jread_options_handle) {
|
||||
jlong Java_org_rocksdb_RocksDB_iterator(JNIEnv*, jobject, jlong db_handle,
|
||||
jlong jcf_handle,
|
||||
jlong jread_options_handle) {
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(db_handle);
|
||||
auto* cf_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(jcf_handle);
|
||||
auto& read_options =
|
||||
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
return rocksdb_iterator_helper(db, read_options, cf_handle);
|
||||
return GET_CPLUSPLUS_POINTER(db->NewIterator(read_options, cf_handle));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+359
-235
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "include/org_rocksdb_Transaction.h"
|
||||
#include "rocksjni/cplusplus_to_java_convert.h"
|
||||
#include "rocksjni/kv_helper.h"
|
||||
#include "rocksjni/portal.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
@@ -158,90 +159,113 @@ typedef std::function<ROCKSDB_NAMESPACE::Status(
|
||||
std::string*)>
|
||||
FnGet;
|
||||
|
||||
// TODO(AR) consider refactoring to share this between here and rocksjni.cc
|
||||
jbyteArray txn_get_helper(JNIEnv* env, const FnGet& fn_get,
|
||||
const jlong& jread_options_handle,
|
||||
const jbyteArray& jkey, const jint& jkey_part_len) {
|
||||
jbyte* key = env->GetByteArrayElements(jkey, nullptr);
|
||||
if (key == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
return nullptr;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(key),
|
||||
jkey_part_len);
|
||||
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
std::string value;
|
||||
ROCKSDB_NAMESPACE::Status s = fn_get(*read_options, key_slice, &value);
|
||||
|
||||
// trigger java unref on key.
|
||||
// by passing JNI_ABORT, it will simply release the reference without
|
||||
// copying the result back to the java byte array.
|
||||
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
|
||||
|
||||
if (s.IsNotFound()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
jbyteArray jret_value = env->NewByteArray(static_cast<jsize>(value.size()));
|
||||
if (jret_value == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
return nullptr;
|
||||
}
|
||||
env->SetByteArrayRegion(
|
||||
jret_value, 0, static_cast<jsize>(value.size()),
|
||||
const_cast<jbyte*>(reinterpret_cast<const jbyte*>(value.c_str())));
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
return nullptr;
|
||||
}
|
||||
return jret_value;
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: get
|
||||
* Signature: (JJ[BIJ)[B
|
||||
* Signature: (JJ[BIIJ)[B
|
||||
*/
|
||||
jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BIJ(
|
||||
jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BIIJ(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle,
|
||||
jbyteArray jkey, jint jkey_part_len, jlong jcolumn_family_handle) {
|
||||
jbyteArray jkey, jint jkey_off, jint jkey_part_len,
|
||||
jlong jcolumn_family_handle) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
FnGet fn_get =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
const ROCKSDB_NAMESPACE::ReadOptions&,
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
|
||||
const ROCKSDB_NAMESPACE::Slice&, std::string*)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::Get, txn, std::placeholders::_1,
|
||||
column_family_handle, std::placeholders::_2, std::placeholders::_3);
|
||||
return txn_get_helper(env, fn_get, jread_options_handle, jkey, jkey_part_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Get(*read_options, column_family_handle, key.slice(),
|
||||
&value.pinnable_slice()));
|
||||
return value.NewByteArray();
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: get
|
||||
* Signature: (JJ[BI)[B
|
||||
* Signature: (JJ[BII)[B
|
||||
*/
|
||||
jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BI(
|
||||
jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BII(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle,
|
||||
jbyteArray jkey, jint jkey_part_len) {
|
||||
jbyteArray jkey, jint jkey_off, jint jkey_part_len) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
FnGet fn_get =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
const ROCKSDB_NAMESPACE::ReadOptions&,
|
||||
const ROCKSDB_NAMESPACE::Slice&, std::string*)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::Get, txn, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3);
|
||||
return txn_get_helper(env, fn_get, jread_options_handle, jkey, jkey_part_len);
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Get(*read_options, key.slice(), &value.pinnable_slice()));
|
||||
return value.NewByteArray();
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: get
|
||||
* Signature: (JJ[BII[BIIJ)I
|
||||
*/
|
||||
jint Java_org_rocksdb_Transaction_get__JJ_3BII_3BIIJ(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle,
|
||||
jbyteArray jkey, jint jkey_off, jint jkey_part_len, jbyteArray jval,
|
||||
jint jval_off, jint jval_part_len, jlong jcolumn_family_handle) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
|
||||
jval_part_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Get(*read_options, column_family_handle, key.slice(),
|
||||
&value.pinnable_slice()));
|
||||
return value.Fetch();
|
||||
} catch (const ROCKSDB_NAMESPACE::KVException& e) {
|
||||
return e.Code();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: getDirect
|
||||
* Signature: (JJLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJ)I
|
||||
*/
|
||||
jint Java_org_rocksdb_Transaction_getDirect(JNIEnv* env, jobject, jlong jhandle,
|
||||
jlong jread_options_handle,
|
||||
jobject jkey_bb, jint jkey_off,
|
||||
jint jkey_part_len, jobject jval_bb,
|
||||
jint jval_off, jint jval_part_len,
|
||||
jlong jcolumn_family_handle) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice key(env, jkey_bb, jkey_off,
|
||||
jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JDirectBufferPinnableSlice value(env, jval_bb, jval_off,
|
||||
jval_part_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Get(*read_options, column_family_handle, key.slice(),
|
||||
&value.pinnable_slice()));
|
||||
return value.Fetch();
|
||||
} catch (const ROCKSDB_NAMESPACE::KVException& e) {
|
||||
return e.Code();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(AR) consider refactoring to share this between here and rocksjni.cc
|
||||
@@ -428,47 +452,91 @@ jobjectArray Java_org_rocksdb_Transaction_multiGet__JJ_3_3B(
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: getForUpdate
|
||||
* Signature: (JJ[BIJZZ)[B
|
||||
* Signature: (JJ[BIIJZZ)[B
|
||||
*/
|
||||
jbyteArray Java_org_rocksdb_Transaction_getForUpdate__JJ_3BIJZZ(
|
||||
jbyteArray Java_org_rocksdb_Transaction_getForUpdate__JJ_3BIIJZZ(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle,
|
||||
jbyteArray jkey, jint jkey_part_len, jlong jcolumn_family_handle,
|
||||
jboolean jexclusive, jboolean jdo_validate) {
|
||||
jbyteArray jkey, jint jkey_off, jint jkey_part_len,
|
||||
jlong jcolumn_family_handle, jboolean jexclusive, jboolean jdo_validate) {
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
FnGet fn_get_for_update =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
const ROCKSDB_NAMESPACE::ReadOptions&,
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
|
||||
const ROCKSDB_NAMESPACE::Slice&, std::string*, bool, bool)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::GetForUpdate, txn,
|
||||
std::placeholders::_1, column_family_handle, std::placeholders::_2,
|
||||
std::placeholders::_3, jexclusive, jdo_validate);
|
||||
return txn_get_helper(env, fn_get_for_update, jread_options_handle, jkey,
|
||||
jkey_part_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
txn->GetForUpdate(*read_options, column_family_handle, key.slice(),
|
||||
&value.pinnable_slice(), jexclusive, jdo_validate));
|
||||
return value.NewByteArray();
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: getForUpdate
|
||||
* Signature: (JJ[BIZZ)[B
|
||||
* Signature: (JJ[BII[BIIJZZ)I
|
||||
*/
|
||||
jbyteArray Java_org_rocksdb_Transaction_getForUpdate__JJ_3BIZZ(
|
||||
jint Java_org_rocksdb_Transaction_getForUpdate__JJ_3BII_3BIIJZZ(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle,
|
||||
jbyteArray jkey, jint jkey_part_len, jboolean jexclusive,
|
||||
jboolean jdo_validate) {
|
||||
jbyteArray jkey, jint jkey_off, jint jkey_part_len, jbyteArray jval,
|
||||
jint jval_off, jint jval_len, jlong jcolumn_family_handle,
|
||||
jboolean jexclusive, jboolean jdo_validate) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
FnGet fn_get_for_update =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
const ROCKSDB_NAMESPACE::ReadOptions&,
|
||||
const ROCKSDB_NAMESPACE::Slice&, std::string*, bool, bool)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::GetForUpdate, txn,
|
||||
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
|
||||
jexclusive, jdo_validate);
|
||||
return txn_get_helper(env, fn_get_for_update, jread_options_handle, jkey,
|
||||
jkey_part_len);
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
txn->GetForUpdate(*read_options, column_family_handle, key.slice(),
|
||||
&value.pinnable_slice(), jexclusive, jdo_validate));
|
||||
return value.Fetch();
|
||||
} catch (const ROCKSDB_NAMESPACE::KVException& e) {
|
||||
return e.Code();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: getDirectForUpdate
|
||||
* Signature: (JJLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJZZ)I
|
||||
*/
|
||||
jint Java_org_rocksdb_Transaction_getDirectForUpdate(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle,
|
||||
jobject jkey_bb, jint jkey_off, jint jkey_part_len, jobject jval_bb,
|
||||
jint jval_off, jint jval_len, jlong jcolumn_family_handle,
|
||||
jboolean jexclusive, jboolean jdo_validate) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice key(env, jkey_bb, jkey_off,
|
||||
jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JDirectBufferPinnableSlice value(env, jval_bb, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
txn->GetForUpdate(*read_options, column_family_handle, key.slice(),
|
||||
&value.pinnable_slice(), jexclusive, jdo_validate));
|
||||
return value.Fetch();
|
||||
} catch (const ROCKSDB_NAMESPACE::KVException& e) {
|
||||
return e.Code();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -519,29 +587,15 @@ jobjectArray Java_org_rocksdb_Transaction_multiGetForUpdate__JJ_3_3B(
|
||||
jread_options_handle, jkey_parts);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: getIterator
|
||||
* Signature: (JJ)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_Transaction_getIterator__JJ(JNIEnv* /*env*/,
|
||||
jobject /*jobj*/,
|
||||
jlong jhandle,
|
||||
jlong jread_options_handle) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
return GET_CPLUSPLUS_POINTER(txn->GetIterator(*read_options));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: getIterator
|
||||
* Signature: (JJJ)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_Transaction_getIterator__JJJ(
|
||||
JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle,
|
||||
jlong jread_options_handle, jlong jcolumn_family_handle) {
|
||||
jlong Java_org_rocksdb_Transaction_getIterator(JNIEnv* /*env*/,
|
||||
jobject /*jobj*/, jlong jhandle,
|
||||
jlong jread_options_handle,
|
||||
jlong jcolumn_family_handle) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* read_options =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jread_options_handle);
|
||||
@@ -552,84 +606,93 @@ jlong Java_org_rocksdb_Transaction_getIterator__JJJ(
|
||||
txn->GetIterator(*read_options, column_family_handle));
|
||||
}
|
||||
|
||||
typedef std::function<ROCKSDB_NAMESPACE::Status(
|
||||
const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>
|
||||
FnWriteKV;
|
||||
|
||||
// TODO(AR) consider refactoring to share this between here and rocksjni.cc
|
||||
void txn_write_kv_helper(JNIEnv* env, const FnWriteKV& fn_write_kv,
|
||||
const jbyteArray& jkey, const jint& jkey_part_len,
|
||||
const jbyteArray& jval, const jint& jval_len) {
|
||||
jbyte* key = env->GetByteArrayElements(jkey, nullptr);
|
||||
if (key == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: put
|
||||
* Signature: (J[BII[BIIJZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Transaction_put__J_3BII_3BIIJZ(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey,
|
||||
jint jkey_off, jint jkey_part_len, jbyteArray jval, jint jval_off,
|
||||
jint jval_len, jlong jcolumn_family_handle, jboolean jassume_tracked) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Put(column_family_handle, key.slice(), value.slice(),
|
||||
jassume_tracked));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
jbyte* value = env->GetByteArrayElements(jval, nullptr);
|
||||
if (value == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
|
||||
return;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(key),
|
||||
jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast<char*>(value),
|
||||
jval_len);
|
||||
|
||||
ROCKSDB_NAMESPACE::Status s = fn_write_kv(key_slice, value_slice);
|
||||
|
||||
// trigger java unref on key.
|
||||
// by passing JNI_ABORT, it will simply release the reference without
|
||||
// copying the result back to the java byte array.
|
||||
env->ReleaseByteArrayElements(jval, value, JNI_ABORT);
|
||||
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
|
||||
|
||||
if (s.ok()) {
|
||||
return;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: put
|
||||
* Signature: (J[BI[BIJZ)V
|
||||
* Signature: (J[BII[BII)V
|
||||
*/
|
||||
void Java_org_rocksdb_Transaction_put__J_3BI_3BIJZ(
|
||||
void Java_org_rocksdb_Transaction_put__J_3BII_3BII(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey,
|
||||
jint jkey_part_len, jbyteArray jval, jint jval_len,
|
||||
jint jkey_off, jint jkey_part_len, jbyteArray jval, jint jval_off,
|
||||
jint jval_len) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Put(key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: putDirect
|
||||
* Signature: (JLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Transaction_putDirect__JLjava_nio_ByteBuffer_2IILjava_nio_ByteBuffer_2IIJZ(
|
||||
JNIEnv* env, jobject, jlong jhandle, jobject jkey_bb, jint jkey_off,
|
||||
jint jkey_len, jobject jval_bb, jint jval_off, jint jval_len,
|
||||
jlong jcolumn_family_handle, jboolean jassume_tracked) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
FnWriteKV fn_put =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
|
||||
const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&,
|
||||
bool)>(&ROCKSDB_NAMESPACE::Transaction::Put, txn,
|
||||
column_family_handle, std::placeholders::_1,
|
||||
std::placeholders::_2, jassume_tracked);
|
||||
txn_write_kv_helper(env, fn_put, jkey, jkey_part_len, jval, jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice key(env, jkey_bb, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice value(env, jval_bb, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Put(column_family_handle, key.slice(), value.slice(),
|
||||
jassume_tracked));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: put
|
||||
* Signature: (J[BI[BI)V
|
||||
* Method: putDirect
|
||||
* Signature: (JLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V
|
||||
*/
|
||||
void Java_org_rocksdb_Transaction_put__J_3BI_3BI(JNIEnv* env, jobject /*jobj*/,
|
||||
jlong jhandle, jbyteArray jkey,
|
||||
jint jkey_part_len,
|
||||
jbyteArray jval,
|
||||
jint jval_len) {
|
||||
void Java_org_rocksdb_Transaction_putDirect__JLjava_nio_ByteBuffer_2IILjava_nio_ByteBuffer_2II(
|
||||
JNIEnv* env, jobject, jlong jhandle, jobject jkey_bb, jint jkey_off,
|
||||
jint jkey_len, jobject jval_bb, jint jval_off, jint jval_len) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
FnWriteKV fn_put =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::Put, txn, std::placeholders::_1,
|
||||
std::placeholders::_2);
|
||||
txn_write_kv_helper(env, fn_put, jkey, jkey_part_len, jval, jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice key(env, jkey_bb, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice value(env, jval_bb, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Put(key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
typedef std::function<ROCKSDB_NAMESPACE::Status(
|
||||
@@ -780,41 +843,92 @@ void Java_org_rocksdb_Transaction_put__J_3_3BI_3_3BI(
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: merge
|
||||
* Signature: (J[BI[BIJZ)V
|
||||
* Signature: (J[BII[BIIJZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Transaction_merge__J_3BI_3BIJZ(
|
||||
void Java_org_rocksdb_Transaction_merge__J_3BII_3BIIJZ(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey,
|
||||
jint jkey_part_len, jbyteArray jval, jint jval_len,
|
||||
jlong jcolumn_family_handle, jboolean jassume_tracked) {
|
||||
jint jkey_off, jint jkey_part_len, jbyteArray jval, jint jval_off,
|
||||
jint jval_len, jlong jcolumn_family_handle, jboolean jassume_tracked) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
FnWriteKV fn_merge =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
|
||||
const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&,
|
||||
bool)>(&ROCKSDB_NAMESPACE::Transaction::Merge, txn,
|
||||
column_family_handle, std::placeholders::_1,
|
||||
std::placeholders::_2, jassume_tracked);
|
||||
txn_write_kv_helper(env, fn_merge, jkey, jkey_part_len, jval, jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Merge(column_family_handle, key.slice(), value.slice(),
|
||||
jassume_tracked));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: merge
|
||||
* Signature: (J[BI[BI)V
|
||||
* Signature: (J[BII[BII)V
|
||||
*/
|
||||
void Java_org_rocksdb_Transaction_merge__J_3BI_3BI(
|
||||
void Java_org_rocksdb_Transaction_merge__J_3BII_3BII(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey,
|
||||
jint jkey_part_len, jbyteArray jval, jint jval_len) {
|
||||
jint jkey_off, jint jkey_part_len, jbyteArray jval, jint jval_off,
|
||||
jint jval_len) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
FnWriteKV fn_merge =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::Merge, txn, std::placeholders::_1,
|
||||
std::placeholders::_2);
|
||||
txn_write_kv_helper(env, fn_merge, jkey, jkey_part_len, jval, jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Merge(key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: mergeDirect
|
||||
* Signature: (JLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJZ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_rocksdb_Transaction_mergeDirect__JLjava_nio_ByteBuffer_2IILjava_nio_ByteBuffer_2IIJZ(
|
||||
JNIEnv* env, jobject, jlong jhandle, jobject jkey_bb, jint jkey_off,
|
||||
jint jkey_len, jobject jval_bb, jint jval_off, jint jval_len,
|
||||
jlong jcolumn_family_handle, jboolean jassume_tracked) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice key(env, jkey_bb, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice value(env, jval_bb, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Merge(column_family_handle, key.slice(), value.slice(),
|
||||
jassume_tracked));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: mergeDirect
|
||||
* Signature: (JLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_rocksdb_Transaction_mergeDirect__JLjava_nio_ByteBuffer_2IILjava_nio_ByteBuffer_2II(
|
||||
JNIEnv* env, jobject, jlong jhandle, jobject jkey_bb, jint jkey_off,
|
||||
jint jkey_len, jobject jval_bb, jint jval_off, jint jval_len) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice key(env, jkey_bb, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice value(env, jval_bb, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->Merge(key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
typedef std::function<ROCKSDB_NAMESPACE::Status(
|
||||
@@ -1065,14 +1179,15 @@ void Java_org_rocksdb_Transaction_putUntracked__J_3BI_3BIJ(
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
FnWriteKV fn_put_untracked =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
|
||||
const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn,
|
||||
column_family_handle, std::placeholders::_1, std::placeholders::_2);
|
||||
txn_write_kv_helper(env, fn_put_untracked, jkey, jkey_part_len, jval,
|
||||
jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, 0, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, 0, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
txn->PutUntracked(column_family_handle, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1084,13 +1199,14 @@ void Java_org_rocksdb_Transaction_putUntracked__J_3BI_3BI(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey,
|
||||
jint jkey_part_len, jbyteArray jval, jint jval_len) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
FnWriteKV fn_put_untracked =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn,
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
txn_write_kv_helper(env, fn_put_untracked, jkey, jkey_part_len, jval,
|
||||
jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, 0, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, 0, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, txn->PutUntracked(key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1137,42 +1253,50 @@ void Java_org_rocksdb_Transaction_putUntracked__J_3_3BI_3_3BI(
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: mergeUntracked
|
||||
* Signature: (J[BI[BIJ)V
|
||||
* Signature: (J[BII[BIIJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Transaction_mergeUntracked__J_3BI_3BIJ(
|
||||
void Java_org_rocksdb_Transaction_mergeUntracked(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey,
|
||||
jint jkey_part_len, jbyteArray jval, jint jval_len,
|
||||
jint jkey_off, jint jkey_part_len, jbyteArray jval, jint jval_off,
|
||||
jint jval_len, jlong jcolumn_family_handle) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
txn->MergeUntracked(column_family_handle, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: mergeUntrackedDirect
|
||||
* Signature: (JLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Transaction_mergeUntrackedDirect(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobject jkey, jint jkey_off,
|
||||
jint jkey_part_len, jobject jval, jint jval_off, jint jval_len,
|
||||
jlong jcolumn_family_handle) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
auto* column_family_handle =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyHandle*>(
|
||||
jcolumn_family_handle);
|
||||
FnWriteKV fn_merge_untracked =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
|
||||
const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::MergeUntracked, txn,
|
||||
column_family_handle, std::placeholders::_1, std::placeholders::_2);
|
||||
txn_write_kv_helper(env, fn_merge_untracked, jkey, jkey_part_len, jval,
|
||||
jval_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: mergeUntracked
|
||||
* Signature: (J[BI[BI)V
|
||||
*/
|
||||
void Java_org_rocksdb_Transaction_mergeUntracked__J_3BI_3BI(
|
||||
JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey,
|
||||
jint jkey_part_len, jbyteArray jval, jint jval_len) {
|
||||
auto* txn = reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jhandle);
|
||||
FnWriteKV fn_merge_untracked =
|
||||
std::bind<ROCKSDB_NAMESPACE::Status (ROCKSDB_NAMESPACE::Transaction::*)(
|
||||
const ROCKSDB_NAMESPACE::Slice&, const ROCKSDB_NAMESPACE::Slice&)>(
|
||||
&ROCKSDB_NAMESPACE::Transaction::MergeUntracked, txn,
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
txn_write_kv_helper(env, fn_merge_untracked, jkey, jkey_part_len, jval,
|
||||
jval_len);
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice key(env, jkey, jkey_off,
|
||||
jkey_part_len);
|
||||
ROCKSDB_NAMESPACE::JDirectBufferSlice value(env, jval, jval_off, jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
txn->MergeUntracked(column_family_handle, key.slice(), value.slice()));
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Loaded 100 of 232 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user