Compare commits

...
10 Commits
Author SHA1 Message Date
anand76 a19ac636e7 Revert "Fix Windows build issue in trie_index_factory.h"
This reverts commit c62e9a0d19.
2026-10-15 10:39:38 -07:00
Anand Ananthabhotla 4cc5e15c15 Make InjectedErrorLog::Entry 256 bytes on 32-bit targets (#15388)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15388

`InjectedErrorLog::Entry` in `utilities/fault_injection_fs.h` is written to the injected error log in its native layout, and a `static_assert` requires `sizeof(Entry) == 256` so the record size stays stable. That assert fails on i386 builds.

Root cause: the fields add up to only 252 bytes. On targets where `alignof(uint64_t)` is 8 (x86_64, aarch64, armv7), the compiler adds 4 bytes of implicit tail padding, so the size is 256. On i386, `uint64_t` members are 4-byte aligned inside a struct, so there is no tail padding and the size is 252.

Fix: add an explicit trailing `uint8_t padding[4]` field so the size no longer depends on `uint64_t` alignment.

Compatibility: on 64-bit targets the new field takes the place of the old implicit tail padding, so every field offset and the 256-byte record layout are unchanged. `Record()` value-initializes the entry (`Entry entry{}`), so the padding bytes are always written as zero. No decoder in the repo depends on the padding bytes.

___

landed-with-radar-review

Differential Revision: D124873369

fbshipit-source-id: f5c6f43ca85013b0e769853ad13b8d4f3f89b958
2026-10-13 10:50:04 -07:00
xingbowang b3063c1db0 Fix big-endian builds for the fault-injection filesystem (#15297)
Summary:
The persistent injected-error log writes `InjectedErrorLog::Entry` objects using their native in-memory layout, while the log format's consumer expects little-endian multi-byte fields. The existing `static_assert(port::kLittleEndian)` protected that format by rejecting every big-endian build that included the fault-injection filesystem, even though the log is an optional diagnostic.

This change keeps RocksDB and the fault-injection filesystem available on supported big-endian platforms while disabling only the incompatible persistent log. Log-file creation now returns an inactive descriptor when `port::kLittleEndian` is false, which makes `Record()` and `Flush()` remain safe no-ops. The fixed 256-byte entry-size assertion remains in place, and format-dependent tests are bypassed on big-endian targets.

The change also adds a release note and documents big-endian support plus the preferred capability-check pattern for optional architecture-specific features.

## Compatibility and limitations

- Little-endian behavior and the on-disk log layout are unchanged.
- On big-endian platforms, configuring an injected-error log path does not create or populate a log file; the rest of fault injection remains available.
- Windows already leaves this POSIX-backed diagnostic disabled.
- This does not introduce a portable log encoding. Supporting persistent injected-error logs on big-endian platforms would require a format change.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15297

Test Plan:
- `build_tools/rockstest.sh fault_injection_fs_test '--gtest_filter=InjectedErrorLogTest.*'` (little-endian macOS): all 5 tests passed.
- `make check-sources`
- `git diff --check upstream/main...2026_10_01_endian_fix`

Native big-endian build and test execution were not available locally; that path relies on the compile-time `port::kLittleEndian` capability and should be covered by big-endian CI.

Reviewed By: jaykorean

Differential Revision: D122880760

Pulled By: xingbowang

fbshipit-source-id: 59dae9661a47608aa12f41078ad1b3ef0fa7231e
2026-10-13 10:49:57 -07:00
anand76 c62e9a0d19 Fix Windows build issue in trie_index_factory.h 2026-10-13 10:49:35 -07:00
anand76 abeebd9630 Additional HISTORY.md update for 11.8.1 2026-08-04 15:22:01 -07:00
Anand Ananthabhotla 3730e34363 Defer obsolete OPTIONS file deletion on open (#15004)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15004

This change reduces DB open latency on remote storage by avoiding foreground deletion of obsolete `OPTIONS-*` files during open when `avoid_unnecessary_blocking_io` is enabled. DB open now avoids foreground deletion of obsolete `OPTIONS-*` files on the pre-open `WriteOptionsFile()` path. `RenameTempFileToOptionsFile()` records the new OPTIONS file and chooses an explicit cleanup mode: skip while open is still in progress, schedule deletion when background work is available, or delete immediately when blocking IO is allowed or background jobs are rejected.

`DBImpl::Open()` makes the deferred cleanup decision once final open status is known. A successful open schedules obsolete OPTIONS files on the purge queue, while a failed open deletes them synchronously before tearing down the partially opened DB. The default synchronous behavior remains unchanged when `avoid_unnecessary_blocking_io` is disabled, and the latest-two OPTIONS retention policy is preserved.

Reviewed By: pdillinger

Differential Revision: D113593680

fbshipit-source-id: 8aee99b3f38a729647d0bcb644d212181fe86895
2026-08-04 15:13:23 -07:00
anand1976 d4a9fdc01b Update HISTORY.md for 11.8.1 release 2026-08-04 11:45:22 -07:00
Josh Kang 9657aad4ec Fix IoUringBackend crash in ReadIOExecutor worker threads (#15020)
Summary:

Each read executor worker creates its own `IoUringBackend`, and Folly's I/O thread pool creates its worker threads and EventBases lazily. A successful calling-thread probe or `DB::Open()` therefore does not validate every worker. A later read can start a new worker; previously, an `IoUringBackend` initialization exception on that worker could escape startup and terminate the process. The previous 512-submit setup also allowed Folly's capacity retry to reduce the completion queue to 512 while retaining a 1024-entry submission queue, which the kernel rejects with `EINVAL`.

Use a 256-submit limit so the 512-entry retry remains valid. Configure the shared read executor during `DB::Open()` through `DBOptions::read_io_executor_threads`, defaulting to one thread. The backend factory catches per-worker io_uring initialization failures and uses Folly's default backend. Each worker's thread-local availability marker starts false and is set true only after `IoUringBackend` construction succeeds. When construction fails, the marker remains false and `SubmitReadAsync()` performs the read synchronously. The executor's maximum thread capacity can be raised or lowered; `db_stress` requests eight threads for async DB APIs.

To expose this degradation, `FSRandomAccessFile::SubmitReadAsync()` now reports whether the asynchronous path was used. `RandomAccessFileReader` increments the `FILE_SUBMIT_ASYNC_READ_FALLBACK` ticker for every submission that falls back to synchronous I/O, making the fallback visible through the DB's `Statistics`.

Reviewed By: xingbowang

Differential Revision: D114031018
2026-08-04 11:37:29 -07:00
Anand Ananthabhotla b4fd1daa68 Avoid deletefile test purge/flush deadlock (#15027)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15027

`BackgroundPurgeCFDropTest` blocks `DBImpl::BGWorkPurge:start` to verify dropped-CF files remain until background purge is released. Runtime OPTIONS-file cleanup can also schedule a purge on the HIGH pool when `avoid_unnecessary_blocking_io` is enabled, so installing the sync-point dependency before test setup can park that OPTIONS purge before the foreground `Flush()` has a chance to run.

This does not need a broader product change. Runtime OPTIONS-file writes are rare, and the hard deadlock is a test artifact caused by the sync-point dependency. Outside the test, the observable impact would be limited to a foreground `Flush()` API call waiting longer if its flush is queued behind the OPTIONS-file purge.

Scope the sync-point dependency to the behavior under test by installing it only for the background-purge case, after the setup flush has produced the SST and just before `DropColumnFamily()`. This keeps setup-side OPTIONS-file purge work outside the blocked window while still verifying that dropped-CF file deletion waits for background purge.

Reviewed By: joshkang97

Differential Revision: D114241457

fbshipit-source-id: 21c5f4242a50590e161b3720077a9dfe5e77aa08
2026-07-30 12:20:29 -07:00
anand1976 3a75b13f6a Update HISTORY.md for 11.8.0 release 2026-07-29 16:58:07 -07:00
44 changed files with 593 additions and 130 deletions

No files matched your search

+7
View File
@@ -298,6 +298,7 @@ build (and where noted, run tests) across:
| Axis | Must support |
|------|--------------|
| OS | Linux (x86_64 + ARM), macOS, Windows |
| Architecture | Little-endian and big-endian targets, including Linux on s390x |
| Compiler | GCC, Clang (libstdc++ **and** libc++), AppleClang, **MSVC (VS2022)**, MinGW (Linux cross-compile, build-only, no gflags) |
| Build system | Make, CMake, and BUCK (internal) -- keep all in sync (see "Build system" above) |
| Config | release (`-fno-rtti`), `ASSERT_STATUS_CHECKED`, ASAN/UBSAN/TSAN, folly, unity build, JNI/Java |
@@ -312,6 +313,12 @@ abstractions; otherwise guard with `#ifdef OS_WIN` (POSIX `<unistd.h>` ->
Windows `<process.h>`). Because libc++ is also tested, include what you use
rather than relying on libstdc++ transitive includes.
Do not use `static_assert` to reject a supported architecture for an optional
feature. Check an existing `port::` capability such as `port::kLittleEndian`
and disable only the unsupported feature so the rest of RocksDB still builds.
Use a portable representation when the feature itself must work across
architectures.
### Unit Test
* After all of the unit tests are added, review them and try to extract common
reusable utility functions to reduce code duplication due to copy past between
+19
View File
@@ -1,6 +1,25 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 11.8.1 (07/28/2026)
### Public API Changes
* Add `DBOptions::read_io_executor_threads` to increase the maximum thread count of the shared filesystem read I/O executor used for asynchronous reads when opening a DB. Opening a DB never reduces the shared executor thread count. Change the experimental `FSRandomAccessFile::SubmitReadAsync()` API to return whether the asynchronous path was used, and add the `FILE_SUBMIT_ASYNC_READ_FALLBACK` ticker to count calls that fall back to synchronous reads.
### Performance Improvements
* When `DBOptions::avoid_unnecessary_blocking_io` is true, obsolete `OPTIONS-*` files found during DB open are deleted by background purge instead of synchronously on the opening thread.
## 11.8.0 (07/28/2026)
### Public API Changes
* Add callback-based asynchronous read APIs, `DB::GetAsync()` and `DB::MultiGetAsync()`. The idea is that when IO is required, RocksDB can suspend its internal coroutine read path, allowing the read executor thread to do other work. When the IO is complete, RocksDB invokes the user callback. This requires filesystem support for full performance benefits. A new filesystem API `FSRandomAccessFile::SubmitReadAsync()` is introduced for this. Unlike `ReadAsync`, the filesystem is responsible for eventually calling the callback. `FileSystem::GetReadExecutor()` returns the IO executor whose EventBases run coroutine read processing. In the Posix filesystem, an IO-uring based event loop is used to complete the IO.
* Added `FlushOptions::listener_wait` (default `false`). When set together with `FlushOptions::wait == true`, `DB::Flush()` will not return until the registered `EventListener::OnFlushCompleted` callbacks for the flushed memtables have finished running. By default (`false`), `Flush(wait=true)` may return as soon as the flush result is committed, which can be before (or while) the `OnFlushCompleted` callbacks execute on the background flush thread. Also added the corresponding C API `rocksdb_flushoptions_set_listener_wait()` / `rocksdb_flushoptions_get_listener_wait()`.
* Deprecated `PinnableWideColumns::serialized_size()` in favor of the new `PinnableWideColumns::payload_size()`, which returns the total size of the columns' names and values (for a plain value this equals the value size). Relatedly, wide-column read accounting changed subtly: read statistics for entities (`BYTES_READ`, `BYTES_PER_READ`, and the per-key byte counts for `GetEntity`/`MultiGetEntity`) and the `ReadOptions::value_size_soft_limit` threshold now measure entities by this payload size instead of the serialized entity size. This drops serialization framing bytes from those measurements (so reported/limited sizes for entity reads are slightly smaller than before) and makes plain-value and wide-column accounting consistent.
### Behavior Changes
* `ReadOptions::value_size_soft_limit` now also bounds the blob values resolved from pre-flush blob direct write references during `MultiGet`/`MultiGetEntity` (previously these were resolved after the limit check and read without bound). Enforcement is "always make progress": at least one key is read even if its value alone exceeds the limit, and only once the returned size exceeds the limit do subsequent keys get `Status::Aborted` -- so a caller retrying the aborted keys cannot loop forever on a single value that by itself exceeds the limit.
### Performance Improvements
* Reduced copying when reading wide-column entities that have blob-referenced columns, whether stored in separate blob files or embedded (same-file) as blob records in the same SST. On the point-lookup (`GetEntity`/`MultiGetEntity`) and blob-backed memtable/direct-write read paths, resolving these columns no longer re-serializes the whole entity into a fresh buffer: inline columns are referenced in place in the pinned entity and each resolved blob value is referenced in place from its fetched buffer. This is enabled by an internal change to `PinnableWideColumns` that lets its columns reference multiple backing buffers.
## 11.7.0 (07/16/2026)
### Public API Changes
* Added `RangeLockManagerHandle::SetIsKilledCallback()`, a `std::function<bool()>` predicate polled during range-lock waits. When it returns true, a blocked `GetRangeLock()` wait returns promptly with `Status::Aborted()` instead of waiting for the lock timeout. Range-lock users that install no callback keep the original wait-until-grant-or-timeout behavior.
@@ -141,6 +141,15 @@ int rocksdb_options_get_max_file_opening_threads(rocksdb_options_t* opt) {
return opt->rep.max_file_opening_threads;
}
void rocksdb_options_set_read_io_executor_threads(rocksdb_options_t* opt,
int v) {
opt->rep.read_io_executor_threads = v;
}
int rocksdb_options_get_read_io_executor_threads(rocksdb_options_t* opt) {
return opt->rep.read_io_executor_threads;
}
void rocksdb_options_set_open_files_async(rocksdb_options_t* opt,
unsigned char v) {
opt->rep.open_files_async = v;
@@ -55,6 +55,12 @@ rocksdb_options_set_verify_sst_unique_id_in_manifest(rocksdb_options_t* opt,
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_verify_sst_unique_id_in_manifest(rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_read_io_executor_threads(
rocksdb_options_t* opt, int v);
extern ROCKSDB_LIBRARY_API int rocksdb_options_get_read_io_executor_threads(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API const char* rocksdb_options_get_db_log_dir(
rocksdb_options_t* opt, size_t* size);
@@ -849,6 +849,10 @@ static void rocksdb_roundtrip_test_rocksdb_options(void) {
CheckCondition(rocksdb_options_get_preserve_internal_time_seconds(obj) == 1);
rocksdb_options_set_preserve_internal_time_seconds(obj, 0);
CheckCondition(rocksdb_options_get_preserve_internal_time_seconds(obj) == 0);
rocksdb_options_set_read_io_executor_threads(obj, 1);
CheckCondition(rocksdb_options_get_read_io_executor_threads(obj) == 1);
rocksdb_options_set_read_io_executor_threads(obj, 0);
CheckCondition(rocksdb_options_get_read_io_executor_threads(obj) == 0);
rocksdb_options_set_reuse_manifest_on_open(obj, 1);
CheckCondition(rocksdb_options_get_reuse_manifest_on_open(obj) == 1);
rocksdb_options_set_reuse_manifest_on_open(obj, 0);
+9
View File
@@ -9107,6 +9107,15 @@ int rocksdb_options_get_max_file_opening_threads(rocksdb_options_t* opt) {
return opt->rep.max_file_opening_threads;
}
void rocksdb_options_set_read_io_executor_threads(rocksdb_options_t* opt,
int v) {
opt->rep.read_io_executor_threads = v;
}
int rocksdb_options_get_read_io_executor_threads(rocksdb_options_t* opt) {
return opt->rep.read_io_executor_threads;
}
void rocksdb_options_set_open_files_async(rocksdb_options_t* opt,
unsigned char v) {
opt->rep.open_files_async = v;
+54 -12
View File
@@ -244,6 +244,11 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
blob_callback_(immutable_db_options_.sst_file_manager.get(), &mutex_,
&error_handler_, &event_logger_,
immutable_db_options_.listeners, dbname_) {
#if USE_COROUTINES
immutable_db_options_.fs->SetReadIOExecutorThreads(
immutable_db_options_.read_io_executor_threads);
#endif // USE_COROUTINES
// !batch_per_trx_ implies seq_per_batch_ because it is only unset for
// WriteUnprepared, which should use seq_per_batch_.
assert(batch_per_txn_ || seq_per_batch_);
@@ -5830,7 +5835,7 @@ void DeleteOptionsFilesHelper(const std::map<uint64_t, std::string>& filenames,
}
} // namespace
Status DBImpl::DeleteObsoleteOptionsFiles() {
Status DBImpl::DeleteObsoleteOptionsFiles(bool schedule_only) {
std::vector<std::string> filenames;
// use ordered map to store keep the filenames sorted from the newest
// to the oldest.
@@ -5855,8 +5860,23 @@ Status DBImpl::DeleteObsoleteOptionsFiles() {
// Keeps the latest 2 Options file
const size_t kNumOptionsFilesKept = 2;
DeleteOptionsFilesHelper(options_filenames, kNumOptionsFilesKept,
immutable_db_options_.info_log, GetEnv());
if (options_filenames.size() > kNumOptionsFilesKept) {
if (schedule_only) {
InstrumentedMutexLock l(&mutex_);
for (auto iter =
std::next(options_filenames.begin(), kNumOptionsFilesKept);
iter != options_filenames.end(); ++iter) {
const uint64_t file_number =
std::numeric_limits<uint64_t>::max() - iter->first;
SchedulePendingPurge(iter->second, GetName(), kOptionsFile, file_number,
/*job_id=*/0);
}
SchedulePurge();
} else {
DeleteOptionsFilesHelper(options_filenames, kNumOptionsFilesKept,
immutable_db_options_.info_log, GetEnv());
}
}
return Status::OK();
}
@@ -5896,18 +5916,42 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
}
if (s.ok()) {
int my_disable_delete_obsolete_files;
enum class ObsoleteOptionsFileCleanup {
kSkip,
kDeleteNow,
kSchedule,
};
ObsoleteOptionsFileCleanup obsolete_options_file_cleanup =
ObsoleteOptionsFileCleanup::kSkip;
{
InstrumentedMutexLock l(&mutex_);
versions_->options_file_number_ = options_file_number;
versions_->options_file_size_ = options_file_size;
my_disable_delete_obsolete_files = disable_delete_obsolete_files_;
if (!disable_delete_obsolete_files_ && !is_remote_compaction_enabled) {
if (immutable_db_options_.avoid_unnecessary_blocking_io &&
!reject_new_background_jobs_) {
// DB::Open() sets `opened_successfully_` after WriteOptionsFile()
// returns, then schedules the deferred OPTIONS-file purge.
obsolete_options_file_cleanup =
opened_successfully_ ? ObsoleteOptionsFileCleanup::kSchedule
: ObsoleteOptionsFileCleanup::kSkip;
} else {
obsolete_options_file_cleanup =
ObsoleteOptionsFileCleanup::kDeleteNow;
}
}
}
if (!my_disable_delete_obsolete_files && !is_remote_compaction_enabled) {
// TODO: Should we check for errors here?
DeleteObsoleteOptionsFiles().PermitUncheckedError();
if (obsolete_options_file_cleanup != ObsoleteOptionsFileCleanup::kSkip) {
Status obsolete_options_status =
DeleteObsoleteOptionsFiles(obsolete_options_file_cleanup ==
ObsoleteOptionsFileCleanup::kSchedule);
if (!obsolete_options_status.ok()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Unable to delete obsolete OPTIONS files: %s",
obsolete_options_status.ToString().c_str());
}
}
}
@@ -7568,8 +7612,7 @@ void DBImpl::GetAsync(const ReadOptions& options,
Status& status, AsyncCallback& callback) {
const bool stats_enabled = callback.EnableStats();
#if USE_COROUTINES
auto* read_executor =
immutable_db_options_.env->GetFileSystem()->GetReadExecutor();
auto* read_executor = immutable_db_options_.fs->GetReadExecutor();
if (read_executor != nullptr) {
auto* read_event_base = read_executor->getEventBase();
assert(read_event_base != nullptr);
@@ -7620,8 +7663,7 @@ void DBImpl::MultiGetAsync(const ReadOptions& options, const size_t num_keys,
const bool sorted_input, AsyncCallback& callback) {
const bool stats_enabled = callback.EnableStats();
#if USE_COROUTINES
auto* read_executor =
immutable_db_options_.env->GetFileSystem()->GetReadExecutor();
auto* read_executor = immutable_db_options_.fs->GetReadExecutor();
if (read_executor != nullptr) {
auto* read_event_base = read_executor->getEventBase();
assert(read_event_base != nullptr);
+1 -1
View File
@@ -1562,7 +1562,7 @@ class DBImpl : public DB {
// 2. db_mutex is NOT held
Status RenameTempFileToOptionsFile(const std::string& file_name,
bool is_remote_compaction_enabled);
Status DeleteObsoleteOptionsFiles();
Status DeleteObsoleteOptionsFiles(bool schedule_only);
void NotifyOnManualFlushScheduled(autovector<ColumnFamilyData*> cfds,
FlushReason flush_reason);
+21
View File
@@ -243,6 +243,11 @@ Status DBImpl::ValidateOptions(
}
Status DBImpl::ValidateOptions(const DBOptions& db_options) {
if (db_options.read_io_executor_threads <= 0) {
return Status::InvalidArgument(
"read_io_executor_threads must be greater than zero");
}
if (db_options.db_paths.size() > 4) {
return Status::NotSupported(
"More than four DB paths are not supported yet. ");
@@ -2872,11 +2877,16 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
}
TEST_SYNC_POINT("DBImpl::Open:Opened");
Status persist_options_status;
bool cleanup_obsolete_options_files = false;
if (s.ok()) {
// Persist RocksDB Options before scheduling the compaction.
// The WriteOptionsFile() will release and lock the mutex internally.
persist_options_status =
impl->WriteOptionsFile(write_options, true /*db_mutex_already_held*/);
cleanup_obsolete_options_files =
impl->immutable_db_options_.avoid_unnecessary_blocking_io &&
impl->immutable_db_options_.compaction_service == nullptr &&
!impl->disable_delete_obsolete_files_;
impl->opened_successfully_ = true;
} else {
persist_options_status.PermitUncheckedError();
@@ -2966,6 +2976,17 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
s = impl->RegisterRecordSeqnoTimeWorker();
}
impl->options_mutex_.Unlock();
if (cleanup_obsolete_options_files) {
Status obsolete_options_status =
impl->DeleteObsoleteOptionsFiles(/*schedule_only=*/s.ok());
if (!obsolete_options_status.ok()) {
ROCKS_LOG_WARN(impl->immutable_db_options_.info_log,
"Unable to %s obsolete OPTIONS files%s: %s",
s.ok() ? "schedule deletion of" : "delete",
s.ok() ? "" : " after DB open failure",
obsolete_options_status.ToString().c_str());
}
}
if (s.ok()) {
*dbptr = std::move(impl);
} else {
+2 -4
View File
@@ -42,8 +42,7 @@ DEFINE_SYNC_AND_ASYNC(Status, DBImpl::Get)
}
#ifdef WITH_COROUTINES
auto* read_executor =
immutable_db_options_.env->GetFileSystem()->GetReadExecutor();
auto* read_executor = immutable_db_options_.fs->GetReadExecutor();
if (read_executor != nullptr) {
auto* read_event_base = read_executor->getEventBase();
assert(read_event_base != nullptr);
@@ -808,8 +807,7 @@ DEFINE_SYNC_AND_ASYNC(void, DBImpl::MultiGet)
read_options.io_activity = Env::IOActivity::kMultiGet;
}
#ifdef WITH_COROUTINES
auto* read_executor =
immutable_db_options_.env->GetFileSystem()->GetReadExecutor();
auto* read_executor = immutable_db_options_.fs->GetReadExecutor();
if (read_executor != nullptr) {
auto* read_event_base = read_executor->getEventBase();
assert(read_event_base != nullptr);
+9 -7
View File
@@ -418,6 +418,15 @@ TEST_F(DeleteFileTest, BackgroundPurgeCFDropTest) {
// Expect 1 sst file.
CheckFileTypeCounts(dbname_, 0, 1, 1);
if (bg_purge) {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->LoadDependency(
{{"DeleteFileTest::BackgroundPurgeCFDropTest:1",
"DBImpl::BGWorkPurge:start"}});
SyncPoint::GetInstance()->EnableProcessing();
}
ASSERT_OK(db_->DropColumnFamily(cfh));
// Still 1 file, it won't be deleted while ColumnFamilyHandle is alive.
CheckFileTypeCounts(dbname_, 0, 1, 1);
@@ -447,13 +456,6 @@ TEST_F(DeleteFileTest, BackgroundPurgeCFDropTest) {
Reopen(options);
ASSERT_OK(dbfull()->TEST_WaitForPurge());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->LoadDependency(
{{"DeleteFileTest::BackgroundPurgeCFDropTest:1",
"DBImpl::BGWorkPurge:start"}});
SyncPoint::GetInstance()->EnableProcessing();
{
SCOPED_TRACE("avoid_unnecessary_blocking_io = true");
do_test(true);
+191
View File
@@ -3,13 +3,17 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include <atomic>
#include <memory>
#include <string>
#include <utility>
#include "db/db_impl/db_impl.h"
#include "db/db_test_util.h"
#include "rocksdb/options.h"
#include "rocksdb/table.h"
#include "test_util/testharness.h"
#include "util/cast_util.h"
namespace ROCKSDB_NAMESPACE {
class OptionsFileTest : public testing::Test {
@@ -20,6 +24,91 @@ class OptionsFileTest : public testing::Test {
};
namespace {
std::string Basename(const std::string& fname) {
const size_t basename_pos = fname.find_last_of("/\\");
return basename_pos == std::string::npos ? fname
: fname.substr(basename_pos + 1);
}
bool IsOptionsFile(const std::string& fname) {
return Basename(fname).find(kOptionsFileNamePrefix) == 0;
}
class FailOptionsDirFsyncFileSystem : public FileSystemWrapper {
public:
explicit FailOptionsDirFsyncFileSystem(std::shared_ptr<FileSystem> target)
: FileSystemWrapper(std::move(target)) {}
static const char* kClassName() { return "FailOptionsDirFsyncFileSystem"; }
const char* Name() const override { return kClassName(); }
void FailOptionsDirFsyncAfterNextOptionsFile() {
observe_options_file_.store(true, std::memory_order_release);
fail_options_dir_fsync_.store(false, std::memory_order_release);
options_dir_fsync_failures_.store(0, std::memory_order_release);
}
int options_dir_fsync_failures() const {
return options_dir_fsync_failures_.load(std::memory_order_acquire);
}
IOStatus NewWritableFile(const std::string& fname,
const FileOptions& file_opts,
std::unique_ptr<FSWritableFile>* result,
IODebugContext* dbg) override {
IOStatus s =
FileSystemWrapper::NewWritableFile(fname, file_opts, result, dbg);
if (!s.ok()) {
return s;
}
if (observe_options_file_.load(std::memory_order_acquire) &&
IsOptionsFile(fname)) {
fail_options_dir_fsync_.store(true, std::memory_order_release);
observe_options_file_.store(false, std::memory_order_release);
}
return s;
}
IOStatus NewDirectory(const std::string& name, const IOOptions& io_opts,
std::unique_ptr<FSDirectory>* result,
IODebugContext* dbg) override {
IOStatus s = FileSystemWrapper::NewDirectory(name, io_opts, result, dbg);
if (s.ok()) {
*result = std::make_unique<FailOptionsDirFsyncDirectory>(
std::move(*result), *this);
}
return s;
}
private:
class FailOptionsDirFsyncDirectory : public FSDirectoryWrapper {
public:
FailOptionsDirFsyncDirectory(std::unique_ptr<FSDirectory>&& dir,
FailOptionsDirFsyncFileSystem& fs)
: FSDirectoryWrapper(std::move(dir)), fs_(fs) {}
IOStatus FsyncWithDirOptions(
const IOOptions& options, IODebugContext* dbg,
const DirFsyncOptions& dir_fsync_options) override {
if (fs_.fail_options_dir_fsync_.load(std::memory_order_acquire) &&
IsOptionsFile(dir_fsync_options.renamed_new_name)) {
fs_.fail_options_dir_fsync_.store(false, std::memory_order_release);
fs_.options_dir_fsync_failures_.fetch_add(1, std::memory_order_acq_rel);
return IOStatus::IOError("Injected OPTIONS directory fsync failure");
}
return FSDirectoryWrapper::FsyncWithDirOptions(options, dbg,
dir_fsync_options);
}
private:
FailOptionsDirFsyncFileSystem& fs_;
};
std::atomic<bool> observe_options_file_{false};
std::atomic<bool> fail_options_dir_fsync_{false};
std::atomic<int> options_dir_fsync_failures_{0};
};
void UpdateOptionsFiles(DB* db,
std::unordered_set<std::string>* filename_history,
int* options_files_count) {
@@ -58,6 +147,34 @@ void VerifyOptionsFileName(
}
}
}
int CountOptionsFiles(Env* env, const std::string& dbname) {
std::vector<std::string> filenames;
EXPECT_OK(env->GetChildren(dbname, &filenames));
int options_files_count = 0;
uint64_t number;
FileType type;
for (const auto& filename : filenames) {
if (ParseFileName(filename, &number, &type) && type == kOptionsFile) {
++options_files_count;
}
}
return options_files_count;
}
int CountOptionsFiles(DB* db) {
return CountOptionsFiles(db->GetEnv(), db->GetName());
}
void GenerateStaleOptionsFiles(DB* db, int options_file_count) {
ASSERT_OK(db->DisableFileDeletions());
for (int i = 0; i < options_file_count; ++i) {
ASSERT_OK(db->SetOptions(
{{"level0_file_num_compaction_trigger", std::to_string(8 + i)}}));
}
ASSERT_GT(CountOptionsFiles(db), 2);
}
} // anonymous namespace
TEST_F(OptionsFileTest, NumberOfOptionsFiles) {
@@ -79,6 +196,80 @@ TEST_F(OptionsFileTest, NumberOfOptionsFiles) {
}
}
TEST_F(OptionsFileTest, ObsoleteOptionsFilesPurgedSynchronouslyOnOpen) {
Options opt;
opt.create_if_missing = true;
ASSERT_OK(DestroyDB(dbname_, opt));
std::unique_ptr<DB> db;
ASSERT_OK(DB::Open(opt, dbname_, &db));
GenerateStaleOptionsFiles(db.get(), 5);
db.reset();
opt.create_if_missing = false;
opt.avoid_unnecessary_blocking_io = false;
ASSERT_OK(DB::Open(opt, dbname_, &db));
ASSERT_LE(CountOptionsFiles(db.get()), 2);
}
TEST_F(OptionsFileTest, ObsoleteOptionsFilesPurgedInBackgroundOnOpen) {
Options opt;
opt.create_if_missing = true;
ASSERT_OK(DestroyDB(dbname_, opt));
std::unique_ptr<DB> db;
ASSERT_OK(DB::Open(opt, dbname_, &db));
GenerateStaleOptionsFiles(db.get(), 5);
db.reset();
opt.create_if_missing = false;
opt.avoid_unnecessary_blocking_io = true;
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->LoadDependency(
{{"OptionsFileTest::ObsoleteOptionsFilesPurgedInBackgroundOnOpen:"
"ReleasePurge",
"DBImpl::BGWorkPurge:start"}});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(DB::Open(opt, dbname_, &db));
ASSERT_GT(CountOptionsFiles(db.get()), 2);
TEST_SYNC_POINT(
"OptionsFileTest::ObsoleteOptionsFilesPurgedInBackgroundOnOpen:"
"ReleasePurge");
ASSERT_OK(static_cast_with_check<DBImpl>(db.get())->TEST_WaitForPurge());
ASSERT_LE(CountOptionsFiles(db.get()), 2);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(OptionsFileTest, ObsoleteOptionsFilesPurgedSynchronouslyOnOpenFailure) {
auto fail_fs =
std::make_shared<FailOptionsDirFsyncFileSystem>(FileSystem::Default());
std::unique_ptr<Env> env(NewCompositeEnv(fail_fs));
Options opt;
opt.env = env.get();
opt.create_if_missing = true;
ASSERT_OK(DestroyDB(dbname_, opt));
std::unique_ptr<DB> db;
ASSERT_OK(DB::Open(opt, dbname_, &db));
GenerateStaleOptionsFiles(db.get(), 5);
db.reset();
opt.create_if_missing = false;
opt.avoid_unnecessary_blocking_io = true;
fail_fs->FailOptionsDirFsyncAfterNextOptionsFile();
ASSERT_NOK(DB::Open(opt, dbname_, &db));
ASSERT_EQ(fail_fs->options_dir_fsync_failures(), 1);
ASSERT_LE(CountOptionsFiles(opt.env, dbname_), 2);
}
TEST_F(OptionsFileTest, OptionsFileName) {
const uint64_t kOptionsFileNum = 12345;
uint64_t number;
+2 -2
View File
@@ -92,11 +92,11 @@ class DbStressRandomAccessFileWrapper : public FSRandomAccessFileOwnerWrapper {
dbg);
}
void SubmitReadAsync(FSReadRequest& req, const IOOptions& options,
bool SubmitReadAsync(FSReadRequest& req, const IOOptions& options,
std::function<void(FSReadRequest&)> cb,
IODebugContext* dbg) override {
CheckIOActivity(options);
target()->SubmitReadAsync(req, options, std::move(cb), dbg);
return target()->SubmitReadAsync(req, options, std::move(cb), dbg);
}
};
+5
View File
@@ -5841,6 +5841,11 @@ void InitializeOptionsGeneral(
Options& options) {
options.create_missing_column_families = true;
options.create_if_missing = true;
#if USE_COROUTINES
if (FLAGS_use_async_db_api) {
options.read_io_executor_threads = 8;
}
#endif // USE_COROUTINES
if (FLAGS_statistics) {
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
+2 -2
View File
@@ -363,11 +363,11 @@ IOStatus FSRandomAccessFileTracingWrapper::ReadAsync(
return s;
}
void FSRandomAccessFileTracingWrapper::SubmitReadAsync(
bool FSRandomAccessFileTracingWrapper::SubmitReadAsync(
FSReadRequest& req, const IOOptions& opts,
std::function<void(FSReadRequest&)> cb, IODebugContext* dbg) {
const uint64_t start_time = clock_->NowNanos();
target()->SubmitReadAsync(
return target()->SubmitReadAsync(
req, opts,
[this, start_time, cb = std::move(cb)](FSReadRequest& completed_req) {
const uint64_t elapsed = clock_->NowNanos() - start_time;
+1 -1
View File
@@ -233,7 +233,7 @@ class FSRandomAccessFileTracingWrapper : public FSRandomAccessFileOwnerWrapper {
void* cb_arg, void** io_handle, IOHandleDeleter* del_fn,
IODebugContext* dbg) override;
void SubmitReadAsync(FSReadRequest& req, const IOOptions& opts,
bool SubmitReadAsync(FSReadRequest& req, const IOOptions& opts,
std::function<void(FSReadRequest&)> cb,
IODebugContext* dbg) override;
+74 -55
View File
@@ -34,6 +34,7 @@
#include <algorithm>
#include <ctime>
#include <exception>
#include <stdexcept>
// Get nano time includes
#if defined(OS_LINUX) || defined(OS_FREEBSD)
@@ -46,7 +47,6 @@
#endif
#include <deque>
#include <set>
#include <thread>
#include <vector>
#include "env/composite_env_wrapper.h"
@@ -60,22 +60,22 @@
#include "rocksdb/slice.h"
#include "rocksdb/utilities/object_registry.h"
#include "test_util/sync_point.h"
#include "util/atomic.h"
#include "util/coding.h"
#include "util/compression_context_cache.h"
#include "util/mutexlock.h"
#include "util/random.h"
#include "util/string_util.h"
#include "util/thread_local.h"
#include "util/threadpool_imp.h"
#if USE_COROUTINES
#if USE_COROUTINES && FOLLY_HAS_LIBURING
#include "folly/executors/IOThreadPoolExecutor.h"
#include "folly/io/async/EventBaseManager.h"
#if FOLLY_HAS_LIBURING
#include "folly/executors/thread_factory/NamedThreadFactory.h"
#include "folly/io/async/EventBase.h"
#include "folly/io/async/EventBaseManager.h"
#include "folly/io/async/IoUringBackend.h"
#endif // FOLLY_HAS_LIBURING
#endif
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
#if !defined(TMPFS_MAGIC)
#define TMPFS_MAGIC 0x01021994
@@ -97,11 +97,10 @@ inline mode_t GetDBFileMode(bool allow_non_owner_access) {
return allow_non_owner_access ? 0644 : 0600;
}
#if USE_COROUTINES
#if FOLLY_HAS_LIBURING
#if USE_COROUTINES && FOLLY_HAS_LIBURING
folly::IoUringOptions GetReadIOUringOptions() {
folly::IoUringOptions options;
options.setMaxSubmit(512);
options.setMaxSubmit(256);
options.setCapacity(1024);
options.setMinCapacity(512);
options.setRegisterRingFd(true);
@@ -110,42 +109,69 @@ folly::IoUringOptions GetReadIOUringOptions() {
return options;
}
#endif // FOLLY_HAS_LIBURING
struct ReadIOExecutorState {
ReadIOExecutorState() {
#if FOLLY_HAS_LIBURING
try {
// Older Linux kernels can support basic io_uring while rejecting some
// of the required setup flags. Validate the worker options at runtime
// before exposing the lazy executor.
std::make_unique<folly::IoUringBackend>(GetReadIOUringOptions());
folly::EventBase::Options event_base_options;
event_base_options.setBackendFactory([]() {
return std::make_unique<folly::IoUringBackend>(GetReadIOUringOptions());
});
event_base_manager_ =
std::make_unique<folly::EventBaseManager>(event_base_options);
executor_ = std::make_unique<folly::IOThreadPoolExecutor>(
std::max(1u, std::thread::hardware_concurrency()),
/*minThreads=*/0,
std::make_shared<folly::NamedThreadFactory>("RocksDBAsyncRead"),
event_base_manager_.get());
} catch (const std::runtime_error&) {
executor_.reset();
event_base_manager_.reset();
return;
}
#else
event_base_manager_ = nullptr;
executor_ = nullptr;
#endif // FOLLY_HAS_LIBURING
ReadIOExecutorState() = default;
ReadIOExecutorState(const ReadIOExecutorState&) = delete;
ReadIOExecutorState& operator=(const ReadIOExecutorState&) = delete;
ReadIOExecutorState(ReadIOExecutorState&&) = delete;
ReadIOExecutorState& operator=(ReadIOExecutorState&&) = delete;
~ReadIOExecutorState() {
delete executor_.LoadRelaxed();
delete event_base_manager_.LoadRelaxed();
}
std::unique_ptr<folly::EventBaseManager> event_base_manager_;
std::unique_ptr<folly::IOThreadPoolExecutor> executor_;
folly::IOExecutor* GetExecutor() { return executor_.Load(); }
folly::EventBaseManager* GetEventBaseManager() {
return event_base_manager_.Load();
}
void SetThreads(int num) {
assert(num > 0);
MutexLock lock(&mutex_);
const size_t requested_threads = static_cast<size_t>(num);
auto* executor = executor_.Load();
if (executor != nullptr) {
if (requested_threads > executor->numThreads()) {
try {
executor->setNumThreads(requested_threads);
} catch (const std::exception&) {
}
}
return;
}
try {
folly::EventBase::Options event_base_options;
event_base_options.setBackendFactory(
[]() -> std::unique_ptr<folly::EventBaseBackendBase> {
try {
auto backend = std::make_unique<folly::IoUringBackend>(
GetReadIOUringOptions());
SetCurrentThreadReadIOUringBackendAvailable();
return backend;
} catch (const std::exception&) {
return folly::EventBase::getDefaultBackend();
}
});
auto event_base_manager =
std::make_unique<folly::EventBaseManager>(event_base_options);
auto new_executor = std::make_unique<folly::IOThreadPoolExecutor>(
requested_threads, 0,
std::make_shared<folly::NamedThreadFactory>("RocksDBAsyncRead"),
event_base_manager.get());
event_base_manager_.Store(event_base_manager.release());
executor_.Store(new_executor.release());
} catch (const std::exception&) {
return;
}
}
port::Mutex mutex_;
Atomic<folly::EventBaseManager*> event_base_manager_{nullptr};
Atomic<folly::IOThreadPoolExecutor*> executor_{nullptr};
};
#endif // USE_COROUTINES
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
// list of pathnames that are locked
// Only used for error message.
@@ -207,22 +233,15 @@ class PosixFileSystem : public FileSystem {
const char* Name() const override { return kClassName(); }
const char* NickName() const override { return kDefaultName(); }
#if USE_COROUTINES
#if USE_COROUTINES && FOLLY_HAS_LIBURING
folly::IOExecutor* GetReadExecutor() override {
return read_io_executor_state_.executor_.get();
return read_io_executor_state_.GetExecutor();
}
void SetReadIOExecutorThreads(int num) override {
assert(num > 0);
if (num <= 0) {
return;
}
if (read_io_executor_state_.executor_ != nullptr) {
read_io_executor_state_.executor_->setNumThreads(
static_cast<size_t>(num));
}
read_io_executor_state_.SetThreads(num);
}
#endif // USE_COROUTINES
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
~PosixFileSystem() override = default;
bool IsInstanceOf(const std::string& name) const override {
@@ -350,7 +369,7 @@ class PosixFileSystem : public FileSystem {
options
#if USE_COROUTINES && FOLLY_HAS_LIBURING
,
read_io_executor_state_.event_base_manager_.get()
read_io_executor_state_.GetEventBaseManager()
#endif
#if defined(ROCKSDB_IOURING_PRESENT)
,
@@ -1391,9 +1410,9 @@ class PosixFileSystem : public FileSystem {
supported_ops |= (1 << FSSupportedOps::kFSPrefetch);
}
#if USE_COROUTINES
#if USE_COROUTINES && FOLLY_HAS_LIBURING
ReadIOExecutorState read_io_executor_state_;
#endif // USE_COROUTINES
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
#if defined(ROCKSDB_IOURING_PRESENT)
// io_uring instance
+26 -3
View File
@@ -87,6 +87,20 @@ IOStatus IOError(const std::string& context, const std::string& file_name,
}
}
#if USE_COROUTINES && FOLLY_HAS_LIBURING
namespace {
thread_local bool current_thread_read_io_uring_backend_available = false;
} // namespace
void SetCurrentThreadReadIOUringBackendAvailable() {
current_thread_read_io_uring_backend_available = true;
}
bool IsCurrentThreadReadIOUringBackendAvailable() {
return current_thread_read_io_uring_backend_available;
}
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
// A wrapper for fadvise, if the platform doesn't support fadvise,
// it will simply return 0.
int Fadvise(int fd, off_t offset, size_t len, int advice) {
@@ -701,9 +715,9 @@ struct PosixSubmitReadAsyncState {
} // namespace
void PosixRandomAccessFile::SubmitReadAsync(
FSReadRequest& req, const IOOptions& /*opts*/,
std::function<void(FSReadRequest&)> cb, IODebugContext* /*dbg*/) {
bool PosixRandomAccessFile::SubmitReadAsync(
FSReadRequest& req, const IOOptions& opts,
std::function<void(FSReadRequest&)> cb, IODebugContext* dbg) {
if (use_direct_io()) {
assert(IsSectorAligned(req.offset, GetRequiredBufferAlignment()));
assert(IsSectorAligned(req.len, GetRequiredBufferAlignment()));
@@ -711,6 +725,14 @@ void PosixRandomAccessFile::SubmitReadAsync(
}
assert(req.len == 0 || req.scratch != nullptr);
// Workers using the default EventBase backend read synchronously.
if (!IsCurrentThreadReadIOUringBackendAvailable()) {
req.status =
Read(req.offset, req.len, opts, &(req.result), req.scratch, dbg);
cb(req);
return false;
}
assert(read_event_base_manager_);
folly::EventBase* event_base =
read_event_base_manager_->getExistingEventBase();
@@ -735,6 +757,7 @@ void PosixRandomAccessFile::SubmitReadAsync(
} else {
state->Queue();
}
return true;
}
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
+5 -1
View File
@@ -77,6 +77,10 @@ std::string IOErrorMsg(const std::string& context,
// file_name can be left empty if it is not unkown.
IOStatus IOError(const std::string& context, const std::string& file_name,
int err_number);
#if USE_COROUTINES && FOLLY_HAS_LIBURING
void SetCurrentThreadReadIOUringBackendAvailable();
bool IsCurrentThreadReadIOUringBackendAvailable();
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
#if !defined(OS_WIN)
bool PosixPositionedWrite(int fd, const char* buf, size_t nbyte, off_t offset);
#endif
@@ -465,7 +469,7 @@ class PosixRandomAccessFile : public FSRandomAccessFile {
IODebugContext* dbg) override;
#if USE_COROUTINES && FOLLY_HAS_LIBURING
void SubmitReadAsync(FSReadRequest& req, const IOOptions& opts,
bool SubmitReadAsync(FSReadRequest& req, const IOOptions& opts,
std::function<void(FSReadRequest&)> cb,
IODebugContext* dbg) override;
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
+8 -11
View File
@@ -12,7 +12,6 @@
#include "rocksdb/file_system.h"
#if USE_COROUTINES && FOLLY_HAS_LIBURING
#include "folly/ScopeGuard.h"
#include "folly/executors/IOThreadPoolExecutor.h"
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
@@ -21,24 +20,22 @@ namespace ROCKSDB_NAMESPACE {
#if USE_COROUTINES && FOLLY_HAS_LIBURING
class PosixFileSystemTest : public testing::Test {};
TEST_F(PosixFileSystemTest, SetsReadIOExecutorThreads) {
TEST_F(PosixFileSystemTest, OnlyIncreasesReadIOExecutor) {
auto fs = FileSystem::Default();
fs->SetReadIOExecutorThreads(1);
auto* read_executor = fs->GetReadExecutor();
if (read_executor == nullptr) {
return;
}
auto* thread_pool = dynamic_cast<folly::IOThreadPoolExecutor*>(read_executor);
ASSERT_NE(thread_pool, nullptr);
ASSERT_NE(read_executor->getEventBase(), nullptr);
const auto original_threads = thread_pool->numThreads();
auto restore_threads = folly::makeGuard([&] {
fs->SetReadIOExecutorThreads(static_cast<int>(original_threads));
});
const size_t original_threads = thread_pool->numThreads();
fs->SetReadIOExecutorThreads(1);
ASSERT_EQ(thread_pool->numThreads(), static_cast<size_t>(1));
fs->SetReadIOExecutorThreads(2);
ASSERT_EQ(thread_pool->numThreads(), static_cast<size_t>(2));
fs->SetReadIOExecutorThreads(static_cast<int>(original_threads + 1));
EXPECT_EQ(thread_pool->numThreads(), original_threads + 1);
fs->SetReadIOExecutorThreads(static_cast<int>(original_threads));
EXPECT_EQ(thread_pool->numThreads(), original_threads + 1);
}
#endif // USE_COROUTINES && FOLLY_HAS_LIBURING
+18 -16
View File
@@ -20,11 +20,9 @@
namespace ROCKSDB_NAMESPACE {
#if defined(WITH_COROUTINES)
inline folly::coro::Task<void> SubmitMultiReadAsync(FSRandomAccessFile* file,
FSReadRequest* fs_reqs,
size_t num_fs_reqs,
const IOOptions& opts,
IODebugContext* dbg) {
inline folly::coro::Task<void> SubmitMultiReadAsync(
FSRandomAccessFile* file, FSReadRequest* fs_reqs, size_t num_fs_reqs,
const IOOptions& opts, Statistics* stats, IODebugContext* dbg) {
if (num_fs_reqs == 0) {
co_return;
}
@@ -39,14 +37,16 @@ inline folly::coro::Task<void> SubmitMultiReadAsync(FSRandomAccessFile* file,
[&] { assert(pending.load(std::memory_order_acquire) == 0); });
#endif // NDEBUG
for (size_t i = 0; i < num_fs_reqs; ++i) {
file->SubmitReadAsync(
fs_reqs[i], opts,
[executor, &baton, &pending](FSReadRequest& /*req*/) {
if (pending.fetch_sub(1) == 1) {
executor->add([&baton] { baton.post(); });
}
},
dbg);
if (!file->SubmitReadAsync(
fs_reqs[i], opts,
[executor, &baton, &pending](FSReadRequest& /*req*/) {
if (pending.fetch_sub(1) == 1) {
executor->add([&baton] { baton.post(); });
}
},
dbg)) {
RecordTick(stats, FILE_SUBMIT_ASYNC_READ_FALLBACK, 1);
}
}
co_await baton;
@@ -145,7 +145,8 @@ DEFINE_SYNC_AND_ASYNC(IOStatus, RandomAccessFileReader::Read)
fs_req.status.PermitUncheckedError();
TEST_SYNC_POINT_CALLBACK(
"RandomAccessFileReader::ReadCoroutine:SubmitReadAsync", &fs_req);
co_await SubmitMultiReadAsync(file_.get(), &fs_req, 1, opts, dbg);
co_await SubmitMultiReadAsync(file_.get(), &fs_req, 1, opts, stats_,
dbg);
io_s = fs_req.status;
tmp = fs_req.result;
#else
@@ -224,7 +225,8 @@ DEFINE_SYNC_AND_ASYNC(IOStatus, RandomAccessFileReader::Read)
if (fs_req.scratch != nullptr) {
TEST_SYNC_POINT_CALLBACK(
"RandomAccessFileReader::ReadCoroutine:SubmitReadAsync", &fs_req);
co_await SubmitMultiReadAsync(file_.get(), &fs_req, 1, opts, dbg);
co_await SubmitMultiReadAsync(file_.get(), &fs_req, 1, opts, stats_,
dbg);
io_s = fs_req.status;
tmp_result = fs_req.result;
} else {
@@ -408,7 +410,7 @@ DEFINE_SYNC_AND_ASYNC(IOStatus, RandomAccessFileReader::MultiRead)
const_cast<void*>(static_cast<void*>(dbg)));
#if defined(USE_COROUTINES) && defined(WITH_COROUTINES)
co_await SubmitMultiReadAsync(file_.get(), fs_reqs, num_fs_reqs, opts,
dbg);
stats_, dbg);
#else
io_s = file_->MultiRead(fs_reqs, num_fs_reqs, opts, dbg);
#endif
+39
View File
@@ -16,6 +16,14 @@
#include "test_util/testutil.h"
#include "util/random.h"
#if defined(USE_COROUTINES)
#include "folly/Executor.h"
#include "folly/coro/BlockingWait.h"
#include "folly/coro/Task.h"
#include "folly/executors/IOThreadPoolExecutor.h"
#include "rocksdb/statistics.h"
#endif
namespace ROCKSDB_NAMESPACE {
class RandomAccessFileReaderTest : public testing::Test {
@@ -101,8 +109,39 @@ class ScratchObservingRandomAccessFile : public FSRandomAccessFileOwnerWrapper {
private:
bool* saw_null_scratch_;
};
} // namespace
#if defined(USE_COROUTINES)
TEST_F(RandomAccessFileReaderTest, CountsCoroutineReadSyncFallback) {
const std::string fname = "async-read-fallback";
const std::string content = "async read fallback";
Write(fname, content);
const std::string fpath = Path(fname);
std::unique_ptr<FSRandomAccessFile> file;
ASSERT_OK(fs_->NewRandomAccessFile(fpath, FileOptions(), &file, nullptr));
auto statistics = CreateDBStatistics();
RandomAccessFileReader reader(std::move(file), fpath,
env_->GetSystemClock().get(), nullptr,
statistics.get());
std::string scratch(content.size(), '\0');
Slice result;
folly::IOThreadPoolExecutor executor(1);
folly::EventBase* event_base = executor.getEventBase();
ASSERT_NE(event_base, nullptr);
ASSERT_OK(folly::coro::blockingWait(folly::coro::co_withExecutor(
folly::Executor::getKeepAliveToken(event_base),
reader.ReadCoroutine(IOOptions(), 0, content.size(), &result,
scratch.data(), nullptr, nullptr))));
ASSERT_EQ(content, result.ToString());
EXPECT_EQ(1, statistics->getTickerCount(FILE_SUBMIT_ASYNC_READ_FALLBACK));
}
#endif // USE_COROUTINES
// Skip the following tests in lite mode since direct I/O is unsupported.
TEST_F(RandomAccessFileReaderTest, ReadDirectIO) {
+6
View File
@@ -4993,6 +4993,12 @@ rocksdb_options_set_verify_sst_unique_id_in_manifest(rocksdb_options_t* opt,
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_verify_sst_unique_id_in_manifest(rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_read_io_executor_threads(
rocksdb_options_t* opt, int v);
extern ROCKSDB_LIBRARY_API int rocksdb_options_get_read_io_executor_threads(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API const char* rocksdb_options_get_db_log_dir(
rocksdb_options_t* opt, size_t* size);
+3 -1
View File
@@ -1113,7 +1113,9 @@ class DB {
// is available, RocksDB can run an internal read coroutine on the read
// executor, suspend while async file IO is outstanding, and resume when the
// filesystem signals completion. The callback is invoked after the requested
// statuses and output buffers have been populated.
// statuses and output buffers have been populated. Applications can set
// `DBOptions::read_io_executor_threads` before opening the DB to configure
// executor parallelism for their workload.
//
// Callers must keep the DB, callback, inputs, and output buffers alive until
// the callback returns. The callback may run inline before the async method
+10 -5
View File
@@ -466,8 +466,9 @@ class FileSystem : public Customizable {
// coroutine read execution is not supported.
virtual folly::IOExecutor* GetReadExecutor() { return nullptr; }
// Set the maximum number of threads used by the FileSystem-owned read IO
// executor. The number must be positive.
// Increase the maximum number of threads used by the FileSystem-owned read
// IO executor to at least the requested number. The executor is not reduced
// when its current maximum is larger. The number must be positive.
virtual void SetReadIOExecutorThreads(int /*number*/) {}
// Create a brand new sequentially-readable file with the specified name.
@@ -1133,14 +1134,18 @@ class FSRandomAccessFile {
// completion executor and must update req.status and invoke cb(req) exactly
// once.
//
// Returns true when the asynchronous path is used and false when the request
// is completed using the synchronous Read fallback.
//
// Default implementation is to read the data synchronously and invoke the
// callback before returning.
virtual void SubmitReadAsync(FSReadRequest& req, const IOOptions& opts,
virtual bool SubmitReadAsync(FSReadRequest& req, const IOOptions& opts,
std::function<void(FSReadRequest&)> cb,
IODebugContext* dbg) {
req.status =
Read(req.offset, req.len, opts, &(req.result), req.scratch, dbg);
cb(req);
return false;
}
// EXPERIMENTAL
@@ -1919,10 +1924,10 @@ class FSRandomAccessFileWrapper : public FSRandomAccessFile {
IODebugContext* dbg) override {
return target()->ReadAsync(req, opts, cb, cb_arg, io_handle, del_fn, dbg);
}
void SubmitReadAsync(FSReadRequest& req, const IOOptions& opts,
bool SubmitReadAsync(FSReadRequest& req, const IOOptions& opts,
std::function<void(FSReadRequest&)> cb,
IODebugContext* dbg) override {
target()->SubmitReadAsync(req, opts, std::move(cb), dbg);
return target()->SubmitReadAsync(req, opts, std::move(cb), dbg);
}
Temperature GetTemperature() const override {
return target_->GetTemperature();
+5
View File
@@ -780,6 +780,11 @@ struct DBOptions {
// Default: 16
int max_file_opening_threads = 16;
// Requested maximum number of threads in the shared read I/O executor. A DB
// open can increase the executor to this size but cannot reduce it. Used
// exclusively for asynchronous read requests (e.g. GetAsync, MultiGetAsync).
int read_io_executor_threads = 1;
// If true, SST files are opened and validated asynchronously in the
// background after DB::Open returns. This reduces DB open time for
// databases with many SST files and high latency file systems. Mostly useful
+3
View File
@@ -653,6 +653,9 @@ enum Tickers : uint32_t {
CHECKPOINT_READ_BYTES,
CHECKPOINT_WRITE_BYTES,
// Number of SubmitReadAsync calls that fell back to a synchronous read
FILE_SUBMIT_ASYNC_READ_FALLBACK,
TICKER_ENUM_MAX
};
+1 -1
View File
@@ -13,7 +13,7 @@
// minor or major version number planned for release.
#define ROCKSDB_MAJOR 11
#define ROCKSDB_MINOR 8
#define ROCKSDB_PATCH 0
#define ROCKSDB_PATCH 1
// Make it easy to do conditional compilation based on version checks, i.e.
// #if ROCKSDB_VERSION_GE(4, 5, 6)
+4
View File
@@ -5357,6 +5357,8 @@ class TickerTypeJni {
return -0x7E;
case ROCKSDB_NAMESPACE::Tickers::ATOMIC_FLUSH_REQUEST_REASON_OTHER:
return -0x7F;
case ROCKSDB_NAMESPACE::Tickers::FILE_SUBMIT_ASYNC_READ_FALLBACK:
return -0x80;
case ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX:
// -0x54 is the max value at this time. Since these values are exposed
// directly to Java clients, we'll keep the value the same till the next
@@ -5904,6 +5906,8 @@ class TickerTypeJni {
ATOMIC_FLUSH_REQUEST_REASON_MEMTABLE_MAX_RANGE_DELETIONS;
case -0x7F:
return ROCKSDB_NAMESPACE::Tickers::ATOMIC_FLUSH_REQUEST_REASON_OTHER;
case -0x80:
return ROCKSDB_NAMESPACE::Tickers::FILE_SUBMIT_ASYNC_READ_FALLBACK;
case -0x54:
// -0x54 is the max value at this time. Since these values are exposed
// directly to Java clients, we'll keep the value the same till the next
@@ -1077,6 +1077,11 @@ public enum TickerType {
*/
ATOMIC_FLUSH_REQUEST_REASON_OTHER((byte) -0x7F),
/**
* Number of SubmitReadAsync calls that fell back to a synchronous read
*/
FILE_SUBMIT_ASYNC_READ_FALLBACK((byte) -0x80),
TICKER_ENUM_MAX((byte) -0x54);
private final byte value;
+2
View File
@@ -336,6 +336,8 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
"rocksdb.atomic_flush.request.reason.other"},
{CHECKPOINT_READ_BYTES, "rocksdb.checkpoint.read.bytes"},
{CHECKPOINT_WRITE_BYTES, "rocksdb.checkpoint.write.bytes"},
{FILE_SUBMIT_ASYNC_READ_FALLBACK,
"rocksdb.file.submit.async.read.fallback"},
};
const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
+7
View File
@@ -304,6 +304,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct ImmutableDBOptions, max_file_opening_threads),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"read_io_executor_threads",
{offsetof(struct ImmutableDBOptions, read_io_executor_threads),
OptionType::kInt, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"table_cache_numshardbits",
{offsetof(struct ImmutableDBOptions, table_cache_numshardbits),
OptionType::kInt, OptionVerificationType::kNormal,
@@ -772,6 +776,7 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
info_log(options.info_log),
info_log_level(options.info_log_level),
max_file_opening_threads(options.max_file_opening_threads),
read_io_executor_threads(options.read_io_executor_threads),
statistics(options.statistics),
use_fsync(options.use_fsync),
db_paths(options.db_paths),
@@ -886,6 +891,8 @@ void ImmutableDBOptions::Dump(Logger* log) const {
info_log.get());
ROCKS_LOG_HEADER(log, " Options.max_file_opening_threads: %d",
max_file_opening_threads);
ROCKS_LOG_HEADER(log, " Options.read_io_executor_threads: %d",
read_io_executor_threads);
ROCKS_LOG_HEADER(log, " Options.statistics: %p",
stats);
if (stats) {
+1
View File
@@ -36,6 +36,7 @@ struct ImmutableDBOptions {
std::shared_ptr<Logger> info_log;
InfoLogLevel info_log_level;
int max_file_opening_threads;
int read_io_executor_threads;
std::shared_ptr<Statistics> statistics;
bool use_fsync;
std::vector<DbPath> db_paths;
+2
View File
@@ -84,6 +84,8 @@ void BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
options.max_open_files = mutable_db_options.max_open_files;
options.max_file_opening_threads =
immutable_db_options.max_file_opening_threads;
options.read_io_executor_threads =
immutable_db_options.read_io_executor_threads;
options.max_total_wal_size = mutable_db_options.max_total_wal_size;
options.statistics = immutable_db_options.statistics;
options.use_fsync = immutable_db_options.use_fsync;
+1
View File
@@ -406,6 +406,7 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
"table_cache_numshardbits=28;"
"max_open_files=72;"
"max_file_opening_threads=35;"
"read_io_executor_threads=7;"
"max_background_jobs=8;"
"max_background_compactions=33;"
"use_fsync=true;"
@@ -1 +0,0 @@
`ReadOptions::value_size_soft_limit` now also bounds the blob values resolved from pre-flush blob direct write references during `MultiGet`/`MultiGetEntity` (previously these were resolved after the limit check and read without bound). Enforcement is "always make progress": at least one key is read even if its value alone exceeds the limit, and only once the returned size exceeds the limit do subsequent keys get `Status::Aborted` -- so a caller retrying the aborted keys cannot loop forever on a single value that by itself exceeds the limit.
@@ -0,0 +1 @@
Fixed RocksDB builds on big-endian architectures such as s390x by disabling the optional persistent fault-injection log, whose binary format is little-endian-only.
@@ -1 +0,0 @@
Reduced copying when reading wide-column entities that have blob-referenced columns, whether stored in separate blob files or embedded (same-file) as blob records in the same SST. On the point-lookup (`GetEntity`/`MultiGetEntity`) and blob-backed memtable/direct-write read paths, resolving these columns no longer re-serializes the whole entity into a fresh buffer: inline columns are referenced in place in the pinned entity and each resolved blob value is referenced in place from its fetched buffer. This is enabled by an internal change to `PinnableWideColumns` that lets its columns reference multiple backing buffers.
@@ -1 +0,0 @@
Add callback-based asynchronous read APIs, `DB::GetAsync()` and `DB::MultiGetAsync()`. The idea is that when IO is required, RocksDB can suspend its internal coroutine read path, allowing the read executor thread to do other work. When the IO is complete, RocksDB invokes the user callback. This requires filesystem support for full performance benefits. A new filesystem API `FSRandomAccessFile::SubmitReadAsync()` is introduced for this. Unlike `ReadAsync`, the filesystem is responsible for eventually calling the callback. `FileSystem::GetReadExecutor()` returns the IO executor whose EventBases run coroutine read processing. In the Posix filesystem, an IO-uring based event loop is used to complete the IO.
@@ -1 +0,0 @@
Added `FlushOptions::listener_wait` (default `false`). When set together with `FlushOptions::wait == true`, `DB::Flush()` will not return until the registered `EventListener::OnFlushCompleted` callbacks for the flushed memtables have finished running. By default (`false`), `Flush(wait=true)` may return as soon as the flush result is committed, which can be before (or while) the `OnFlushCompleted` callbacks execute on the background flush thread. Also added the corresponding C API `rocksdb_flushoptions_set_listener_wait()` / `rocksdb_flushoptions_get_listener_wait()`.
@@ -1 +0,0 @@
Deprecated `PinnableWideColumns::serialized_size()` in favor of the new `PinnableWideColumns::payload_size()`, which returns the total size of the columns' names and values (for a plain value this equals the value size). Relatedly, wide-column read accounting changed subtly: read statistics for entities (`BYTES_READ`, `BYTES_PER_READ`, and the per-key byte counts for `GetEntity`/`MultiGetEntity`) and the `ReadOptions::value_size_soft_limit` threshold now measure entities by this payload size instead of the serialized entity size. This drops serialization framing bytes from those measurements (so reported/limited sizes for entity reads are slightly smaller than before) and makes plain-value and wide-column accounting consistent.
+4 -1
View File
@@ -26,6 +26,7 @@
#include "env/io_posix.h"
#include "monitoring/thread_status_util.h"
#include "port/lang.h"
#include "port/port.h"
#include "port/stack_trace.h"
#include "rocksdb/env.h"
#include "rocksdb/io_status.h"
@@ -79,7 +80,9 @@ uint8_t DetailPayloadSize(const InjectedErrorLog::TaggedEntryDetail& detail) {
int OpenInjectedErrorLogFile(const std::string& path) {
#ifndef OS_WIN
if (path.empty()) {
// Entry is written in native layout, while the decoder expects little-endian
// fields. Keep the optional log disabled on unsupported architectures.
if (!port::kLittleEndian || path.empty()) {
return -1;
}
int flags = O_WRONLY | O_CREAT | O_TRUNC;
+5 -2
View File
@@ -44,6 +44,8 @@ namespace ROCKSDB_NAMESPACE {
// A binary log that records injected errors directly to a file.
// Thread-safe for concurrent writes. The log file can be flushed on exit or
// from a signal handler so records survive clean exits and crash paths.
// Disabled on big-endian platforms because the decoder expects little-endian
// fields in the native Entry layout.
class InjectedErrorLog {
public:
static constexpr size_t kMaxOpNameLen = 32;
@@ -86,12 +88,13 @@ class InjectedErrorLog {
char op_name[kMaxOpNameLen];
char file_name[kMaxFileNameLen];
char status_message[kMaxStatusMessageLen];
// Explicit tail padding so the size does not depend on the alignment of
// uint64_t (4 bytes on i386, 8 bytes on most other targets).
uint8_t padding[4];
};
static_assert(sizeof(Entry) == 256,
"Injected error log entry size must stay stable");
static_assert(port::kLittleEndian,
"Injected error log requires little-endian platforms");
explicit InjectedErrorLog(const std::string& path);
~InjectedErrorLog();
+21
View File
@@ -10,6 +10,7 @@
#include <thread>
#include <vector>
#include "port/port.h"
#include "test_util/testharness.h"
namespace ROCKSDB_NAMESPACE {
@@ -79,6 +80,11 @@ class CloseCountingWritableFile : public FSWritableFileOwnerWrapper {
// the expected fixed-width fields. The test writes one entry, flushes the
// file, and decodes the raw bytes back into an entry struct.
TEST_F(InjectedErrorLogTest, BasicRecordAndFlush) {
if (!port::kLittleEndian) {
ROCKSDB_GTEST_BYPASS(
"Persistent injected error log requires little-endian");
return;
}
std::string path = test::PerThreadDBPath("injected_error_log_basic.bin");
{
InjectedErrorLog log(path);
@@ -114,6 +120,11 @@ TEST_F(InjectedErrorLogTest, BasicRecordAndFlush) {
// to the old in-memory ring size. The test writes more than 1,000 entries and
// checks that both the first and last ones are still present in the file.
TEST_F(InjectedErrorLogTest, DirectLogKeepsAllEntries) {
if (!port::kLittleEndian) {
ROCKSDB_GTEST_BYPASS(
"Persistent injected error log requires little-endian");
return;
}
std::string path =
test::PerThreadDBPath("injected_error_log_all_entries.bin");
{
@@ -144,6 +155,11 @@ TEST_F(InjectedErrorLogTest, DirectLogKeepsAllEntries) {
// shared file. The test has several threads emit records concurrently and then
// checks the raw file size matches the total number of writes.
TEST_F(InjectedErrorLogTest, ConcurrentRecord) {
if (!port::kLittleEndian) {
ROCKSDB_GTEST_BYPASS(
"Persistent injected error log requires little-endian");
return;
}
std::string path = test::PerThreadDBPath("injected_error_log_concurrent.bin");
constexpr int kNumThreads = 4;
constexpr int kRecordsPerThread = 200;
@@ -180,6 +196,11 @@ TEST_F(InjectedErrorLogTest, ConcurrentRecord) {
// in the fixed-width record. The test logs a path longer than the file-name
// field and checks the stored sample matches the expected tail bytes.
TEST_F(InjectedErrorLogTest, LongFileNameKeepsSuffix) {
if (!port::kLittleEndian) {
ROCKSDB_GTEST_BYPASS(
"Persistent injected error log requires little-endian");
return;
}
std::string path =
test::PerThreadDBPath("injected_error_log_suffix_truncation.bin");
const std::string long_file_name =