benchmarking-transaction-patterns について
CockroachDB の開発者向けに、明示的な複数ステートメントのトランザクションと単一ステートメントの CTE を比較するベンチマークスキルです。競合時の公平な測定、リトライ、レイテンシ、スループットの評価を扱います。
主な機能
- 01独立したベンチマーク用データベースの準備
- 02明示的トランザクションと CTE の測定
- 03公平な比較のためのデータリセット
- 04スループット、リトライ、レイテンシの比較
- 05ベンチマークの整合性確認
活用シーン
- 01競合時のトランザクション設計の比較
- 02リトライとテールレイテンシの評価
- 03本番変更前の測定結果の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cockroachlabs/ cockroachdb-skills --skill benchmarking-transaction-patternsインストール後、Claude Code で/benchmarking-transaction-patternsを入力して読み込まれたか確認します。
詳細
トランザクションパターンのベンチマーク
CockroachDB における同一のトランザクション型ビジネスワークフローの 2 つの構成、すなわち明示的な複数ステートメントトランザクションと単一ステートメントの CTE トランザクションについて、ベンチマーク、説明、比較を行うためのガイドです。競合発生時のパフォーマンス、公平なテスト方法、結果の解釈に焦点を当てています。
設計スキルの補足: 一般的なトランザクション設計の原則については、designing-application-transactions を参照してください。SQL 構文とクエリパターンについては、cockroachdb-sql を参照してください。
基本概念
競合が発生している状況では、トランザクションの構成自体が主要なパフォーマンス調整要素となります。明示的モデル(複数ステートメントの BEGIN/COMMIT)では、ラウンドトリップ中もトランザクションが開いたままになるため、競合が発生する時間帯が広がります。CTE モデル(単一ステートメント)では、同じロジックを 1 つのアトミックなステートメントにまとめることで、トランザクションの所要時間とリトライを削減します。
明示的トランザクションモデル
BEGIN;
SELECT balance FROM accounts WHERE id = $1;
-- Application decides whether transfer is allowed
UPDATE accounts SET balance = balance - $2 WHERE id = $1;
UPDATE accounts SET balance = balance + $2 WHERE id = $3;
INSERT INTO transfers (from_acct, to_acct, amount, created_at)
VALUES ($1, $3, $2, now());
COMMIT;
CTE トランザクションモデル
CockroachDB では、デフォルトで 1 つのステートメント内で同じテーブルを複数回変更することが拒否されるため(sql.multiple_modifications_of_table.enabled)、引き落としと入金は CASE を使った 1 つの UPDATE にまとめます。
WITH funded AS (
SELECT 1 FROM accounts WHERE id = $1 AND balance >= $2
), upd AS (
UPDATE accounts
SET balance = CASE WHEN id = $1 THEN balance - $2 ELSE balance + $2 END
WHERE id IN ($1, $3) AND EXISTS (SELECT 1 FROM funded)
RETURNING id
), ins AS (
INSERT INTO transfers (from_acct, to_acct, amount, created_at)
SELECT $1, $3, $2, now() WHERE (SELECT count(*) FROM upd) = 2
RETURNING id
)
SELECT id FROM ins;
手順
1. ベンチマーク環境を準備する
専用のテストデータベースとスキーマを用意します。ベンチマークのワークロードを他のトラフィックと混在させないでください。
CREATE DATABASE IF NOT EXISTS bankbench;
USE bankbench;
CREATE TABLE accounts (
id INT PRIMARY KEY,
balance DECIMAL(18,2) NOT NULL DEFAULT 0
);
CREATE TABLE transfers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
from_acct INT NOT NULL,
to_acct INT NOT NULL,
amount DECIMAL(18,2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
2. テストデータを投入する
効率よくデータを投入するには、複数行の UPSERT を使用します。単一行の挿入では、セットアップコストが実際とは異なるものになります。
INSERT INTO accounts (id, balance)
SELECT generate_series(1, 10000), 1000.00
ON CONFLICT (id) DO UPDATE SET balance = 1000.00;
3. 明示的トランザクションのベンチマークを実行する
実際の状況に即した同時実行数で実行します。PostgreSQL 互換の pgbench を使用する例を示します。
# Create a pgbench script file: explicit_transfer.sql
# \set from_id random(1, 10000)
# \set to_id random(1, 10000)
# \set amount 10.00
# BEGIN;
# SELECT balance FROM accounts WHERE id = :from_id;
# UPDATE accounts SET balance = balance - :amount WHERE id = :from_id;
# UPDATE accounts SET balance = balance + :amount WHERE id = :to_id;
# INSERT INTO transfers (from_acct, to_acct, amount, created_at) VALUES (:from_id, :to_id, :amount, now());
# COMMIT;
pgbench -n -c 64 -j 8 -T 120 -f explicit_transfer.sql \
"postgresql://root@localhost:26257/bankbench?sslmode=disable"
スループット(tps)、リトライ数、p50/p95/p99 レイテンシ、最大レイテンシ、失敗数を記録します。
4. 公平に比較するため、実行ごとにリセットする
公平なベンチマークを行うため、明示的モデルと CTE モデルの実行の間でアカウント残高をリセットし、テーブルサイズ、インデックスサイズ、アカウントの状態を同程度に保ちます。
UPDATE accounts SET balance = 1000.00;
5. CTE トランザクションのベンチマークを実行する
明示的モデルの実行と同じ同時実行数、実行時間、パラメーターを使用して実行します。
# Create a pgbench script file: cte_transfer.sql containing the CTE query above
pgbench -n -c 64 -j 8 -T 120 -f cte_transfer.sql \
"postgresql://root@localhost:26257/bankbench?sslmode=disable"
6. 結果を比較する
以下の指標を必ず並べて比較します。
| 指標 | 確認する内容 |
|---|---|
| スループット(txn/s) | 高いほど良好です。CTE は通常、競合時にも高いスループットを維持します |
| リトライ総数 | CTE ではほぼゼロまで減ることがよくあります |
| p50 レイテンシ | トランザクション時間の中央値 |
| p95 レイテンシ | 中程度の競合時のテールレイテンシ |
| p99 レイテンシ | 最悪時のテールレイテンシ。明示的モデルでは急増することがよくあります |
| 最大レイテンシ | 外れ値の挙動 |
| 失敗数 | リトライ不可のエラー |
7. ベンチマークの信頼性を検証する
結果を解釈する前に、ベンチマークが問題なく実行されたことを確認します。
-- Confirm expected transfer volume
SELECT COUNT(*) AS total_transfers FROM transfers;
# Check node liveness and start times (no node restarts mid-benchmark)
cockroach node status --certs-dir=<certs-dir> # or --insecure for an insecure cluster
ベンチマークの参考結果
報告されている、競合の激しい状況での 2 つのモデルの比較結果は次のとおりです。
| 指標 | 明示的モデル | CTE | 変化率 |
|---|---|---|---|
| スループット | 591.1 txn/s | 1,035.1 txn/s | +75.1% |
| 実行時間 | 216.5s | 123.7s | -42.9% |
| 平均レイテンシ | 202.2 ms | 111.3 ms | -45.0% |
| リトライ総数 | 2,270,977 | 0 | -100% |
実行規模をさらに大きくした場合も同じ傾向が保たれました。明示的モデルではリトライが増え続け、ときおり失敗も発生した一方、CTE モデルではリトライも失敗もゼロのままでした。
影響の概要
| 項目 | 明示的な複数ステートメント | 単一ステートメントの CTE |
|---|---|---|
| 往復回数 | クライアントとサーバー間で複数回やり取り | 単一のリクエスト |
| トランザクションの存続期間 | 長い | 短い |
| クライアント側の再試行の複雑さ | 高い | 低い |
| アトミックな不変条件の適用 | ステートメント/アプリケーションロジックに分散 | SQL 内に収まる |
| 予想スループット | 競合時は低い | 競合時は高い |
| クライアントから見える再試行 | 発生しやすい | 多くの場合減少 |
判断の指針
明示的なパターンを選ぶ場合
- ビジネスワークフローを単一の SQL ステートメントで適切に表現できない場合
- 最大スループットより、読みやすさや段階的なビジネスロジックが重要な場合
- 競合レベルが低く、再試行の増幅が主要なコスト要因ではない場合
CTE パターンを選ぶ場合
- ワークフローで競合が多い場合
- 操作が本質的にアトミックな場合
- アプリケーションが現在、複数のステートメントにまたがる読み取り・判断・書き込みを実行している場合
- 主な目的が、スループットの向上、再試行の削減、p95/p99 レイテンシーの安定化である場合
公平なベンチマークのルール
- 残高、テーブルサイズ、インデックスサイズを一定に保ち、公平に比較できるよう、実行の間にデータをリセットします
- リセットしない実行は、同条件での比較ベンチマークではなく、デモとして扱います
- 明確に比較できるよう、一度に 1 つの業務単位を処理する場合は
--batch-size=1を使います - 適切なメトリクスを比較します — スループット、再試行回数、p50、p95、p99、最大レイテンシー、失敗数を必ず含めます
- データ投入には複数行の UPSERT を使います — 1 行ずつ投入するとセットアップコストが歪みます
よくある誤解
- 「CTE は常に優れている」 — ワークフローが競合の影響を受けやすく、単一のアトミックなステートメントで表現できる場合に限ります。
- 「SQL Activity に待機が表示されるなら、CTE は失敗した」 — CTE は競合を減らしますが、なくすわけではありません。スループット、テールレイテンシー、再試行の傾向を比較してください。
- 「単一ステートメントなら競合しない」 — CTE は競合が起きる時間帯を狭めますが、競合をなくすわけではありません。
安全上の考慮事項
- ベンチマークは本番環境ではなく、専用のテストクラスターで実行してください
- 公平に比較できるよう、実行の間にデータをリセットしてください
- ベンチマークの実行中はクラスターの健全性を監視してください
- 現実的でありながら、破壊的ではない同時実行レベルを使用してください
- 本番環境を変更する前に、ベンチマーク結果が実際のワークロードに当てはまることを検証してください