クバネティス(Kubernetes) 環境で Redis-Sentinel Java App 構築 ②


Failover テスト

🖧 Redis-Sentinel ポッド(Pod) ダイアグラム: Redis-0 Master

┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ Sentinel-0 │ │ Sentinel-1 │ │ Sentinel-2 │ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ └───────────────────┼───────────────────┘ ▼ ┌───────────────┐ │ Redis-0 │ │ 🟢Master │ └──────┬────────┘ ┌──────────┴──────────┐ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ Redis-1 │ │ Redis-2 │ │ 🟡Replica-1 │ │ 🟡Replica-2 │ └──────────────┘ └──────────────┘

1. Failover(障害対応) テスト開始

SSH ターミナルを 2つ開き、Terminal-1 では App ポッド(Pod) 生成、Redis ポッド(Pod) ダウンなどを実行し、 Terminal-2 で App ログを見ます。

    Java(Spring) App ポッド(Pod) 生成

    サーバ (Terminal-1 [~/kube/app/sentinel]) で実行
    $ kubectl apply -f app-deployment.yaml -> ポッド(Pod) 生成

    💻 App ログ

    サーバ (Terminal-2 [~/kube/app/sentinel]) で実行
    $ kubectl logs -f -l app=spring-redis-sentinel-app
    1秒あたり 10件ずつ入力([SUCCESS]) されることを確認できます。

    💻 Terminal-2

    💥 1. Redis(Redis-0) ポッド(Pod) ダウン

    Terminal-1) $ date; kubectl delete pod redis-0 -> 15:47:39

    💻 Redis-0 LOG

    ① 39秒 Redis ダウン (+0秒)
    ② 41秒 Redis 再起動 (+2秒)
    ③ 54秒 マスター -> レプリカへ切替 (+15秒)

    📄 redis.log

    Redis-0 全ログ ▼
    📄 redis.log

    💻 Redis-1 LOG

    ① 43秒 レプリカ -> マスターへ切替 (+4秒)

    📄 redis.log

    App ログ

    Terminal-2) $ kubectl logs -f -l app=spring-redis-sentinel-app
    ① 39秒 Redis 接続切断 -> 再接続試行 (+0秒)
    ② 44秒 入力失敗 1件発生: keyA-00000147 (+5秒)
    ③ 45秒から入力成功 (+6秒)

    💻 Terminal-2

    App 全ログ ▼
    💻 Terminal-2

    🖧 Redis-Sentinel ポッド(Pod) ダイアグラム: Failover 状態 (Redis-1 Master)

    ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ Sentinel-0 │ │ Sentinel-1 │ │ Sentinel-2 │ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ └────┬──────────────┴───────────────────┘ │ │ ┌───────────────┐ │ │ Redis-0 │ │ │ 🟡Replica-1 │ │ └──────┬────────┘ │ ▲ │ ┌──────────┴──────────┐ ▼ │ ▼ ┌──────────────┐ ┌──────────────┐ │ Redis-1 │ │ Redis-2 │ │ 🟢Master │ │ 🟡Replica-2 │ └──────────────┘ └──────────────┘

    2. Failback(原状復帰) テスト

    1) Redis(Redis-1) ポッド(Pod) 💥ダウン(delete)

    Terminal-1) $ date; kubectl delete pod redis-1 -> 15:48:05

    💻 Redis-1 ログ

    ① 05秒 Redis ダウン (+0秒)
    ② 08秒 Redis 再起動 (+3秒)
    ③ 20秒 マスター -> レプリカへ切替 (+15秒)

    📄 redis.log

    💻 Redis-0 ログ

    ① 09秒 レプリカ -> マスターへ切替 (+4秒)

    📄 redis.log

    💻 App ログ

    ① 06秒 Redis-1 接続切断 -> 再接続試行 (+1秒)
    ② 10秒 入力成功(09秒に Redis-0 がマスターへ切り替わりました) (+5秒)

    💻 Terminal-2

    App 全ログ ▼
    💻 Terminal-2

    🖧 Redis-Sentinel ポッド(Pod) ダイアグラム: 原状復帰状態 (Redis-0 Master)

    ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ Sentinel-0 │ │ Sentinel-1 │ │ Sentinel-2 │ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ └───────────────────┼───────────────────┘ ▼ ┌───────────────┐ │ Redis-0 │ │ 🟢Master │ └──────┬────────┘ ┌──────────┴──────────┐ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ Redis-1 │ │ Redis-2 │ │ 🟡Replica-1 │ │ 🟡Replica-2 │ └──────────────┘ └──────────────┘

    🧑‍💻 テスト検討および核心要約

    今回の障害対応(Failover/Failback) テストで、クバネティス(Kubernetes) 環境の Redis-Sentinel と Java(Spring Boot) アプリケーション間の実際の連動動作を検証しました。

    ⏱️ 障害対応タイムライン (Timeline)

    区分 時刻
    (相対時間)
    Redis-Sentinel 動作 Java (Spring) App 動作
    障害発生 15:47:39
    (+0秒)
    Master(Redis-0) ポッド(Pod) 終了 (delete) 接続切断検知および再接続試行 (Reconnecting)
    Failover 15:47:43
    (+4秒)
    Sentinel が障害検知後 Redis-1 を Master へ昇格 Sentinel から新規 Master IP を受信
    復旧完了 15:47:45
    (+6秒)
    新規 Master(Redis-1) データ書き込み準備完了 [SUCCESS] 正常書き込み再開 (ただし 1件のリクエストは再試行処理)
    旧マスター再起動 15:47:54
    (+15秒)
    再起動した Redis-0 が Redis-1 の Replica へ昇格/連動 影響なし (正常サービス維持)

    💡 実務適用のための核心示唆 (Key Takeaways) ▼
    • 1. Sentinel Failover 切替時間 (約 4~5秒):
      マスターポッド(Pod) がダウンしたあと、Sentinel がダウンを判定(down-after-milliseconds 3000)し、投票(Quorum) を経て新しいマスターを昇格させるまで 約 4秒のダウンタイムが発生します。
    • 2. Spring/Lettuce クライアント再試行(Retry) の重要性:
      今回のテストでデータ損失がほとんどなかった核心理由は、App 側の再試行(Retry 5回 / 5秒) 設定のおかげです。 マスター切替中に発生した 1件の失敗リクエスト(keyA-00000147) も、バックグラウンド再試行で新規マスターへ渡り、無中断に近いサービス連続性を確保しました。
    • 3. ユーザー体験(UX) およびタイムアウト考慮事項:
      クライアント観点では、障害発生時に約 4~5秒の応答遅延(Latency) が発生します。 Web/App フロントエンド API 呼び出し時にユーザー画面が止まらないよう、Front-end Timeout および Circuit Breaker(Resilience4j など) を適切に組み合わせることを推奨します。
    • 4. Automatic Failback(原状復帰) 未サポートおよび正常動作の特性:
      既存 Master(Redis-0) が再び生きても、元の Master 位置へ復帰(Failback) せず、新規 Master(Redis-1) の Replica に入るのが Sentinel の正常な動作です。 (テスト 2回目の Failback は Redis-1 を強制削除して再誘導した結果です。)
    🛡️ Front-end Timeout および Circuit Breaker 説明 ▼

    🛡️ Redis 障害時にシステム麻痺を防ぐ安全装置

    Sentinel の障害対応(Failover) 中に発生する 4~5秒の応答遅延(Latency) は、バックエンドサーバと ユーザー画面へ障害が伝播する連鎖障害(Cascading Failure) を引き起こすことがあります。 これを防ぐため、実務では次の 2つの安全装置を必ず組み合わせて構築します。

    1. Front-end Timeout (フロントエンドタイムアウト)

    ユーザーのブラウザやモバイルアプリがバックエンド API を呼んだあと、「応答が来ないときに無期限に待たず、リクエストを諦める最大時間」です。

    • 問題状況 (Timeout 未設定時): Redis 障害中に 4秒間応答が止まると、焦ったユーザーが '再読み込み' や 'ボタン' を連打します。これによりバックエンド Tomcat/Spring スレッド(Thread) が枯渇し、Web サーバ全体がダウンします。
    • 解決策 (2~3秒 Timeout 適用): 2秒以内に応答がなければ直ちにリクエストを中断し、「一時的なネットワーク遅延です。しばらくしてから再試行してください」 という案内を出してバックエンド暴走を遮断します。
    2. Circuit Breaker (サーキットブレーカー - Resilience4j)

    電気配電盤の '遮断器' のようにバックエンド(Spring) アプリケーション内部に置き、Redis 障害検知時に 遮断器を開いて(Open) Redis への接続試行を直ちに中断するライブラリです。

    [正常状態] 🟢 CLOSED ──(エラー/遅延急増)──► 🔴 OPEN [遮断状態: 直ちに Fallback 実行] ▲ │ │ │ (設定した待機時間後) └────────────── (成功検証) ─────────── 🟡 HALF-OPEN [少数リクエストテスト]
    • 🟢 CLOSED (正常): すべてのキャッシュ/データリクエストを Redis へ正常に渡します。
    • 🔴 OPEN (遮断): Redis 遅延/エラー率が指定値を超えると遮断器を開き、 Redis 呼び出しをそもそも試みず、あらかじめ準備した代替ロジック(Fallback: 例: Main RDB 直接照会) を 直ちに実行します。
    • 🟡 HALF-OPEN (検証): Redis が復旧したか確認するため少数のリクエストだけ送り、 正常動作が確認できれば再び 🟢 CLOSED 状態へ戻します。

    🔄 障害発生時の 2つの技術の協調プロセス

    • 0秒 (Redis Master ダウン): Sentinel が Failover を開始し、応答遅延が発生
    • 1~2秒 (Circuit Breaker 作動): Spring App の Resilience4j がエラー/遅延を検知し、遮断器を 🔴 OPEN へ切替 ➔ Redis の代わりに RDB を照会するかデフォルト値を返す(Fallback)
    • 2秒 (Front-end Timeout 作動): フロントエンドがリクエストを安全に切り、無限ローディングおよびユーザー連打による WAS 暴走を防止
    • 4~5秒 (Failover 完了 & 自動復旧): Sentinel が新規 Master 昇格を完了すると、Resilience4j がそれを検知して 🟢 CLOSED に戻し、キャッシュサービスを自動正常化


    << K8s Redis-Sentinel App ① K8s Sidecarパターン 準備 ① >>

    Email 返事がかかってなれば、メールでお知らせします。