TiDB クラスタ性能ベンチ (2026-06-27)

3 ノード GMKtec M5 Ultra クラスタの TiDB v8.1.0 にどれくらい捌ける性能があるかを、point-select / OLTP read-write の 2 種類で計測したログ。

クラスタ構成は 2026-06-25_construction_plan.md の通り。本ドキュメントは「実機で何を測ったか」「どこで詰まったか」「Aurora 換算」を残す。

TL;DR

ワークロード

数字

Aurora MySQL 換算

point-select read-only

49k QPS, p95 4.3 ms (threads=128)

db.r6g.2xlarge writer 相当

OLTP read-write mix

1,140 TPS / 22.8k QPS, p95 100–300 ms

db.r6g.2xlarge writer 相当

bulk INSERT…SELECT

約 416k rows/sec (100 万行 / 2.4 秒)

ハードウェアは余裕があるが (バックグラウンド CPU 12%)、49k QPS は計測セットアップ上の上限。真の上限を測るにはクラスタ外の bench 専用機が必要。

物理前提

項目

ノード

GMKtec M5 Ultra ×3 (node1/node2/node3)

CPU

Ryzen 7 7730U 8C/16T @ 4.5GHz

RAM

32 GB DDR4

SSD

1 TB NVMe

ネットワーク

TP-Link SG3210X-M2 2.5GbE switch

OS

Ubuntu Server 24.04 LTS

Kubernetes

v1.31.14 (kubeadm + Cilium)

TiDB

v8.1.0 (TiDB Operator v1.6.0)

ベンチ実行時 (2026-06-27 朝) の TiDB クラスタ初期配置:

PD     node2:1   node3:2   node1:0
TiKV   node2:2   node3:1   node1:0   ← 偏り
TiDB   node2:1   node3:1   node1:0

node1 に control-plane taint が残っていたため Pod が node2/3 にしか乗らず、TiKV-0/-1 が両方 node2 になっていた。

計測スクリプト

すべて scripts/tidb/ 配下。

ファイル

用途

tidb_load.sh

bench.load_test に 100 万行投入 + 簡易 heavy query

tidb_qps.sh

mysqlslap で point-select / UPDATE の並列スイープ

tidb_qps_push.sh

高並列 (256/512) で 10k QPS 突破を狙うスイープ

sysbench_run.sh

sysbench oltp_point_select

sysbench_rw.sh

sysbench oltp_read_write / write_only / update_index

pybench.py

pymysql ベースの軽量 QPS 計測 (Mac から Tailscale LB に撃つ用)

env で TIDB_HOST TIDB_PORT TIDB_USER TIDB_DB THREADS_LIST TIME を上書き可能。

実行環境のバリエーション

ロケーション

接続先

1M 行 INSERT…SELECT 所要

Mac → kubectl port-forward 経由

127.0.0.1:4000

28 秒

Mac → Tailscale LB

tidb.<tailnet>.ts.net:4000

(pybench で 10k QPS 頭打ち)

node1 → NodePort 直

127.0.0.1:31299

2.4 秒

node1 から NodePort 経由が 10 倍以上速い。port-forward は Mac↔Pi の TCP proxy が直列に乗るので latency に支配される。以後の bench は基本 node1 から実行。

point-select QPS スイープ (sysbench oltp_point_select)

TIDB_PORT=31299 TIME=15 THREADS_LIST="64 128 256" \
  bash scripts/tidb/sysbench_run.sh

threads

QPS

p95 latency

64

39,562

3.89 ms

128

49,193

4.33 ms

256

44,952

10.65 ms

threads=256 で QPS が下がって latency が 2 倍超になっているので頭打ち。

この時のノード負荷

ベンチ中 (threads=128) の各ノード load average / sysbench プロセス:

ノード

role

load avg (1m)

コメント

node1

sysbench クライアントのみ

0.98 / 16

sysbench 自体 1.55 cores

node2

TiDB-0 + TiKV-0 + TiKV-1

4.06 / 16

TiKV 2 個共存で偏ってる

node3

TiDB-1 + TiKV-2 + PD

1.64 / 16

3 ノード合計 48 cores のうち実質 5–6 cores (12%) しか使っていない。ハードに余力ありで、49k はソフト側 (TiDB / TiKV のスケジューリング / kube-proxy 経路) の壁。

OLTP read-write mix (sysbench oltp_read_write)

TIDB_PORT=31299 TIME=20 THREADS_LIST="32 64 128 256" \
  bash scripts/tidb/sysbench_rw.sh

threads

TPS

QPS

p95

32

651

13,020

64 ms

64

864

17,286

94 ms

128

1,010

20,203

167 ms

256

1,140

22,801

314 ms

各 transaction = 20 queries (10 SELECT + 4 UPDATE + 4 INSERT + 2 DELETE)。

実用上の良いポイントは threads=64 の 864 TPS / 17k QPS / p95 94ms。それ以上は latency 悪化が激しい。

49k QPS の壁を破ろうとした試み

ハード余力ありでソフト側上限らしいので、SQL 層と KV 層をそれぞれ scale-out して測った。

試み 1: control-plane taint 除去 + TiDB を 2→3 replicas

kubectl taint nodes node1 node-role.kubernetes.io/control-plane:NoSchedule-
kubectl -n tidb-cluster patch tc basic --type=merge -p '{
  "spec":{"tidb":{"replicas":3,
    "topologySpreadConstraints":[
      {"topologyKey":"kubernetes.io/hostname","maxSkew":1}
    ]}}}'

TiDB-2 が node1 に着地 → 3 ノード均等配置完了。再ベンチ:

threads

QPS (TiDB=2)

QPS (TiDB=3)

128

49,193

41,012

256

44,952

41,961

384

42,845

伸びるどころか下がった。SQL 層は壁ではなかった。

試み 2: TiKV を 3→4 replicas + topology spread

kubectl -n tidb-cluster patch tc basic --type=merge -p '{
  "spec":{"tikv":{"replicas":4,
    "topologySpreadConstraints":[
      {"topologyKey":"kubernetes.io/hostname","maxSkew":1}
    ]}}}'

TiKV-3 が node1 に新規追加。PD のリージョン balance を待ってから再ベンチ:

threads

QPS (TiKV=3)

QPS (TiKV=4 settled)

64

39,562

25,665

128

49,193

30,940

256

44,952

34,665

さらに下がった

なぜ TiKV を増やすと下がったか

決定的なのは node1 が「bench 専用」から「bench + TiKV」になったこと。

  • TiKV=3 のとき: node1 は何も乗ってない → sysbench が CPU/IO を独占

  • TiKV=4 のとき: TiKV-3 が node1 に乗った → sysbench と TiKV が CPU/IO を奪い合い

クラスタ自体の理論上限は TiKV=4 の方が高いはずだが、計測クライアントが DB と同居した瞬間に「公正な計測」が不可能になる。これが local-cluster の根本的な計測難。

PD の状態の見方 (pd-ctl)

ベンチ中の region 分布や leader 分布を確認するときに使ったコマンド。

# エイリアス
alias pdctl='kubectl -n tidb-cluster exec -it basic-pd-0 -- /pd-ctl -u http://basic-pd:2379'

pdctl store              # store 一覧 (region_count, region_size, leader_count, leader_size)
pdctl operator show      # 進行中のリージョン移動 (空 = balance 完了)
pdctl scheduler show     # 動いてる scheduler 一覧
pdctl config show        # PD 設定 (max-replicas, schedule-limit 等)
pdctl store limit        # store ごとの ops/sec 上限

region_count だけ見るとミスリードする話

TiKV-3 追加後、region_count だけ見ると激しく偏って見える:

Store

region_count

region_size

leader_count

leader_size

TiKV-0 (node2)

130

771 MB

29

86

TiKV-1 (node2)

121

779 MB

37

141

TiKV-2 (node3)

110

795 MB

30

122

TiKV-3 (node1)

41

769 MB

38

689

region_count41 vs 130 で偏ってるように見えるが、region_size は全部 770–795 MB でほぼ均等。count の差は「空 region と肥大 region の混在」によるもので、実データは均衡している。

代わりに leader_size が TiKV-3 だけ 689 と突出している。これは新規追加した sbtest テーブル (大きいリージョン少数) のリーダーが TiKV-3 に偏ったため。ベンチ対象は sbtest なので、リクエストの大半が node1 に集中 → node1 の CPU/IO が sysbench と取り合い → QPS 低下。

つまり「balance してない」のではなく「balance はしてるが leader 偏在が起きてる」。

ブラウザで見るなら Grafana の PD Statistics - balanceTiDB Dashboard の Store Topology が早い。

Aurora MySQL 換算

公開ベンチ (sysbench OLTP cached) との対比:

Aurora インスタンス

想定 point-select QPS

本クラスタ比

db.r6g.large (2 vCPU / 16GB)

5–10k

圧倒的に上

db.r6g.xlarge (4 vCPU / 32GB)

15–25k

db.r6g.2xlarge (8 vCPU / 64GB)

40–60k

ほぼ同等

db.r6g.4xlarge (16 vCPU / 128GB)

80–120k

TiDB pod を増やしてかつ別マシンから測ればここまで届きそう

OLTP read-write の方:

Aurora インスタンス

想定 TPS

本クラスタ比

db.r6g.large

200–300

db.r6g.xlarge

500–800

db.r6g.2xlarge

1,000–1,500

ほぼ同等

db.r6g.4xlarge

2,000–3,000

届かず

結論: read-only / OLTP どちらでも db.r6g.2xlarge writer 1 台相当。Aurora db.r6g.2xlarge は月 $700–800、本クラスタ (GMKtec M5 Ultra ×3 ≒ 30 万円) は約 1 年で回収できる勘定。ただし Aurora は HA・自動バックアップ・ストレージ自動拡張全部込みで、手元クラスタはそれを自分で面倒見る前提。

やってないこと / 次の宿題

  • クラスタ外の bench 専用機からの計測。今回は (Mac via Tailscale = 10k 頭打ち) と (node1 colocated = 49k) しか取れていない。同 LAN にもう 1 台 (別 Mini PC or Pi) 置いて測ると、真の上限が見える可能性。

  • TiKV / PD の rebalance を PV pin の壁を越えて実行する手順。今回は新規 TiKV を追加するだけで終わったが、本来の rebalance は scale-out → store evict → scale-in の 3 段階を踏む。

  • TiKV scale-out 時の store limit チューニング。デフォルト 15 ops/sec は遅すぎる気がする。

  • PD config (region-schedule-limit, leader-schedule-limit) の調整で leader 偏りをどこまで早く解消できるか。

  • Mac の Homebrew sysbench が TLS/SSL を強制してくる問題の回避。Docker 経由 or pingcap/go-tpc に切り替えるのが楽そう。

適用したクラスタ設定変更 (本検証由来)

2026-06-25_construction_plan.md 側に反映済みの変更:

  1. Phase 3 node1 手順に control-plane taint 除去ステップを追加。これがないと TiKV/TiDB が node1 に乗らず必ず偏る。

  2. TidbCluster CR で全コンポーネントに topologySpreadConstraints を追加。TiDB Operator v1.6 では whenUnsatisfiable フィールドは受け付けず、maxSkew + topologyKey のみ指定する (省略すると DoNotSchedule 扱い)。

  3. TiDB を 2 → 3 replicas に変更 (各ノード 1 個)。

  4. PD / TiKV は replicas=3 のまま topology spread を入れただけだと既存 PV ノード固定で動けない点を注意書きに追加。新規構築なら最初から spread 入れて初期スケジュールで均等配置するのが正解、既存環境からの移行はちょっと面倒。

ベンチ結果ファイル

node1 上の /tmp/ 配下に残してある (再現したくなったら参照):

/tmp/sb_peak.out      ← TiKV=3 時の sysbench point-select (49k)
/tmp/sb_rw.out        ← OLTP read-write (1,140 TPS)
/tmp/sb_after.out     ← TiDB=3 時の point-select (41k に下がった)
/tmp/sb_tikv4.out     ← TiKV=4 直後の point-select (rebalance 途中)
/tmp/sb_settled.out   ← TiKV=4 settled 後の point-select (依然下がってる)