【実務・中級編】 レイテンシのトラブルシューティング – Redis

Redisレイテンシの魔物を討伐せよ:イベントループを支配する極限のトラブルシューティング

テックリードの私だ。コードレビューや障害対応の現場で、こんな悲鳴を聞いたことはないか?

> 「おい、急にRedisの応答が数秒間フリーズしたぞ! タイムアウトが続発している!」

おっと、慌てるな。教科書通りの「メモリが足りないのでは?」「スワップしてる?」なんて浅い話を今さらするつもりはない。
Redisはシングルスレッド(正確にはメインのイベントループがシングルスレッド)で動作する、極めて潔いアーキテクチャだ。だからこそ、そのたった一つの心臓部であるイベントループを詰まらせた瞬間、システム全体が窒息する。

今回は、プロダクション環境でRedisのレイテンシ悪化に直面した際、私が現場でどのように原因を特定し、秒速でねじ伏せるのか。その「極限の知見」を叩き込もう。

—

1. 敵を知る:なぜRedisは「突然」遅くなるのか?

Redisが遅延するとき、原因の9割は「イベントループのブロッキング」だ。
Redisのメインスレッドは、ネットワークI/Oの多重化(epoll等)、コマンドのパース、そしてメモリ上のデータ構造の操作をたった1つのスレッドでこなしている。

つまり、「1つのコマンドの処理に0.1秒かかると、その間に飛んできた他の数千、数万のコマンドはすべて待たされる」ことになる。これがレイテンシスパイクの正体だ。

主なブロッキング要因は以下の4つに集約される。
1. O(N)コマンドの暴力(巨大なHashやListの全件走査)
2. `KEYS` や不適切な `SMEMBERS` の実戦投入
3. ディスクI/Oとフォークの呪い(RDBスナップショットやAOF rewrite)
4. 巨大なオブジェクトの削除(Lazyfree未設定)

これらを感覚ではなく、科学的に暴くためのツールを見ていこう。

—

2. 診断のファーストステップ:`LATENCY DOCTOR`

障害が発生したら、まず何をするか? `INFO` を見る? いや、もっと直接的な特効薬がある。`LATENCY DOCTOR` だ。

このコマンドは、Redis自身がこれまでに検知したレイテンシの「病歴」を自動診断し、原因のサマリーと処方箋を人間が読める形式で出力してくれる。

127.0.0.1:6379> LATENCY DOCTOR

実行すると、以下のようなレポートが返ってくる(※イメージ)。

=== Latency Doctor Report ===
[142.223ms] 20履歴中の最大スパイク。直近のイベント: command (MGET)
[54.112ms] 危険なコマンドが実行されました。

  • 診断: 0.1秒以上のブロックを引き起こしたコマンドがあります。

直近のワースト要因は `KEYS ` または 10万件以上の要素を持つ `HGETALL` です。
スローログを確認してください。

  • 助言: `SLOWLOG GET` を実行し、どのキーがボトルネックになっているかを特定してください。

もしあなたが本番環境で `KEYS ` を叩いているエンジニアを見かけたら、その場でコードレビューを即座に差し戻し、このレポートを叩きつけてやってほしい。

—

3. スローログの深層解析:隠れた時限爆弾を見つける

`LATENCY DOCTOR` が大まかな傾向を示してくれたら、次は具体的な犯人を特定する。ここで使うのが `SLOWLOG` だ。

Redisのコンフィグで、何マイクロ秒以上かかったコマンドを記録するかを設定しているはずだ(例:`slowlog-log-slower-than 10000`=10ms以上)。

最新の10件のスローログを取得する
127.0.0.1:6379> SLOWLOG GET 10

出力例:

1) (integer) 42
2) (integer) 1680000000 # タイムスタンプ
3) (integer) 12500 # 実行時間(マイクロ秒:この場合は12.5ms)
4) 1) “HGETALL” # 実行されたコマンド
2) “user:session:999999” # 対象のキー
5) “127.0.0.1:54321” # クライアントのIPとポート
6) “”

【設計の鉄則:O(N)の罠を断ち切れ】

ここで `HGETALL` や `LRANGE 0 -1` がヒットしている場合、データ構造の設計が破綻している。
数万件の要素を持つHashに対して `HGETALL` を使うのは、リレーショナルデータベースでインデックスなしの `SELECT ` を全件フルスキャンするのと同じ罪だ。

解決策:

  • ページネーション概念を導入し、`HSCAN` や `SSCAN` を使ってカーソルベースでインクリメンタルに取得する。
  • 1つのキーにデータを詰め込みすぎない(ハッシュのシャーディングや、データ構造の再設計)。

—

4. イベントループを監視する:`LATENCY LATEST` と実時間モニター

「今、何が起きているのか?」をリアルタイムで知るには、`LATENCY LATEST` が有効だ。

127.0.0.1:6379> LATENCY LATEST

出力:

1) 1) “command”
2) (integer) 165 # 最新の遅延イベントの経過時間(ミリ秒)
3) (integer) 182 # 最大遅延時間(ミリ秒)
4) (integer) 1422 # 最後に起きてからの経過秒数

さらに、OSレベルでのイベントループの遅延を測定したい場合は、`redis-cli` の `–latency` モードを使え。

$ redis-cli -h 127.0.0.1 -p 6379 –latency
min: 0, max: 14, avg: 0.12 (1234 samples)

これによって、ネットワークの往復遅延を含まない、Redisサーバー内部の応答遅延のゆらぎをミリ秒単位で常時監視できる。これが跳ね上がっている時は、確実にサーバー内部で何かがイベントループをブロックしている。

—

5. 最も凶悪なブロッキング要因:メモリ解放(`DEL` の呪い)とフォーク

ここまで読んだ優秀なエンジニアなら、こう思うはずだ。
「じゃあ、不要になった巨大なキーを `DEL` すればいいんだな?」

待て、それこそが最大の罠だ。

数GBもある巨大なHashやSetを通常の `DEL` コマンドで削除すると、Redisはそのメモリ領域を解放するためにメインスレッドをブロックする。結果として、「データを消した瞬間に数秒間のフリーズが発生する」という皮肉な障害が起きる。

堅牢な設計パターン:Lazyfreeの活用

これを防ぐためには、非同期でメモリ解放を行う `UNLINK` コマンドを使用する。`UNLINK` はキーを名前空間から即座に切り離し、メモリの実際の解放(deallocation)はバックグラウンドスレッドに丸投げする。

また、Redisのコンフィグ(`redis.conf`)で、明示的に非同期削除を有効化しておくべきだ。

自動的にバックグラウンドでメモリを解放する設定
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
slave-lazy-flush yes

プロダクション環境では、この設定はデフォルトで有効(あるいは要件に合わせてチューニング)にしておくのが、シニアエンジニアとしての最低限のたしなみだ。

—

6. まとめ:トラブルシューティングのチェックリスト

今後、君のシステムでRedisのレイテンシアラートが鳴り響いたとき、以下の手順で冷静に対処してほしい。

1. `LATENCY DOCTOR` で過去の病歴と大まかな原因を即座に把握する。
2. `SLOWLOG GET` で、どのO(N)コマンドがイベントループを殺しているのかを特定する。
3. 該当キーのサイズやデータ構造を確認し、`HSCAN` への置き換えやデータ分割を検討する。
4. もしキーの削除が原因なら、`DEL` を即座に `UNLINK` へ切り替え、`lazyfree` 設定を見直す。

Redisは非常に素直なミドルウェアだ。彼が悲鳴を上げているときは、必ず「無理な使い方をされている」理由がある。そのSOSをロジカルに読み解き、優雅にシステムを救い出すのが、我々テクニカルリードの仕事だ。

さあ、コードを書き換える時間だ。設計レビューの準備はいいか?

コメント

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