mirror of
https://github.com/facebook/rocksdb.git
synced 2026-10-23 08:09:02 +03:00
Summary: ## Summary Pull Request resolved: https://github.com/facebook/rocksdb/pull/15073 The callback-based async read APIs make coroutine-native implementations and StackableDB composition awkward because wrappers must bridge callbacks back into tasks. This change introduces `CoroDB` as a side capability exposing lazy `GetCoroutine` and `MultiGetCoroutine` tasks. `DBImpl` and `CompactedDBImpl` implement this capability directly, while `DB::GetAsync` and `DB::MultiGetAsync` remain available as compatibility entry points with synchronous fallback. The native interface should also be more efficient because callers directly await the DB coroutine. It avoids the callback/Baton adapter, callback context handoff, and explicit rescheduling required for each request. Native tasks must run on the DB filesystem read executor's EventBase; debug builds verify this affinity. Public API documentation, a release note, and native-coroutine support in `db_bench` and `db_stress` are included. ## Dispatch DB::GetAsync -> GetCoroDB() on the concrete DB -> virtual CoroDB::GetCoroutine(...) -> DBImpl::GetCoroutine or CompactedDBImpl::GetCoroutine -> AsyncCallback::OnComplete(...) Virtual dispatch selects the coroutine implementation belonging to the concrete DB while preserving the existing callback API. ## Statistics Coroutine reads preserve RocksDB's TLS statistics behavior while keeping concurrent requests isolated. Each top-level task copies only the caller's statistics configuration—`PerfLevel`, per-level perf context enablement, and IO statistics enablement—into fresh request-local `PerfContext` and `IOStatsContext` objects. Existing counters are not inherited. If both perf and IO statistics are disabled, no request statistics context is installed. Folly `RequestContext` preserves these contexts across suspension and resumption on the read executor's EventBase. At completion, RocksDB publishes the request's collected values to TLS, allowing the awaiting caller to use `get_perf_context()` and `get_iostats_context()` as after a synchronous read. Callers must consume or copy them before leaving that EventBase. Statistics scopes are installed only by top-level coroutine reads and cannot be nested. ## Benchmarks Compared the previous `GetAsync` callback bridge with native `GetCoroutine`. Both variants used optimized builds, the same compacted DB, a fixed seed, eight executor threads, ten jobs per thread, and 2.56M reads per invocation. Results are the mean of three independent invocations using the default RocksDB block cache. A sequential read warmed the OS page cache before the non-direct-read measurements. | Workload | `GetAsync` bridge | Native `GetCoroutine` | Change | | Direct I/O | 237,710 ops/s | 243,588 ops/s | +2.47% | | Warm OS page cache | 783,859 ops/s | 891,531 ops/s | +13.74% | The direct-I/O difference is within observed run-to-run variation. When reads are served from the OS page cache and API overhead is a larger portion of latency, the native interface improves throughput by 13.74%. Every invocation completed all requested reads. ```bash db_bench \ --benchmarks=readrandomcoroutine \ --db=$CORO_BENCH_DB \ --use_existing_db=true \ --num=2000000 \ --value_size=1024 \ --compression_type=none \ --threads=8 \ --coro_jobs_per_thread=10 \ --reads=320000 \ --use_direct_reads=<true|false> \ --perf_level=1 \ --statistics=true \ --seed=20260807 ``` Reviewed By: xingbowang Differential Revision: D115232511 fbshipit-source-id: 30b48da2a798094d9080178f5430aae26e6c5954
135 lines
5.6 KiB
C++
135 lines
5.6 KiB
C++
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
//
|
|
// 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).
|
|
|
|
#if defined(USE_COROUTINES)
|
|
#include "folly/coro/Coroutine.h"
|
|
#include "folly/coro/Nothrow.h"
|
|
#include "folly/coro/Task.h"
|
|
#endif
|
|
#include "rocksdb/rocksdb_namespace.h"
|
|
|
|
// This file has two sections. The first section applies to all instances of
|
|
// header file inclusion and has an include guard. The second section is
|
|
// meant for multiple inclusions in the same source file, and is idempotent.
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
#ifndef UTIL_CORO_UTILS_H_
|
|
#define UTIL_CORO_UTILS_H_
|
|
|
|
#if defined(USE_COROUTINES)
|
|
|
|
// The following macros expand to regular and coroutine function
|
|
// declarations for a given function
|
|
#define DECLARE_SYNC_AND_ASYNC(__ret_type__, __func_name__, ...) \
|
|
__ret_type__ __func_name__(__VA_ARGS__); \
|
|
folly::coro::Task<__ret_type__> __func_name__##Coroutine(__VA_ARGS__);
|
|
|
|
#define DECLARE_SYNC_AND_ASYNC_VIRTUAL(__ret_type__, __func_name__, ...) \
|
|
virtual __ret_type__ __func_name__(__VA_ARGS__); \
|
|
virtual folly::coro::Task<__ret_type__> __func_name__##Coroutine(__VA_ARGS__);
|
|
|
|
#define DECLARE_SYNC_AND_ASYNC_OVERRIDE(__ret_type__, __func_name__, ...) \
|
|
__ret_type__ __func_name__(__VA_ARGS__) override; \
|
|
folly::coro::Task<__ret_type__> __func_name__##Coroutine(__VA_ARGS__) \
|
|
override;
|
|
|
|
#define DECLARE_SYNC_AND_ASYNC_CONST(__ret_type__, __func_name__, ...) \
|
|
__ret_type__ __func_name__(__VA_ARGS__) const; \
|
|
folly::coro::Task<__ret_type__> __func_name__##Coroutine(__VA_ARGS__) const;
|
|
|
|
#define DECLARE_SYNC_AND_ASYNC_TEMPLATE_CONST(__template_decl__, __ret_type__, \
|
|
__func_name__, ...) \
|
|
__template_decl__ __ret_type__ __func_name__(__VA_ARGS__) const; \
|
|
__template_decl__ folly::coro::Task<__ret_type__> __func_name__##Coroutine( \
|
|
__VA_ARGS__) const;
|
|
|
|
constexpr bool using_coroutines() { return true; }
|
|
#else // !USE_COROUTINES
|
|
|
|
// The following macros expand to a regular function declaration for a given
|
|
// function
|
|
#define DECLARE_SYNC_AND_ASYNC(__ret_type__, __func_name__, ...) \
|
|
__ret_type__ __func_name__(__VA_ARGS__);
|
|
|
|
#define DECLARE_SYNC_AND_ASYNC_VIRTUAL(__ret_type__, __func_name__, ...) \
|
|
virtual __ret_type__ __func_name__(__VA_ARGS__);
|
|
|
|
#define DECLARE_SYNC_AND_ASYNC_OVERRIDE(__ret_type__, __func_name__, ...) \
|
|
__ret_type__ __func_name__(__VA_ARGS__) override;
|
|
|
|
#define DECLARE_SYNC_AND_ASYNC_CONST(__ret_type__, __func_name__, ...) \
|
|
__ret_type__ __func_name__(__VA_ARGS__) const;
|
|
|
|
#define DECLARE_SYNC_AND_ASYNC_TEMPLATE_CONST(__template_decl__, __ret_type__, \
|
|
__func_name__, ...) \
|
|
__template_decl__ __ret_type__ __func_name__(__VA_ARGS__) const;
|
|
|
|
constexpr bool using_coroutines() { return false; }
|
|
#endif // USE_COROUTINES
|
|
#endif // UTIL_CORO_UTILS_H_
|
|
|
|
// The following section of the file is meant to be included twice in a
|
|
// source file - once defining WITH_COROUTINES and once defining
|
|
// WITHOUT_COROUTINES
|
|
#undef DEFINE_SYNC_AND_ASYNC
|
|
#undef CO_AWAIT
|
|
#undef CO_RETURN
|
|
|
|
#if defined(WITH_COROUTINES) && defined(USE_COROUTINES)
|
|
|
|
// This macro should be used in the beginning of the function
|
|
// definition. The declaration should have been done using one of the
|
|
// DECLARE_SYNC_AND_ASYNC* macros. It expands to the return type and
|
|
// the function name with the Coroutine suffix. For example -
|
|
// DEFINE_SYNC_AND_ASYNC(int, foo)(bool bar) {}
|
|
// would expand to -
|
|
// folly::coro::Task<int> fooCoroutine(bool bar) {}
|
|
#define DEFINE_SYNC_AND_ASYNC(__ret_type__, __func_name__) \
|
|
folly::coro::Task<__ret_type__> __func_name__##Coroutine
|
|
|
|
// This macro should be used to call a function that might be a
|
|
// coroutine. It expands to the correct function name and prefixes
|
|
// the co_await operator if necessary. For example -
|
|
// s = CO_AWAIT(foo, true);
|
|
// if the code is compiled WITH_COROUTINES, would expand to
|
|
// s = co_await folly::coro::co_nothrow(fooCoroutine(true));
|
|
// if compiled WITHOUT_COROUTINES, would expand to
|
|
// s = foo(true);
|
|
// co_nothrow drops folly's per-await exception-rethrow scaffolding (RocksDB
|
|
// uses Status, not exceptions). The call args are passed into CO_AWAIT (not
|
|
// appended after it) so co_nothrow can wrap the whole call.
|
|
#define CO_AWAIT(__func_name__, ...) \
|
|
co_await folly::coro::co_nothrow(__func_name__##Coroutine(__VA_ARGS__))
|
|
|
|
#define CO_RETURN co_return
|
|
|
|
#elif defined(WITHOUT_COROUTINES)
|
|
|
|
// This macro should be used in the beginning of the function
|
|
// definition. The declaration should have been done using one of the
|
|
// DECLARE_SYNC_AND_ASYNC* macros. It expands to the return type and
|
|
// the function name without the Coroutine suffix. For example -
|
|
// DEFINE_SYNC_AND_ASYNC(int, foo)(bool bar) {}
|
|
// would expand to -
|
|
// int foo(bool bar) {}
|
|
#define DEFINE_SYNC_AND_ASYNC(__ret_type__, __func_name__) \
|
|
__ret_type__ __func_name__
|
|
|
|
// This macro should be used to call a function that might be a
|
|
// coroutine. It expands to the correct function name and prefixes
|
|
// the co_await operator if necessary. For example -
|
|
// s = CO_AWAIT(foo, true);
|
|
// if the code is compiled WITH_COROUTINES, would expand to
|
|
// s = co_await folly::coro::co_nothrow(fooCoroutine(true));
|
|
// if compiled WITHOUT_COROUTINES, would expand to
|
|
// s = foo(true);
|
|
#define CO_AWAIT(__func_name__, ...) __func_name__(__VA_ARGS__)
|
|
|
|
#define CO_RETURN return
|
|
|
|
#endif // DO_NOT_USE_COROUTINES
|
|
} // namespace ROCKSDB_NAMESPACE
|