Enable large txn optimization by transaction write batch size (#13634)

Summary:
Larger key/values can cause memtable write to take longer time. Add new option `TransactionOptions::large_txn_commit_optimize_byte_threshold` that enables the optimization by transaction write batch size.

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

Test Plan:
- new unit test
- added option to stress test and ran stress test for some time: `python3 ./tools/db_crashtest.py --txn blackbox  --txn_write_policy=0 --commit_bypass_memtable_one_in=50 --test_batches_snapshots=0`

Reviewed By: jowlyzhang

Differential Revision: D75248126

Pulled By: cbi42

fbshipit-source-id: 9522db93457729ba60e4176f7d47f7c2c7778567
This commit is contained in:
Changyu Bi
2025-05-22 17:29:23 -07:00
committed by Facebook GitHub Bot
parent 1d94aeea44
commit a00391c729
6 changed files with 155 additions and 4 deletions
+7 -1
View File
@@ -849,9 +849,15 @@ Status StressTest::NewTxn(WriteOptions& write_opts, ThreadState* thread,
assert(FLAGS_user_timestamp_size == 0);
if (thread->rand.OneIn(2)) {
txn_options.commit_bypass_memtable = true;
} else {
}
if (thread->rand.OneIn(2)) {
txn_options.large_txn_commit_optimize_threshold = 1;
}
if (thread->rand.OneIn(2) ||
(!txn_options.commit_bypass_memtable &&
txn_options.large_txn_commit_optimize_threshold != 1)) {
txn_options.large_txn_commit_optimize_byte_threshold = 1;
}
if (commit_bypass_memtable) {
*commit_bypass_memtable = txn_options.commit_bypass_memtable;
}
@@ -366,6 +366,22 @@ struct TransactionOptions {
// DeleteRange, SingleDelete.
bool write_batch_track_timestamp_size = false;
// The following three options enable optimizations for large transaction
// commit to bypass memtable write.
// - If any transaction's commit should bybass memtable write,
// set commit_bypass_memtable to true.
// - If only bypass memtable write for transactions with >= n operations,
// set commit_bypass_memtable to false,
// large_txn_commit_optimize_threshold to n, and
// large_txn_commit_optimize_byte_threshold to max.
// Similarly for only optimize when a transaction's write batch size is >= n.
// - If bypass memtable write for transactions with >= n operations or >= x
// bytes,
// set commit_bypass_memtable to false,
// large_txn_commit_optimize_threshold to n, and
// large_txn_commit_optimize_byte_threshold to x.
//
//
// EXPERIMENTAL, SUBJECT TO CHANGE
// Only supports write-committed policy. If set to true, the transaction will
// skip memtable write and ingest into the DB directly during Commit(). This
@@ -396,6 +412,13 @@ struct TransactionOptions {
// comment for `commit_bypass_memtable` for more optimization detail.
uint32_t large_txn_commit_optimize_threshold =
std::numeric_limits<uint32_t>::max();
// EXPERIMENTAL, SUBJECT TO CHANGE
// When the size of a transaction's write batch is at least this threshold,
// we will enable optimizations for commiting a large transaction. See
// comment for `commit_bypass_memtable` for more optimization detail.
uint64_t large_txn_commit_optimize_byte_threshold =
std::numeric_limits<uint64_t>::max();
};
// The per-write optimizations that do not involve transactions. TransactionDB
@@ -0,0 +1 @@
* Add new experimental `TransactionOptions::large_txn_commit_optimize_byte_threshold` to enable optimizations for large transaction commit by transaction batch data size.
@@ -115,6 +115,9 @@ void PessimisticTransaction::Initialize(const TransactionOptions& txn_options) {
commit_bypass_memtable_threshold_ =
db_options.txn_commit_bypass_memtable_threshold;
}
commit_bypass_memtable_byte_threshold_ =
txn_options.large_txn_commit_optimize_byte_threshold;
}
PessimisticTransaction::~PessimisticTransaction() {
@@ -857,6 +860,8 @@ Status WriteCommittedTxn::CommitInternal() {
} else {
assert(commit_bypass_memtable_threshold_ ==
std::numeric_limits<uint32_t>::max());
assert(commit_bypass_memtable_byte_threshold_ ==
std::numeric_limits<uint64_t>::max());
assert(commit_timestamp_ != kMaxTxnTimestamp);
char commit_ts_buf[sizeof(kMaxTxnTimestamp)];
EncodeFixed64(commit_ts_buf, commit_timestamp_);
@@ -895,7 +900,10 @@ Status WriteCommittedTxn::CommitInternal() {
uint32_t wb_count = wb->Count();
RecordInHistogram(db_impl_->immutable_db_options_.stats,
NUM_OP_PER_TRANSACTION, wb_count);
bool bypass_memtable = wb_count >= commit_bypass_memtable_threshold_;
bool bypass_memtable =
!needs_ts &&
(wb_count >= commit_bypass_memtable_threshold_ ||
wb->GetDataSize() >= commit_bypass_memtable_byte_threshold_);
if (!bypass_memtable) {
// insert prepared batch into Memtable only skipping WAL.
// Memtable will ignore BeginPrepare/EndPrepare markers
@@ -166,10 +166,11 @@ class PessimisticTransaction : public TransactionBaseImpl {
// Refer to
// TransactionOptions::skip_prepare
bool skip_prepare_ = false;
// Refer to
// TransactionOptions::commit_bypass_memtable
// Refer to TransactionOptions::commit_bypass_memtable
uint32_t commit_bypass_memtable_threshold_ =
std::numeric_limits<uint32_t>::max();
uint64_t commit_bypass_memtable_byte_threshold_ =
std::numeric_limits<uint64_t>::max();
private:
friend class TransactionTest_ValidateSnapshotTest_Test;
+112
View File
@@ -9889,6 +9889,118 @@ TEST_F(TransactionDBTest, SelfDeadlockBug) {
delete txn1;
delete txn2;
}
TEST_P(CommitBypassMemtableTest,
OptimizeLargeTxnCommitWriteBatchSizeThreshold) {
// Tests TransactionOptions::large_txn_commit_optimize_byte_threshold
const uint64_t threshold = 100;
SetUpTransactionDB();
bool commit_bypass_memtable = false;
SyncPoint::GetInstance()->SetCallBack(
"WriteCommittedTxn::CommitInternal:bypass_memtable",
[&](void* arg) { commit_bypass_memtable = *(static_cast<bool*>(arg)); });
SyncPoint::GetInstance()->EnableProcessing();
Random rnd(301);
// Test with transaction option only
WriteOptions wopts;
TransactionOptions txn_opts;
txn_opts.large_txn_commit_optimize_byte_threshold = threshold;
// Above threshold
auto txn = txn_db->BeginTransaction(wopts, txn_opts, nullptr);
ASSERT_OK(txn->SetName("xid1"));
ASSERT_OK(txn->Put("k1", rnd.RandomString(threshold)));
ASSERT_TRUE(txn->GetWriteBatch()->GetDataSize() >= threshold);
ASSERT_OK(txn->Prepare());
ASSERT_OK(txn->Commit());
ASSERT_TRUE(commit_bypass_memtable);
// Below threshold
txn = txn_db->BeginTransaction(wopts, txn_opts, txn);
ASSERT_OK(txn->SetName("xid2"));
ASSERT_OK(txn->Put("k2", "v2"));
ASSERT_TRUE(txn->GetWriteBatch()->GetDataSize() < threshold);
ASSERT_OK(txn->Prepare());
ASSERT_OK(txn->Commit());
ASSERT_FALSE(commit_bypass_memtable);
delete txn;
// With commit_bypass_memtbale
TransactionOptions txn_opts2;
txn_opts2.commit_bypass_memtable = true;
txn_opts2.large_txn_commit_optimize_byte_threshold = threshold;
txn = txn_db->BeginTransaction(wopts, txn_opts2, nullptr);
ASSERT_OK(txn->SetName("xid3"));
ASSERT_OK(txn->Put("k3", "v3"));
ASSERT_TRUE(txn->GetWriteBatch()->GetDataSize() < threshold);
ASSERT_OK(txn->Prepare());
ASSERT_OK(txn->Commit());
ASSERT_TRUE(commit_bypass_memtable);
delete txn;
// With count based threshold `large_txn_commit_optimize_threshold`
TransactionOptions txn_opts3;
txn_opts3.commit_bypass_memtable = false;
txn_opts3.large_txn_commit_optimize_byte_threshold = threshold;
txn_opts3.large_txn_commit_optimize_threshold = 3;
txn = txn_db->BeginTransaction(wopts, txn_opts3, nullptr);
ASSERT_OK(txn->SetName("xid4"));
ASSERT_OK(txn->Put("k3", "v3"));
ASSERT_OK(txn->Delete("k2"));
ASSERT_OK(txn->Delete("k1"));
ASSERT_TRUE(txn->GetWriteBatch()->GetDataSize() < threshold);
ASSERT_OK(txn->Prepare());
ASSERT_OK(txn->Commit());
ASSERT_TRUE(commit_bypass_memtable);
txn = txn_db->BeginTransaction(wopts, txn_opts3, txn);
ASSERT_OK(txn->SetName("xid4"));
ASSERT_OK(txn->Put("k3", "v3"));
ASSERT_OK(txn->Delete("k2"));
ASSERT_OK(txn->Delete("k1"));
ASSERT_TRUE(txn->GetWriteBatch()->GetDataSize() < threshold);
ASSERT_OK(txn->Prepare());
ASSERT_OK(txn->Commit());
ASSERT_TRUE(commit_bypass_memtable);
txn = txn_db->BeginTransaction(wopts, txn_opts3, txn);
ASSERT_OK(txn->SetName("xid5"));
ASSERT_OK(txn->Put("k5", "v5"));
ASSERT_TRUE(txn->GetWriteBatch()->GetDataSize() < threshold);
ASSERT_OK(txn->Prepare());
ASSERT_OK(txn->Commit());
ASSERT_FALSE(commit_bypass_memtable);
delete txn;
// Test with multiple column families
std::vector<std::string> cfs = {"pk", "sk"};
CreateColumnFamilies(cfs, options);
TransactionOptions txn_opts_cf;
txn_opts_cf.large_txn_commit_optimize_byte_threshold = threshold;
// Below threshold
auto txn_cf = txn_db->BeginTransaction(wopts, txn_opts_cf, nullptr);
ASSERT_OK(txn_cf->SetName("xid_cf_above"));
ASSERT_OK(txn_cf->Put(handles_[0], "k1", rnd.RandomString(threshold / 2)));
ASSERT_OK(txn_cf->Put(handles_[1], "k2", rnd.RandomString(threshold / 2)));
ASSERT_TRUE(txn_cf->GetWriteBatch()->GetDataSize() >= threshold);
ASSERT_OK(txn_cf->Prepare());
ASSERT_OK(txn_cf->Commit());
ASSERT_TRUE(commit_bypass_memtable);
txn_cf = txn_db->BeginTransaction(wopts, txn_opts_cf, txn_cf);
ASSERT_OK(txn_cf->SetName("xid_cf_below"));
ASSERT_OK(txn_cf->Put(handles_[0], "k1", rnd.RandomString(10)));
ASSERT_OK(txn_cf->Put(handles_[1], "k2", rnd.RandomString(10)));
ASSERT_TRUE(txn_cf->GetWriteBatch()->GetDataSize() < threshold);
ASSERT_OK(txn_cf->Prepare());
ASSERT_OK(txn_cf->Commit());
ASSERT_FALSE(commit_bypass_memtable);
delete txn_cf;
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {