Files
Peter Dillinger 741539da9b Fix backward iteration over embedded blob SSTs (#14902)
Summary:
D110140135/https://github.com/facebook/rocksdb/issues/14896 worked around a db_stress failure by disabling test_backward_scan whenever embedded-blob SSTs are ingested. The real limitation was in EmbeddedBlobResolvingIterator: DBIter requires the underlying iterator's value to be pinned for backward iteration (Prev/SeekForPrev), and rebuilt wide-column entity values (with same-file blob columns) were stored in an iterator-owned std::string that was cleared on every reposition, so IsValuePinned() returned false and DBIter returned NotSupported (or tripped the IsValuePinned() assertion in FindValueForCurrentKeyUsingSeek). Whole-value same-file blobs were already pinned and worked.

This change makes the rebuilt wide-column value pinnable like a whole-value blob: it is moved into a heap buffer and pinned into resolved_pinned_value_, whose cleanup ResetState() hands to the PinnedIteratorsManager across repositioning so the value survives backward iteration. The now-redundant resolved_value_ member is removed and value()/IsValuePinned() simplified. The Get/MultiGet resolution path (MaybeResolveEmbeddedValue) is unchanged. The db_stress test_backward_scan workaround is removed now that backward iteration over embedded blobs (including wide-column entities) is supported.

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

Test Plan:
Added DBBlobIndexTest.EmbeddedBlobBackwardIteration: ingests an embedded-blob SST with whole-value blobs plus a mixed wide-column entity (inline default column + embedded same-file blob column), then iterates backward via SeekToLast/Prev and SeekForPrev, verifying values and columns. Verified it fails before the fix (NotSupported at the entity) and passes after.

Full db_blob_index_test and sst_file_reader_test suites pass. make format-auto clean. Direct db_stress run with
ingest_external_file_with_embedded_blobs=1, use_put_entity_one_in=3, test_backward_scan=1, iterpercent>0 ran with no divergence/NotSupported/ assert.

Reviewed By: anand1976

Differential Revision: D110271453

Pulled By: pdillinger

fbshipit-source-id: 501a50d3c97bac9a217b604c43c8748abbb563c9
2026-07-01 10:02:52 -07:00

683 lines
25 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).
//
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.
//
// The test uses an array to compare against values written to the database.
// Keys written to the array are in 1:1 correspondence to the actual values in
// the database according to the formula in the function GenerateValue.
// Space is reserved in the array from 0 to FLAGS_max_key and values are
// randomly written/deleted/read from those positions. During verification we
// compare all the positions in the array. To shorten/elongate the running
// time, you could change the settings: FLAGS_max_key, FLAGS_ops_per_thread,
// (sometimes also FLAGS_threads).
//
// NOTE that if FLAGS_test_batches_snapshots is set, the test will have
// different behavior. See comment of the flag for details.
#ifdef GFLAGS
#include <iostream>
#include <thread>
#include "db_stress_tool/db_stress_common.h"
#include "db_stress_tool/db_stress_driver.h"
#include "db_stress_tool/db_stress_shared_state.h"
#include "port/stack_trace.h"
#include "rocksdb/convenience.h"
#include "utilities/fault_injection_fs.h"
namespace ROCKSDB_NAMESPACE {
namespace {
static std::shared_ptr<ROCKSDB_NAMESPACE::Env> env_guard;
static std::shared_ptr<ROCKSDB_NAMESPACE::Env> legacy_env_wrapper_guard;
// Raw pointers for signal-safe crash callback. Signal handlers can only
// access file-static/global variables; can't capture StressTest instances.
static std::vector<ROCKSDB_NAMESPACE::FaultInjectionTestFS*>
fault_fs_for_crash_flush;
int ValidateNumDbsFlags() {
if (FLAGS_num_dbs < 1) {
fprintf(stderr, "Error: --num_dbs must be >= 1\n");
return 1;
}
if (FLAGS_num_dbs > 1) {
if (FLAGS_clear_column_family_one_in > 0) {
fprintf(stderr,
"Error: --num_dbs > 1 incompatible with "
"--clear_column_family_one_in\n");
return 1;
}
if (FLAGS_test_multi_ops_txns) {
fprintf(stderr,
"Error: --num_dbs > 1 incompatible with "
"--test_multi_ops_txns\n");
return 1;
}
}
return 0;
}
int DestroyAllDbs() {
bool all_ok = true;
const int num_dbs = FLAGS_num_dbs;
auto destroy_one = [&](const std::string& db_path) {
Status s = DbStressDestroyDb(db_path);
if (s.ok()) {
fprintf(stdout, "Successfully destroyed db at %s\n", db_path.c_str());
} else {
fprintf(stderr, "Failed to destroy db at %s: %s\n", db_path.c_str(),
s.ToString().c_str());
all_ok = false;
}
};
if (num_dbs == 1) {
destroy_one(FLAGS_db);
} else {
for (int i = 0; i < num_dbs; i++) {
destroy_one(FLAGS_db + "/db_" + std::to_string(i));
}
DestroyDir(raw_env, FLAGS_db);
}
return all_ok ? 0 : 1;
}
void RegisterCrashCallbacks(
const std::vector<std::unique_ptr<StressTest>>& stress_tests, int num_dbs) {
fault_fs_for_crash_flush.resize(num_dbs, nullptr);
bool any_fault_fs = false;
for (int i = 0; i < num_dbs; i++) {
fault_fs_for_crash_flush[i] =
stress_tests[i]->GetDbFaultInjectionFs().get();
if (fault_fs_for_crash_flush[i]) {
any_fault_fs = true;
}
}
if (any_fault_fs) {
port::RegisterCrashCallback([]() {
for (auto* fs : fault_fs_for_crash_flush) {
if (fs) {
fs->FlushRecentInjectedErrors();
}
}
});
}
}
int ReturnFlagValidationError(const char* message) {
std::cerr << "Error: " << message << '\n';
return 1;
}
} // namespace
KeyGenContext key_gen_ctx;
int db_stress_tool(int argc, char** argv) {
SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
" [OPTIONS]...");
RegisterDbStressBdwFlagValidators();
ParseCommandLineFlags(&argc, &argv, true);
SanitizeDoubleParam(&FLAGS_bloom_bits);
SanitizeDoubleParam(&FLAGS_memtable_prefix_bloom_size_ratio);
SanitizeDoubleParam(&FLAGS_max_bytes_for_level_multiplier);
#ifndef NDEBUG
if (FLAGS_mock_direct_io) {
SetupSyncPointsToMockDirectIO();
}
#endif
compression_type_e = StringToCompressionType(FLAGS_compression_type.c_str());
bottommost_compression_type_e =
StringToCompressionType(FLAGS_bottommost_compression_type.c_str());
checksum_type_e = StringToChecksumType(FLAGS_checksum_type.c_str());
int env_opts = !FLAGS_env_uri.empty() + !FLAGS_fs_uri.empty();
if (env_opts > 1) {
fprintf(stderr, "Error: --env_uri and --fs_uri are mutually exclusive\n");
exit(1);
}
Status s = Env::CreateFromUri(ConfigOptions(), FLAGS_env_uri, FLAGS_fs_uri,
&raw_env, &env_guard);
if (!s.ok()) {
fprintf(stderr, "Error Creating Env URI: %s: %s\n", FLAGS_env_uri.c_str(),
s.ToString().c_str());
exit(1);
}
// Handle --destroy_db_and_exit early
if (FLAGS_destroy_db_and_exit) {
return DestroyAllDbs();
}
// Handle --delete_dir_and_exit early, before other option validation
if (!FLAGS_delete_dir_and_exit.empty()) {
s = DestroyDir(raw_env, FLAGS_delete_dir_and_exit);
if (s.ok()) {
fprintf(stdout, "Successfully deleted directory %s\n",
FLAGS_delete_dir_and_exit.c_str());
return 0;
} else {
fprintf(stderr, "Failed to delete directory %s: %s\n",
FLAGS_delete_dir_and_exit.c_str(), s.ToString().c_str());
return 1;
}
}
{
int rc = ValidateNumDbsFlags();
if (rc != 0) {
return rc;
}
}
if (!FLAGS_verify_cpu_corruption_dir.empty()) {
// The full-keyspace read-back is only well-defined with a single writer,
// and injected I/O faults would taint it -- so require --threads=1 and all
// fault injection off (see the flag's comment).
if (FLAGS_threads != 1) {
fprintf(stderr,
"Error: --verify_cpu_corruption_dir requires --threads=1\n");
exit(1); // NOLINT(concurrency-mt-unsafe)
}
if (FLAGS_read_fault_one_in != 0 || FLAGS_write_fault_one_in != 0 ||
FLAGS_metadata_read_fault_one_in != 0 ||
FLAGS_metadata_write_fault_one_in != 0 ||
FLAGS_open_read_fault_one_in != 0 ||
FLAGS_open_write_fault_one_in != 0 ||
FLAGS_open_metadata_read_fault_one_in != 0 ||
FLAGS_open_metadata_write_fault_one_in != 0 ||
FLAGS_secondary_cache_fault_one_in != 0 || FLAGS_sync_fault_injection) {
fprintf(stderr,
"Error: --verify_cpu_corruption_dir requires all fault injection "
"off (every *_fault_one_in = 0 and sync_fault_injection = "
"false)\n");
exit(1); // NOLINT(concurrency-mt-unsafe)
}
// The read-back compares against the expected-state model, which only the
// default (state-tracked) stress test maintains. The
// batched/cf-consistency/ multi-ops-txns variants set
// IsStateTracked()=false, so every key would look absent -- reject them
// rather than report false losses.
if (FLAGS_test_batches_snapshots || FLAGS_test_cf_consistency ||
FLAGS_test_multi_ops_txns) {
fprintf(stderr,
"Error: --verify_cpu_corruption_dir requires the default "
"state-tracked stress test; it is incompatible with "
"--test_batches_snapshots, --test_cf_consistency, and "
"--test_multi_ops_txns\n");
exit(1); // NOLINT(concurrency-mt-unsafe)
}
}
FLAGS_rep_factory = StringToRepFactory(FLAGS_memtablerep.c_str());
// The number of background threads should be at least as much the
// max number of concurrent compactions.
raw_env->SetBackgroundThreads(FLAGS_max_background_compactions,
ROCKSDB_NAMESPACE::Env::Priority::LOW);
raw_env->SetBackgroundThreads(FLAGS_num_bottom_pri_threads,
ROCKSDB_NAMESPACE::Env::Priority::BOTTOM);
if (FLAGS_prefixpercent > 0 && FLAGS_prefix_size < 0) {
fprintf(stderr,
"Error: prefixpercent is non-zero while prefix_size is "
"not positive!\n");
exit(1);
}
if (FLAGS_test_batches_snapshots && FLAGS_prefix_size <= 0) {
fprintf(stderr,
"Error: please specify prefix_size for "
"test_batches_snapshots test!\n");
exit(1);
}
if (FLAGS_memtable_prefix_bloom_size_ratio > 0.0 && FLAGS_prefix_size < 0 &&
!FLAGS_memtable_whole_key_filtering) {
fprintf(stderr,
"Error: please specify positive prefix_size or enable whole key "
"filtering in order to use memtable_prefix_bloom_size_ratio\n");
exit(1);
}
if ((FLAGS_readpercent + FLAGS_prefixpercent + FLAGS_writepercent +
FLAGS_delpercent + FLAGS_delrangepercent + FLAGS_iterpercent +
FLAGS_customopspercent) != 100) {
fprintf(
stderr,
"Error: "
"Read(-readpercent=%d)+Prefix(-prefixpercent=%d)+Write(-writepercent=%"
"d)+Delete(-delpercent=%d)+DeleteRange(-delrangepercent=%d)"
"+Iterate(-iterpercent=%d)+CustomOps(-customopspercent=%d) percents != "
"100!\n",
FLAGS_readpercent, FLAGS_prefixpercent, FLAGS_writepercent,
FLAGS_delpercent, FLAGS_delrangepercent, FLAGS_iterpercent,
FLAGS_customopspercent);
exit(1);
}
if (FLAGS_disable_wal == 1 && FLAGS_reopen > 0) {
fprintf(stderr, "Error: Db cannot reopen safely with disable_wal set!\n");
exit(1);
}
if ((unsigned)FLAGS_reopen >= FLAGS_ops_per_thread) {
fprintf(stderr,
"Error: #DB-reopens should be < ops_per_thread\n"
"Provided reopens = %d and ops_per_thread = %lu\n",
FLAGS_reopen, (unsigned long)FLAGS_ops_per_thread);
exit(1);
}
if (FLAGS_test_batches_snapshots && FLAGS_delrangepercent > 0) {
fprintf(stderr,
"Error: nonzero delrangepercent unsupported in "
"test_batches_snapshots mode\n");
exit(1);
}
if (FLAGS_active_width > FLAGS_max_key) {
fprintf(stderr, "Error: active_width can be at most max_key\n");
exit(1);
} else if (FLAGS_active_width == 0) {
FLAGS_active_width = FLAGS_max_key;
}
if (FLAGS_value_size_mult * kRandomValueMaxFactor > kValueMaxLen) {
fprintf(stderr, "Error: value_size_mult can be at most %d\n",
kValueMaxLen / kRandomValueMaxFactor);
exit(1);
}
if (FLAGS_use_merge && FLAGS_nooverwritepercent == 100) {
fprintf(
stderr,
"Error: nooverwritepercent must not be 100 when using merge operands");
exit(1);
}
if (FLAGS_enable_blob_direct_write) {
// Blob direct write is intentionally validated as a reduced-scope stress
// feature. We allow the WAL-disabled crash-test profile, including
// wide-column PutEntity/GetEntity coverage, but reject best-efforts
// recovery, parallel memtable/write-queue variants, transactions, remote
// compaction, and APIs/features that depend on active-file snapshotting or
// unsupported blob option transitions.
if (!FLAGS_enable_blob_files) {
return ReturnFlagValidationError(
"enable_blob_direct_write requires enable_blob_files");
}
if (FLAGS_allow_concurrent_memtable_write) {
return ReturnFlagValidationError(
"blob direct write stress requires "
"allow_concurrent_memtable_write=0");
}
if (FLAGS_enable_pipelined_write) {
return ReturnFlagValidationError(
"blob direct write stress does not support "
"enable_pipelined_write");
}
if (FLAGS_unordered_write) {
return ReturnFlagValidationError(
"blob direct write stress does not support unordered_write");
}
if (FLAGS_two_write_queues) {
return ReturnFlagValidationError(
"blob direct write stress does not support two_write_queues");
}
if (FLAGS_use_blob_db) {
return ReturnFlagValidationError(
"blob direct write is only supported with integrated BlobDB");
}
if (FLAGS_use_merge || FLAGS_use_full_merge_v1) {
return ReturnFlagValidationError(
"blob direct write stress does not support merge");
}
if (FLAGS_experimental_mempurge_threshold > 0.0) {
return ReturnFlagValidationError(
"blob direct write stress does not support MemPurge");
}
if (FLAGS_user_timestamp_size > 0) {
return ReturnFlagValidationError(
"blob direct write stress does not support user-defined timestamps");
}
if (FLAGS_allow_setting_blob_options_dynamically ||
FLAGS_enable_blob_garbage_collection) {
return ReturnFlagValidationError(
"blob direct write stress does not support dynamic blob options or "
"blob GC");
}
if (FLAGS_best_efforts_recovery) {
return ReturnFlagValidationError(
"blob direct write stress supports disable_wal-based crash "
"testing, not best-efforts recovery");
}
if (FLAGS_remote_compaction_worker_threads > 0) {
return ReturnFlagValidationError(
"blob direct write stress does not support remote compaction");
}
if (FLAGS_use_txn || FLAGS_txn_write_policy != 0 ||
FLAGS_use_optimistic_txn || FLAGS_test_multi_ops_txns ||
FLAGS_commit_bypass_memtable_one_in > 0) {
return ReturnFlagValidationError(
"blob direct write stress does not support TransactionDB modes");
}
if (FLAGS_test_secondary || FLAGS_backup_one_in > 0 ||
FLAGS_checkpoint_one_in > 0 || FLAGS_get_live_files_apis_one_in > 0 ||
FLAGS_ingest_external_file_one_in > 0) {
return ReturnFlagValidationError(
"blob direct write stress does not support secondary, backup, "
"checkpoint, get_live_files, or ingest_external_file modes");
}
if (FLAGS_ingest_wbwi_one_in > 0) {
return ReturnFlagValidationError(
"blob direct write stress does not support "
"IngestWriteBatchWithIndex");
}
}
if (FLAGS_ingest_external_file_one_in > 0 &&
FLAGS_nooverwritepercent == 100) {
fprintf(
stderr,
"Error: nooverwritepercent must not be 100 when using file ingestion");
exit(1);
}
if (FLAGS_clear_column_family_one_in > 0 && FLAGS_backup_one_in > 0) {
fprintf(stderr,
"Error: clear_column_family_one_in must be 0 when using backup\n");
exit(1);
}
if (FLAGS_test_cf_consistency && FLAGS_disable_wal) {
FLAGS_atomic_flush = true;
}
// Trie UDI uses zero-copy pointers into block data, which is
// incompatible with mmap_read.
if (FLAGS_use_trie_index && FLAGS_mmap_read) {
fprintf(stderr,
"Error: use_trie_index is incompatible with mmap_read. "
"The trie index uses zero-copy pointers into block data "
"which is unsafe with mmap'd reads.\n");
exit(1);
}
// TrieIndexFactory requires plain BytewiseComparator, but timestamps use
// BytewiseComparator.u64ts.
if (FLAGS_use_trie_index && FLAGS_user_timestamp_size > 0) {
fprintf(stderr,
"Error: use_trie_index is incompatible with user-defined "
"timestamps. TrieIndexFactory requires BytewiseComparator "
"but timestamps use BytewiseComparator.u64ts.\n");
exit(1);
}
if (FLAGS_read_only) {
if (FLAGS_writepercent != 0 || FLAGS_delpercent != 0 ||
FLAGS_delrangepercent != 0) {
fprintf(stderr, "Error: updates are not supported in read only mode\n");
exit(1);
} else if (FLAGS_checkpoint_one_in > 0 &&
FLAGS_clear_column_family_one_in > 0) {
fprintf(stdout,
"Warn: checkpoint won't be validated since column families may "
"be dropped.\n");
}
}
// Choose a location for the test database if none given with --db=<path>
if (FLAGS_db.empty()) {
std::string default_db_path;
raw_env->GetTestDirectory(&default_db_path);
default_db_path += "/dbstress";
FLAGS_db = default_db_path;
}
// For num_dbs=1: --db and --expected_values_dir are paths used as-is.
// For num_dbs>1: they are parent directories; C++ creates db_0/, db_1/, ...
// subdirs underneath. DB::Open(create_if_missing) creates each DB dir.
// Python (db_crashtest.py) also creates EV dirs; C++ creates them here as a
// fallback for direct CLI usage.
const int num_dbs = FLAGS_num_dbs;
if (num_dbs > 1) {
s = raw_env->CreateDirIfMissing(FLAGS_db);
if (!s.ok()) {
fprintf(stderr, "Failed to create directory %s: %s\n", FLAGS_db.c_str(),
s.ToString().c_str());
exit(1);
}
}
std::vector<std::string> db_paths;
std::vector<std::string> ev_paths;
for (int i = 0; i < num_dbs; i++) {
std::string suffix = (num_dbs == 1) ? "" : "/db_" + std::to_string(i);
db_paths.push_back(FLAGS_db + suffix);
if (!FLAGS_expected_values_dir.empty()) {
std::string ep = FLAGS_expected_values_dir + suffix;
s = Env::Default()->CreateDirIfMissing(ep);
if (!s.ok()) {
fprintf(stderr, "Failed to create directory %s: %s\n", ep.c_str(),
s.ToString().c_str());
exit(1);
}
ev_paths.push_back(std::move(ep));
}
}
if (ev_paths.empty()) {
ev_paths.resize(num_dbs);
}
// Secondary paths: C++ owns creation entirely.
std::vector<std::string> sec_paths;
if (FLAGS_test_secondary || FLAGS_continuous_verification_interval > 0) {
std::string sec_parent;
if (!FLAGS_secondaries_base.empty()) {
sec_parent = FLAGS_secondaries_base;
} else {
raw_env->GetTestDirectory(&sec_parent);
sec_parent += "/dbstress_secondaries";
}
s = raw_env->CreateDirIfMissing(sec_parent);
if (!s.ok()) {
fprintf(stderr, "Failed to create directory %s: %s\n", sec_parent.c_str(),
s.ToString().c_str());
exit(1);
}
for (int i = 0; i < num_dbs; i++) {
std::string suffix = (num_dbs == 1) ? "" : "/db_" + std::to_string(i);
std::string sec_path = sec_parent + suffix;
s = raw_env->CreateDirIfMissing(sec_path);
if (!s.ok()) {
fprintf(stderr, "Failed to create directory %s: %s\n", sec_path.c_str(),
s.ToString().c_str());
exit(1);
}
sec_paths.push_back(std::move(sec_path));
}
}
if (sec_paths.empty()) {
sec_paths.resize(num_dbs);
}
if (FLAGS_best_efforts_recovery &&
!(FLAGS_skip_verifydb && FLAGS_disable_wal)) {
fprintf(stderr,
"With best-efforts recovery, skip_verifydb and disable_wal "
"should be set to true.\n");
exit(1);
}
if (FLAGS_skip_verifydb) {
if (FLAGS_verify_db_one_in > 0) {
fprintf(stderr,
"Must set -verify_db_one_in=0 if skip_verifydb is true.\n");
exit(1);
}
if (FLAGS_continuous_verification_interval > 0) {
fprintf(stderr,
"Must set -continuous_verification_interval=0 if skip_verifydb "
"is true.\n");
exit(1);
}
}
if ((FLAGS_enable_compaction_filter || FLAGS_inplace_update_support) &&
(FLAGS_acquire_snapshot_one_in > 0 || FLAGS_compact_range_one_in > 0 ||
FLAGS_iterpercent > 0 || FLAGS_prefixpercent > 0 ||
FLAGS_test_batches_snapshots || FLAGS_test_cf_consistency ||
FLAGS_check_multiget_consistency ||
FLAGS_check_multiget_entity_consistency)) {
fprintf(
stderr,
"Error: acquire_snapshot_one_in, compact_range_one_in, iterpercent, "
"prefixpercent, test_batches_snapshots, test_cf_consistency, "
"check_multiget_consistency, check_multiget_entity_consistency must "
"all be 0 when using compaction filter or inplace update support\n");
exit(1);
}
if (FLAGS_test_multi_ops_txns) {
CheckAndSetOptionsForMultiOpsTxnStressTest();
}
if (!FLAGS_use_txn && FLAGS_use_optimistic_txn) {
fprintf(
stderr,
"You cannot set use_optimistic_txn true while use_txn is false. Please "
"set use_txn true if you want to use OptimisticTransactionDB\n");
exit(1);
}
if (FLAGS_create_timestamped_snapshot_one_in > 0) {
if (!FLAGS_use_txn) {
fprintf(stderr, "timestamped snapshot supported only in TransactionDB\n");
exit(1);
} else if (FLAGS_txn_write_policy != 0) {
fprintf(stderr,
"timestamped snapshot supported only in write-committed\n");
exit(1);
}
}
if (FLAGS_preserve_unverified_changes && FLAGS_reopen != 0) {
fprintf(stderr,
"Reopen DB is incompatible with preserving unverified changes\n");
exit(1);
}
if (FLAGS_use_txn && !FLAGS_use_optimistic_txn &&
FLAGS_sync_fault_injection && FLAGS_txn_write_policy != 0) {
fprintf(stderr,
"For TransactionDB, correctness testing with unsync data loss is "
"currently compatible with only write committed policy\n");
exit(1);
}
if (FLAGS_use_put_entity_one_in > 0 &&
(FLAGS_use_full_merge_v1 || FLAGS_test_multi_ops_txns ||
FLAGS_user_timestamp_size > 0)) {
fprintf(stderr,
"Wide columns are incompatible with V1 Merge, the multi-op "
"transaction test, and user-defined timestamps\n");
exit(1);
}
#ifndef NDEBUG
KillPoint* kp = KillPoint::GetInstance();
kp->rocksdb_kill_odds = FLAGS_kill_random_test;
kp->rocksdb_kill_exclude_prefixes = SplitString(FLAGS_kill_exclude_prefixes);
#endif
unsigned int levels = FLAGS_max_key_len;
std::vector<std::string> weights;
uint64_t scale_factor = FLAGS_key_window_scale_factor;
key_gen_ctx.window = scale_factor * 100;
if (!FLAGS_key_len_percent_dist.empty()) {
weights = SplitString(FLAGS_key_len_percent_dist);
if (weights.size() != levels) {
fprintf(stderr,
"Number of weights in key_len_dist should be equal to"
" max_key_len");
exit(1);
}
uint64_t total_weight = 0;
for (std::string& weight : weights) {
uint64_t val = std::stoull(weight);
key_gen_ctx.weights.emplace_back(val * scale_factor);
total_weight += val;
}
if (total_weight != 100) {
fprintf(stderr, "Sum of all weights in key_len_dist should be 100");
exit(1);
}
} else {
uint64_t keys_per_level = key_gen_ctx.window / levels;
for (unsigned int level = 0; level + 1 < levels; ++level) {
key_gen_ctx.weights.emplace_back(keys_per_level);
}
key_gen_ctx.weights.emplace_back(key_gen_ctx.window -
keys_per_level * (levels - 1));
}
// Initialize shared resources (hot-key generator, block cache, WBM,
// rate limiter) once so that all DB instances share them.
InitializeHotKeyGenerator(FLAGS_hot_key_alpha);
block_cache =
StressTest::NewCache(FLAGS_cache_size, FLAGS_cache_numshardbits);
if (FLAGS_use_write_buffer_manager) {
wbm = std::make_shared<WriteBufferManager>(FLAGS_db_write_buffer_size,
block_cache);
}
if (FLAGS_rate_limiter_bytes_per_sec > 0) {
rate_limiter.reset(NewGenericRateLimiter(
FLAGS_rate_limiter_bytes_per_sec, 1000 /* refill_period_us */,
10 /* fairness */,
FLAGS_rate_limit_bg_reads ? RateLimiter::Mode::kReadsOnly
: RateLimiter::Mode::kWritesOnly));
}
// Phase 1: Create StressTest instances (one per DB).
std::vector<std::unique_ptr<StressTest>> stress_tests(num_dbs);
for (int i = 0; i < num_dbs; i++) {
if (FLAGS_test_cf_consistency) {
stress_tests[i].reset(CreateCfConsistencyStressTest(
i, db_paths[i], ev_paths[i], sec_paths[i]));
} else if (FLAGS_test_batches_snapshots) {
stress_tests[i].reset(CreateBatchedOpsStressTest(
i, db_paths[i], ev_paths[i], sec_paths[i]));
} else if (FLAGS_test_multi_ops_txns) {
stress_tests[i].reset(CreateMultiOpsTxnsStressTest(
i, db_paths[i], ev_paths[i], sec_paths[i]));
} else {
stress_tests[i].reset(CreateNonBatchedOpsStressTest(
i, db_paths[i], ev_paths[i], sec_paths[i]));
}
}
RegisterCrashCallbacks(stress_tests, num_dbs);
// Phase 2: Create SharedState for every DB before any worker thread starts.
std::vector<std::unique_ptr<SharedState>> shared_states(num_dbs);
for (int i = 0; i < num_dbs; i++) {
shared_states[i].reset(new SharedState(raw_env, stress_tests[i].get()));
}
// Phase 3: Launch each DB's stress test on its own thread.
std::vector<int> results(num_dbs, 0);
std::vector<std::thread> stress_test_runners;
stress_test_runners.reserve(num_dbs);
for (int i = 0; i < num_dbs; i++) {
stress_test_runners.emplace_back([i, &results, &shared_states]() {
results[i] = RunStressTest(shared_states[i].get()) ? 1 : 0;
});
}
// Phase 4: Wait for all DB threads, collect results, clean up.
bool all_passed = true;
for (int i = 0; i < num_dbs; i++) {
stress_test_runners[i].join();
if (!results[i]) {
all_passed = false;
}
stress_tests[i]->CleanUp();
}
for (auto& fs : fault_fs_for_crash_flush) {
fs = nullptr;
}
return all_passed ? 0 : 1;
}
} // namespace ROCKSDB_NAMESPACE
#endif // GFLAGS