mirror of
https://github.com/facebook/rocksdb.git
synced 2026-10-23 08:09:02 +03:00
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/15017 ## Summary RocksDB async `GetAsync` and `MultiGetAsync` callbacks now default to a completion-only path. Callbacks can override `EnableStats()` when they need request-specific `PerfContext` or `IOStatsContext`; otherwise RocksDB avoids creating a coroutine stats context and returns null stats pointers. Callbacks must not initiate another async read from `OnComplete()`. RocksDB stats normally use thread-local storage, which does not compose with interleaved coroutine reads. A stats-enabled request captures only the caller's configuration: perf level, per-level perf enablement, and whether io stats are disabled. It then starts with empty request contexts. `onSet()` loads that request's contexts into TLS, while `onUnset()` saves the accumulated contexts back into request data. On resume, the same contexts are loaded again. This isolates request counters without preserving or restoring pre-existing executor TLS state. The synchronous fallback similarly resets TLS before executing a stats-enabled read. `PerfContext` and `IOStatsContext` gain `Merge()` support for caller-side aggregation. `PerfContext` move construction and assignment now transfer ownership of per-level state rather than deep-copying it, avoiding allocations during coroutine context handoffs. A fbcode-wide audit found no caller that reads a moved-from `PerfContext`. `db_bench` opts in only for coroutine jobs that print per-job perf context and merges every completed request's callback stats into the job aggregate. ## Example: Two Async Gets on One Thread Both callbacks enable stats. Each request starts with empty counters and carries its contexts across suspensions. ```text Shared executor thread | | onSet(A): load A's empty contexts into TLS | A records 3 reads | onUnset(A): save TLS contexts back into A | | onSet(B): load B's empty contexts into TLS | B records 4 reads | onUnset(B): save TLS contexts back into B | | onSet(A): load A's contexts containing 3 reads | A records 2 more reads | callback A receives block_read_count = 5 | | onSet(B): load B's contexts containing 4 reads | callback B receives block_read_count = 4 ``` Isolation comes from replacing TLS with the active request's saved contexts on every `onSet()`. ### Timers Existing wall-clock timers remain in coroutine frames, so timers spanning `co_await` include suspended time and measure end-to-end latency. Request-level `get_cpu_nanos` is suspension-aware: it starts in `onSet()` and stops in `onUnset()` or `Finalize()`, charging only time while the request is active. Nested CPU timers remain disabled because normal RAII CPU timers would also charge executor work performed while their coroutine is suspended. `MultiGetAsync` fanout additionally requires per-child timer state to avoid double counting. ## Benchmarks Benchmarked to confirm no performance regressions when perf is disabled, and also to see the overhead of enabling perf. ```bash db_bench --benchmarks=<readrandomcoroutine|multireadrandomcoroutine> --db=<db_path> --coro_jobs_per_thread=10 --use_existing_db=true --num=2000000 --value_size=1024 --compression_type=none --threads=8 --batch_size=16 --multiread_batched=true --use_direct_reads=true --cache_size=1428076626 --reads=320000 --perf_level=<1|3> --seed=<seed> ``` | Workload | Before change | Perf level 1 | Before -> 1 | Perf level 3 | 1 -> 3 | | -------- | ------------- | ------------ | ----------- | ------------ | ------ | | Async Get | 475.6K ops/s | 476.7K ops/s | +0.2% | 470.5K ops/s | -1.3% | | Async MultiGet | 567.3K ops/s | 567.4K ops/s | +0.0% | 552.2K ops/s | -2.7% | Reviewed By: xingbowang Differential Revision: D112634874 fbshipit-source-id: 94e240aeb1496fa12dac995b5aa5a9c3543cc5fc
122 lines
4.3 KiB
C++
122 lines
4.3 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
|
|
#include <sstream>
|
|
|
|
#include "monitoring/iostats_context_imp.h"
|
|
#include "rocksdb/env.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
#ifdef NIOSTATS_CONTEXT
|
|
// Should not be used because the counters are not thread-safe.
|
|
// Put here just to make get_iostats_context() simple without ifdef.
|
|
static IOStatsContext iostats_context;
|
|
#else
|
|
thread_local IOStatsContext iostats_context;
|
|
#endif
|
|
|
|
IOStatsContext* get_iostats_context() { return &iostats_context; }
|
|
|
|
void FileIOByTemperature::Merge(const FileIOByTemperature& other) {
|
|
#ifndef NIOSTATS_CONTEXT
|
|
hot_file_bytes_read += other.hot_file_bytes_read;
|
|
warm_file_bytes_read += other.warm_file_bytes_read;
|
|
cool_file_bytes_read += other.cool_file_bytes_read;
|
|
cold_file_bytes_read += other.cold_file_bytes_read;
|
|
ice_file_bytes_read += other.ice_file_bytes_read;
|
|
unknown_non_last_level_bytes_read += other.unknown_non_last_level_bytes_read;
|
|
unknown_last_level_bytes_read += other.unknown_last_level_bytes_read;
|
|
hot_file_read_count += other.hot_file_read_count;
|
|
warm_file_read_count += other.warm_file_read_count;
|
|
cool_file_read_count += other.cool_file_read_count;
|
|
cold_file_read_count += other.cold_file_read_count;
|
|
ice_file_read_count += other.ice_file_read_count;
|
|
unknown_non_last_level_read_count += other.unknown_non_last_level_read_count;
|
|
unknown_last_level_read_count += other.unknown_last_level_read_count;
|
|
#else
|
|
(void)other;
|
|
#endif
|
|
}
|
|
|
|
void IOStatsContext::Reset() {
|
|
#ifndef NIOSTATS_CONTEXT
|
|
thread_pool_id = Env::Priority::TOTAL;
|
|
bytes_read = 0;
|
|
bytes_written = 0;
|
|
open_nanos = 0;
|
|
allocate_nanos = 0;
|
|
write_nanos = 0;
|
|
read_nanos = 0;
|
|
range_sync_nanos = 0;
|
|
prepare_write_nanos = 0;
|
|
fsync_nanos = 0;
|
|
logger_nanos = 0;
|
|
cpu_write_nanos = 0;
|
|
cpu_read_nanos = 0;
|
|
file_io_stats_by_temperature.Reset();
|
|
#endif //! NIOSTATS_CONTEXT
|
|
}
|
|
|
|
void IOStatsContext::Merge(const IOStatsContext& other) {
|
|
#ifndef NIOSTATS_CONTEXT
|
|
bytes_read += other.bytes_read;
|
|
bytes_written += other.bytes_written;
|
|
open_nanos += other.open_nanos;
|
|
allocate_nanos += other.allocate_nanos;
|
|
write_nanos += other.write_nanos;
|
|
read_nanos += other.read_nanos;
|
|
range_sync_nanos += other.range_sync_nanos;
|
|
prepare_write_nanos += other.prepare_write_nanos;
|
|
fsync_nanos += other.fsync_nanos;
|
|
logger_nanos += other.logger_nanos;
|
|
cpu_write_nanos += other.cpu_write_nanos;
|
|
cpu_read_nanos += other.cpu_read_nanos;
|
|
file_io_stats_by_temperature.Merge(other.file_io_stats_by_temperature);
|
|
#else
|
|
(void)other;
|
|
#endif
|
|
}
|
|
|
|
#define IOSTATS_CONTEXT_OUTPUT(counter) \
|
|
if (!exclude_zero_counters || counter > 0) { \
|
|
ss << #counter << " = " << counter << ", "; \
|
|
}
|
|
|
|
std::string IOStatsContext::ToString(bool exclude_zero_counters) const {
|
|
#ifdef NIOSTATS_CONTEXT
|
|
(void)exclude_zero_counters;
|
|
return "";
|
|
#else
|
|
std::ostringstream ss;
|
|
IOSTATS_CONTEXT_OUTPUT(thread_pool_id);
|
|
IOSTATS_CONTEXT_OUTPUT(bytes_read);
|
|
IOSTATS_CONTEXT_OUTPUT(bytes_written);
|
|
IOSTATS_CONTEXT_OUTPUT(open_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(allocate_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(write_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(read_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(range_sync_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(fsync_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(prepare_write_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(logger_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(cpu_write_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(cpu_read_nanos);
|
|
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.hot_file_bytes_read);
|
|
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.warm_file_bytes_read);
|
|
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cool_file_bytes_read);
|
|
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cold_file_bytes_read);
|
|
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.hot_file_read_count);
|
|
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.warm_file_read_count);
|
|
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cool_file_read_count);
|
|
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cold_file_read_count);
|
|
std::string str = ss.str();
|
|
str.erase(str.find_last_not_of(", ") + 1);
|
|
return str;
|
|
#endif //! NIOSTATS_CONTEXT
|
|
}
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|