Add rate limiter priority to ReadOptions (#9424)
Summary: Users can set the priority for file reads associated with their operation by setting `ReadOptions::rate_limiter_priority` to something other than `Env::IO_TOTAL`. Rate limiting `VerifyChecksum()` and `VerifyFileChecksums()` is the motivation for this PR, so it also includes benchmarks and minor bug fixes to get that working. `RandomAccessFileReader::Read()` already had support for rate limiting compaction reads. I changed that rate limiting to be non-specific to compaction, but rather performed according to the passed in `Env::IOPriority`. Now the compaction read rate limiting is supported by setting `rate_limiter_priority = Env::IO_LOW` on its `ReadOptions`. There is no default value for the new `Env::IOPriority` parameter to `RandomAccessFileReader::Read()`. That means this PR goes through all callers (in some cases multiple layers up the call stack) to find a `ReadOptions` to provide the priority. There are TODOs for cases I believe it would be good to let user control the priority some day (e.g., file footer reads), and no TODO in cases I believe it doesn't matter (e.g., trace file reads). The API doc only lists the missing cases where a file read associated with a provided `ReadOptions` cannot be rate limited. For cases like file ingestion checksum calculation, there is no API to provide `ReadOptions` or `Env::IOPriority`, so I didn't count that as missing. Pull Request resolved: https://github.com/facebook/rocksdb/pull/9424 Test Plan: - new unit tests - new benchmarks on ~50MB database with 1MB/s read rate limit and 100ms refill interval; verified with strace reads are chunked (at 0.1MB per chunk) and spaced roughly 100ms apart. - setup command: `./db_bench -benchmarks=fillrandom,compact -db=/tmp/testdb -target_file_size_base=1048576 -disable_auto_compactions=true -file_checksum=true` - benchmarks command: `strace -ttfe pread64 ./db_bench -benchmarks=verifychecksum,verifyfilechecksums -use_existing_db=true -db=/tmp/testdb -rate_limiter_bytes_per_sec=1048576 -rate_limit_bg_reads=1 -rate_limit_user_ops=true -file_checksum=true` - crash test using IO_USER priority on non-validation reads with https://github.com/facebook/rocksdb/issues/9567 reverted: `python3 tools/db_crashtest.py blackbox --max_key=1000000 --write_buffer_size=524288 --target_file_size_base=524288 --level_compaction_dynamic_level_bytes=true --duration=3600 --rate_limit_bg_reads=true --rate_limit_user_ops=true --rate_limiter_bytes_per_sec=10485760 --interval=10` Reviewed By: hx235 Differential Revision: D33747386 Pulled By: ajkr fbshipit-source-id: a2d985e97912fba8c54763798e04f006ccc56e0cmain
parent
1cda273dc3
commit
babe56ddba
@ -0,0 +1,262 @@ |
||||
// Copyright (c) 2022-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 "db/db_test_util.h" |
||||
#include "port/stack_trace.h" |
||||
#include "util/file_checksum_helper.h" |
||||
|
||||
namespace ROCKSDB_NAMESPACE { |
||||
|
||||
class DBRateLimiterTest |
||||
: public DBTestBase, |
||||
public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> { |
||||
public: |
||||
DBRateLimiterTest() |
||||
: DBTestBase("db_rate_limiter_test", /*env_do_fsync=*/false), |
||||
use_direct_io_(std::get<0>(GetParam())), |
||||
use_block_cache_(std::get<1>(GetParam())), |
||||
use_readahead_(std::get<2>(GetParam())) {} |
||||
|
||||
void Init() { |
||||
options_ = GetOptions(); |
||||
Reopen(options_); |
||||
for (int i = 0; i < kNumFiles; ++i) { |
||||
for (int j = 0; j < kNumKeysPerFile; ++j) { |
||||
ASSERT_OK(Put(Key(i * kNumKeysPerFile + j), "val")); |
||||
} |
||||
ASSERT_OK(Flush()); |
||||
} |
||||
MoveFilesToLevel(1); |
||||
} |
||||
|
||||
BlockBasedTableOptions GetTableOptions() { |
||||
BlockBasedTableOptions table_options; |
||||
table_options.no_block_cache = !use_block_cache_; |
||||
return table_options; |
||||
} |
||||
|
||||
ReadOptions GetReadOptions() { |
||||
ReadOptions read_options; |
||||
read_options.rate_limiter_priority = Env::IO_USER; |
||||
read_options.readahead_size = use_readahead_ ? kReadaheadBytes : 0; |
||||
return read_options; |
||||
} |
||||
|
||||
Options GetOptions() { |
||||
Options options = CurrentOptions(); |
||||
options.disable_auto_compactions = true; |
||||
options.file_checksum_gen_factory.reset(new FileChecksumGenCrc32cFactory()); |
||||
options.rate_limiter.reset(NewGenericRateLimiter( |
||||
1 << 20 /* rate_bytes_per_sec */, 100 * 1000 /* refill_period_us */, |
||||
10 /* fairness */, RateLimiter::Mode::kAllIo)); |
||||
options.table_factory.reset(NewBlockBasedTableFactory(GetTableOptions())); |
||||
options.use_direct_reads = use_direct_io_; |
||||
return options; |
||||
} |
||||
|
||||
protected: |
||||
const static int kNumKeysPerFile = 1; |
||||
const static int kNumFiles = 3; |
||||
const static int kReadaheadBytes = 32 << 10; // 32KB
|
||||
|
||||
Options options_; |
||||
const bool use_direct_io_; |
||||
const bool use_block_cache_; |
||||
const bool use_readahead_; |
||||
}; |
||||
|
||||
std::string GetTestNameSuffix( |
||||
::testing::TestParamInfo<std::tuple<bool, bool, bool>> info) { |
||||
std::ostringstream oss; |
||||
if (std::get<0>(info.param)) { |
||||
oss << "DirectIO"; |
||||
} else { |
||||
oss << "BufferedIO"; |
||||
} |
||||
if (std::get<1>(info.param)) { |
||||
oss << "_BlockCache"; |
||||
} else { |
||||
oss << "_NoBlockCache"; |
||||
} |
||||
if (std::get<2>(info.param)) { |
||||
oss << "_Readahead"; |
||||
} else { |
||||
oss << "_NoReadahead"; |
||||
} |
||||
return oss.str(); |
||||
} |
||||
|
||||
#ifndef ROCKSDB_LITE |
||||
INSTANTIATE_TEST_CASE_P(DBRateLimiterTest, DBRateLimiterTest, |
||||
::testing::Combine(::testing::Bool(), ::testing::Bool(), |
||||
::testing::Bool()), |
||||
GetTestNameSuffix); |
||||
#else // ROCKSDB_LITE
|
||||
// Cannot use direct I/O in lite mode.
|
||||
INSTANTIATE_TEST_CASE_P(DBRateLimiterTest, DBRateLimiterTest, |
||||
::testing::Combine(::testing::Values(false), |
||||
::testing::Bool(), |
||||
::testing::Bool()), |
||||
GetTestNameSuffix); |
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_P(DBRateLimiterTest, Get) { |
||||
if (use_direct_io_ && !IsDirectIOSupported()) { |
||||
return; |
||||
} |
||||
Init(); |
||||
|
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
|
||||
int expected = 0; |
||||
for (int i = 0; i < kNumFiles; ++i) { |
||||
{ |
||||
std::string value; |
||||
ASSERT_OK(db_->Get(GetReadOptions(), Key(i * kNumKeysPerFile), &value)); |
||||
++expected; |
||||
} |
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
|
||||
{ |
||||
std::string value; |
||||
ASSERT_OK(db_->Get(GetReadOptions(), Key(i * kNumKeysPerFile), &value)); |
||||
if (!use_block_cache_) { |
||||
++expected; |
||||
} |
||||
} |
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
} |
||||
} |
||||
|
||||
TEST_P(DBRateLimiterTest, NewMultiGet) { |
||||
// The new void-returning `MultiGet()` APIs use `MultiRead()`, which does not
|
||||
// yet support rate limiting.
|
||||
if (use_direct_io_ && !IsDirectIOSupported()) { |
||||
return; |
||||
} |
||||
Init(); |
||||
|
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
|
||||
const int kNumKeys = kNumFiles * kNumKeysPerFile; |
||||
{ |
||||
std::vector<std::string> key_bufs; |
||||
key_bufs.reserve(kNumKeys); |
||||
std::vector<Slice> keys; |
||||
keys.reserve(kNumKeys); |
||||
for (int i = 0; i < kNumKeys; ++i) { |
||||
key_bufs.emplace_back(Key(i)); |
||||
keys.emplace_back(key_bufs[i]); |
||||
} |
||||
std::vector<Status> statuses(kNumKeys); |
||||
std::vector<PinnableSlice> values(kNumKeys); |
||||
db_->MultiGet(GetReadOptions(), dbfull()->DefaultColumnFamily(), kNumKeys, |
||||
keys.data(), values.data(), statuses.data()); |
||||
for (int i = 0; i < kNumKeys; ++i) { |
||||
ASSERT_TRUE(statuses[i].IsNotSupported()); |
||||
} |
||||
} |
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
} |
||||
|
||||
TEST_P(DBRateLimiterTest, OldMultiGet) { |
||||
// The old `vector<Status>`-returning `MultiGet()` APIs use `Read()`, which
|
||||
// supports rate limiting.
|
||||
if (use_direct_io_ && !IsDirectIOSupported()) { |
||||
return; |
||||
} |
||||
Init(); |
||||
|
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
|
||||
const int kNumKeys = kNumFiles * kNumKeysPerFile; |
||||
int expected = 0; |
||||
{ |
||||
std::vector<std::string> key_bufs; |
||||
key_bufs.reserve(kNumKeys); |
||||
std::vector<Slice> keys; |
||||
keys.reserve(kNumKeys); |
||||
for (int i = 0; i < kNumKeys; ++i) { |
||||
key_bufs.emplace_back(Key(i)); |
||||
keys.emplace_back(key_bufs[i]); |
||||
} |
||||
std::vector<std::string> values; |
||||
std::vector<Status> statuses = |
||||
db_->MultiGet(GetReadOptions(), keys, &values); |
||||
for (int i = 0; i < kNumKeys; ++i) { |
||||
ASSERT_OK(statuses[i]); |
||||
} |
||||
} |
||||
expected += kNumKeys; |
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
} |
||||
|
||||
TEST_P(DBRateLimiterTest, Iterator) { |
||||
if (use_direct_io_ && !IsDirectIOSupported()) { |
||||
return; |
||||
} |
||||
Init(); |
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(GetReadOptions())); |
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
|
||||
int expected = 0; |
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { |
||||
++expected; |
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
} |
||||
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { |
||||
// When `use_block_cache_ == true`, the reverse scan will access the blocks
|
||||
// loaded to cache during the above forward scan, in which case no further
|
||||
// file reads are expected.
|
||||
if (!use_block_cache_) { |
||||
++expected; |
||||
} |
||||
} |
||||
// Reverse scan does not read evenly (one block per iteration) due to
|
||||
// descending seqno ordering, so wait until after the loop to check total.
|
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
} |
||||
|
||||
#if !defined(ROCKSDB_LITE) |
||||
|
||||
TEST_P(DBRateLimiterTest, VerifyChecksum) { |
||||
if (use_direct_io_ && !IsDirectIOSupported()) { |
||||
return; |
||||
} |
||||
Init(); |
||||
|
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
|
||||
ASSERT_OK(db_->VerifyChecksum(GetReadOptions())); |
||||
// The files are tiny so there should have just been one read per file.
|
||||
int expected = kNumFiles; |
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
} |
||||
|
||||
TEST_P(DBRateLimiterTest, VerifyFileChecksums) { |
||||
if (use_direct_io_ && !IsDirectIOSupported()) { |
||||
return; |
||||
} |
||||
Init(); |
||||
|
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
|
||||
ASSERT_OK(db_->VerifyFileChecksums(GetReadOptions())); |
||||
// The files are tiny so there should have just been one read per file.
|
||||
int expected = kNumFiles; |
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); |
||||
} |
||||
|
||||
#endif // !defined(ROCKSDB_LITE)
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) { |
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue