今日のゴール
nindaのパフォーマンスを最大限に引き出すためのチューニング手法を学びます。計測から始め、ボトルネックを特定し、段階的に改善するという原則を身につけましょう。
パフォーマンス指標
まず、何を計測すべきかを明確にします。
4つの重要な指標
| 指標 | 意味 | nindaでの例 |
|---|---|---|
| スループット | 単位時間あたりの処理量 | 秒間10万write操作 |
| レイテンシ | 1操作の応答時間 | P99で5ms以内 |
| リソース使用率 | CPU、メモリ、I/O | CPU 70%以下 |
| エラー率 | 失敗した操作の割合 | 0.01%未満 |
レイテンシのパーセンタイル
「平均レイテンシ」だけでは不十分です。ユーザー体験を正確に把握するにはパーセンタイルを見ます。
P50 (中央値): 50%のリクエストがこの時間以内
P95 : 95%のリクエストがこの時間以内
P99 : 99%のリクエストがこの時間以内
P99.9 : 1000回に1回の「最悪のケース」
💡 なぜP99が重要? ユーザーは複数回リクエストを行います。10回中1回でも遅いと、ユーザーは「このシステムは遅い」と感じます。
ボトルネックの特定
最適化の鉄則
❌ 推測で最適化する → 効果がない or 逆効果
✅ 計測してから最適化 → 確実に効果がある
よくあるボトルネック
メモリ最適化
3つのポイント
| ポイント | 問題 | 解決策 |
|---|---|---|
| 適切な型 | 数値を文字列で保存 |
intVal(12345) を使用 |
| アロケーション | 毎回new | オブジェクトプール |
| GC | 停止時間 | 循環参照を避ける |
オブジェクトプールの考え方
# プールから取得 → 使用 → プールに返却
var t = pool.acquire() # 再利用
t.add(?"data")
# 処理...
pool.release(t) # GCを呼ばず再利用
効果: 高頻度操作でGC負荷を大幅に削減
インデックス最適化
nindaは第一フィールドでタプルをインデックス化します。この特性を活かした設計が重要です。
良い設計 vs 悪い設計
# ❌ 悪い例: 全て "data" で始まる
space.write(@[?"data", ?"user", ?1, ?"John"])
space.write(@[?"data", ?"order", ?100])
# → 同じバケットに集中、検索が遅い
# ✅ 良い例: タイプを第一フィールドに
space.write(@[?"user", ?1, ?"John"])
space.write(@[?"order", ?100])
# → タイプごとにバケット分散
# ✅✅ さらに良い: 検索キーを先頭に
space.write(@[?1, ?"user", ?"John"]) # ユーザーIDで検索が多い場合
並行処理の最適化
ロック競合の問題
全スレッドが1つのロックで待機 → スケールしない
解決策: シャーディング
# 第一フィールドのハッシュでシャードを決定
proc getShard(t: Tuple): int =
hash(t[0]) and 0xF # 16シャード
効果: 16シャードなら、ロック競合が1/16に
ネットワーク最適化
分散環境では、ネットワークがボトルネックになりがちです。
3つの手法
| 手法 | 効果 | トレードオフ |
|---|---|---|
| 圧縮 | 帯域削減 50-90% | CPU負荷増 |
| バッファリング | syscall削減 | レイテンシ増 |
| パイプライニング | RTT削減 | 実装複雑 |
パイプライニングの威力
従来方式(3回の往復):
Request1 → Response1 → Request2 → Response2 → Request3 → Response3
合計: 3 RTT
パイプライン(1回の往復):
Request1, Request2, Request3 → Response1, Response2, Response3
合計: 1 RTT
効果: RTT=10msなら、3リクエストで20ms節約
WAL最適化
永続化のボトルネックはディスクI/Oです。
バッチ書き込み
グループコミット
複数のトランザクションを1回のfsyncでまとめてコミットします。
| 設定 | 特性 |
|---|---|
| コミット間隔 1ms | 低レイテンシ、高fsync頻度 |
| コミット間隔 10ms | 高スループット、レイテンシ増 |
| コミット間隔 100ms | 最高スループット、レイテンシ大 |
💡 fsyncは遅い: SSDでも1回数ミリ秒。バッチ化で大幅に改善できます。
パフォーマンスチェックリスト
設計段階
- 第一フィールドに検索キーを配置
- 想定スループット・レイテンシを定義
- キャパシティプランニング実施
実装段階
- 不要なアロケーション削減
- 適切なバッファサイズ設定
- ロック競合の最小化
運用段階
- 定期的なベンチマーク
- メトリクス監視
- WALコンパクション設定
チューニングの原則
4つのルール
- 計測から始める: 推測ではなくデータに基づく
- ボトルネックを特定: 最も遅い部分から改善
- トレードオフを理解: メモリ ↔ CPU、レイテンシ ↔ スループット
- 段階的に改善: 一度に複数変更しない
よくある間違い
| 間違い | なぜ問題か |
|---|---|
| 全てを最適化 | 80%の時間は20%のコードで消費される |
| 計測なしで最適化 | 効果がない変更に時間を浪費 |
| 複数同時変更 | どの変更が効いたか分からない |
本日のまとめ
学んだこと
| トピック | ポイント |
|---|---|
| パフォーマンス指標 | スループット、レイテンシ(P99)、リソース使用率 |
| メモリ最適化 | 適切な型、オブジェクトプール、GC考慮 |
| インデックス最適化 | 第一フィールドの重要性 |
| 並行処理最適化 | シャーディングでロック競合削減 |
| ネットワーク最適化 | 圧縮、バッファリング、パイプライニング |
| WAL最適化 | バッチ書き込み、グループコミット |
最重要ポイント
計測 → 特定 → 最適化 → 検証
推測ではなく、データに基づいて判断しましょう。
演習問題
問題23-1: ベンチマーク設計
write、read、takeの各操作について、スループットとP99レイテンシを計測するベンチマークを設計してください。何を変数として、何を固定すべきでしょうか。
問題23-2: ボトルネック分析
あるシステムで「平均レイテンシ 5ms、P99 レイテンシ 500ms」という結果が出ました。この状況から何が推測できますか?どのような調査を行うべきでしょうか。
問題23-3: シャード設計
1億件のユーザーデータを格納するシステムで、シャード数をいくつにすべきか検討してください。考慮すべき要素を列挙してください。
💡 完全な実装例は GitHubリポジトリ の
src/ninda/を参照してください。
次回は、nindaと他の技術(Redis、Kafka等)との比較を行い、適切な技術選択について学びます。