【実務・中級編】 WAITコマンド – Redis

Redis WAITコマンドの真実:分散システムにおける「強整合性」の錯覚と現実

レプリケーション遅延、ネットワークの分断、そしてフェイルオーバー時のデータロスト。分散データストアを扱うエンジニアなら、一度は頭を悩ませたことがあるはずだ。

「Redisは速い。だが、本当に書き込みは安全にレプリカへ伝わっているのか?」

この問いに対するRedisからの回答の一つが `WAIT` コマンドだ。
しかし、コードレビューの場で「とりあえず `WAIT` を挟んでおけば安全だろ」という甘い認識を見かけるたびに、私は冷や汗が出る。`WAIT` は魔法の杖ではない。その内部動作、非同期レプリケーションの本質、そして可用性と一貫性のトレードオフを完璧に理解していなければ、システムを致命的なデッドロックやレイテンシの悪化へと導く諸刃の剣となる。

今回は、テックリードの視点から、`WAIT` コマンドのメカニズムと実務における堅牢な設計パターンを徹底的に紐解いていこう。

—

1. Redisレプリケーションの「不都合な真実」

まず前提を共有しよう。Redisの標準的なレプリケーションは、本質的に非同期(Asynchronous)である。

1. クライアントがプライマリ(Master)に書き込みコマンド(例: `SET`)を送信する。
2. プライマリがメモリ上のデータ構造を更新し、クライアントに `OK` を返す。
3. この瞬間、クライアントには「書き込み完了」が伝わるが、レプリカ(Replica)にはまだデータが届いていない可能性がある。
4. プライマリはバックグラウンドでレプリカへ非同期にreplication streamを送信する。

[Client] —> SET key val —> [Primary (Memory Updated)]
^ |
|———– OK (Fast!) | (Asynchronous Replication)
v
[Replica (Not yet!)]

このアーキテクチャにより、Redisは圧倒的なスループットと低レイテンシを実現している。しかし、もしこの瞬間にプライマリがクラッシュし、レプリカが昇格(Failover)した場合、直前の書き込みは宇宙の彼方へ消え去る(データロスト)。

この「パフォーマンスの代償として耐久性を犠信する」というRedisの設計思想に、強烈なブレーキ(あるいは同期的確認)をかけるのが `WAIT` コマンドだ。

—

2. WAITコマンドのメカニズム

`WAIT` コマンドの構文は非常にシンプルだ。

WAIT numreplicas timeout

  • `numreplicas`: 書き込み完了を待機するレプリカの最小数。
  • `timeout`: タイムアウト時間(ミリ秒単位)。`0` を指定すると無期限に待つ。

内部で何が起きているのか?

`WAIT` は、プライマリが内部で保持している「レプリケーションオフセット(Replication Offset)」を利用して動作する。

1. クライアントが `SET` などを実行すると、プライマリのオフセットが進む。
2. 続いて `WAIT numreplicas timeout` が実行されると、プライマリは「指定された数のレプリカから、現在のオフセットまでデータを受け取った(ACKが返ってきた)という報告」が来るまで、クライアントの接続をブロックする。
3. 条件を満たすか、タイムアウトに達すると、ブロックが解除され、実際に同期に成功したレプリカの数が返される。

例: 書き込み直後に、最低1台のレプリカへの同期を最大1000ms(1秒)待つ
SET user:1001:status “active”
WAIT 1 1000
戻り値: 1 (1台のレプリカが同期完了したことを示す)

—

3. 【実務的アンチパターン】なぜ「全台同期」を狙うとシステムが死ぬのか

設計レビューでよくある誤りが、「安全性を最大化するために、`WAIT` のレプリカ数を現在のレプリカ総数に設定し、タイムアウトを `0`(無期限)にする」というアプローチだ。

⚠️ 危険なアンチパターン
SET critical:data “value”
WAIT 3 0 # レプリカが3台ある環境で、全台の同期を永遠に待つ

なぜこれが地雷なのか?

分散システムの鉄則を思い出してほしい。「ネットワークは信頼できないし、サーバーは死ぬ」。

もし3台あるレプリカのうち1台が、ネットワーク一時切断やGCポーズ、ハードウェア障害で数秒間応答しなくなったとしよう。
`WAIT 3 0` を実行した瞬間、そのコネクションは永久に(あるいは障害が復旧するまで)ブロックされる。結果として、アプリケーションのコネクションプールが枯渇し、Redis全体、ひいてはフロントエンドのWebサーバー群までが雪崩を打ってダウンする。

教訓: `WAIT` を使うときは、必ず適切な `timeout` を設定し、レプリカのダウンがシステム全体の停止につながらないよう「部分的な可用性(Partial Availability)」を許容しなければならない。

—

4. 堅牢な設計パターン:セマンティックな一貫性の担保

では、実務において `WAIT` はどのように活用すべきか。
金融系のトランザクションや、絶対にロストしてはならないユーザーのメタデータ更新など、「結果整合性では許されないが、可用性も完全に捨てたくない」クリティカルパスでの実装パターンを見てみよう。

パターン:アプリケーション層でのフォールバック実装(Node.js / ioredis の例)

async function safeWriteWithWait(redis, key, value, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) { try { // 1. パイプラインで書き込みとWAITをアトミックに送信 // (注: Redis 7未満などではマルチクライアントの競合に注意。通常はスクリプトか連続コマンド) await redis.set(key, value); // 最低1台のレプリカへ、2000ms以内の同期を要求 const syncedReplicas = await redis.wait(1, 2000); if (syncedReplicas >= 1) {
// 成功: プライマリとレプリカの少なくとも1台に書き込みが担保された
return { success: true, synced: syncedReplicas };
}

// タイムアウトまたは指定数に達しなかった場合
console.warn(`[WARN] WAIT timeout on attempt ${attempt}. Synced: ${syncedReplicas}`);

} catch (error) {
console.error(`[ERROR] Redis write failed: ${error.message}`);
}

// バックオフ処理(少し待ってリトライ)
await new Promise(resolve => setTimeout(resolve, 500 attempt));
}

// リトライ上限超過時のフォールバック(例: DBへの書き込みに切り替える、エラーを上位に返すなど)
throw new Error(“Critical: Failed to guarantee replication sync for Redis write.”);
}

この実装のポイントは以下の通りだ。
1. タイムアウトを必ず設けている(2000ms): レプリカ障害時にアプリがハングするのを防ぐ。
2. 戻り値を検証している: `WAIT` はエラーを吐かずに「現在同期できている台数(0かもしれない)」を返すため、アプリケーション側で `syncedReplicas >= 1` かどうかを評価する必要がある。
3. リトライとフォールバック: 一時的なネットワークの揺れであればリトライで救い、完全な障害であれば適切に例外処理を行う。

—

5. パフォーマンスと運用の注意点

`WAIT` を導入するにあたり、シニアエンジニアとして知っておくべきハードウェア・ネットワーク的な制約がある。

  • レイテンシの底上げ:

`WAIT` は「レプリカからのネットワークACK」を待つため、書き込みのレイテンシは [プライマリの処理時間] + [レプリカまでのネットワーク往復遅延 (RTT)] になる。マルチAZ構成などでレプリカが別データセンターにある場合、数ミリ秒〜十数ミリ秒のレイテンシペナルティが確実に上乗せされる。

  • Pub/Subや他のクライアントへの影響:

`WAIT` 自体はブロッキングコマンドだが、特定クライアントのコネクションをブロックするだけであり、Redisのメインスレッド自体が完全にフリーズするわけではない(他のクライアントの処理は進む)。しかし、コネクション滞留によるメモリ圧迫には注意が必要だ。

—

チーフアーキテクトからの最終提言

Redisの `WAIT` コマンドは、「速度のRedis」と「堅牢性のRDB」のジレンマを埋めるための極めて強力なツールだ。

しかし、「なんとなく安全そうだから」という理由で導入してはならない。システム全体の中で「どのデータが強整合性を必要とし、どのデータが結果整合性で十分か」をドメイン駆動的に見極め、適切なタイムアウト設計とフォールバック機構をコードに組み込むこと。

それこそが、障害を恐れず、かつ極限までパフォーマンスを追求するプロフェッショナルエンジニアの仕事である。次回の設計レビューでは、ただ「動くコード」ではなく、「障害時にどう振る舞うか」が担保された `WAIT` の実装を見せてほしい。

コメント

タイトルとURLをコピーしました