遅いRedisコマンドのトラブルシューティング:パフォーマンスチェックリスト

SLOWLOG、MONITOR、レイテンシツール、コマンドの複雑さ、安全な修正方法を用いて遅いRedisコマンドを見つけるための実用的なチェックリスト。

遅いRedisコマンドのトラブルシューティング:パフォーマンスチェックリスト

遅いRedisコマンドは、通常、想定を超えて成長した普通のコマンドから始まります。セットに200メンバーしかいなかったときは、SMEMBERS呼び出しは無害でした。ダッシュボードクエリは50キーをロードするときは問題ありませんでした。Luaスクリプトは、ある顧客が他の誰よりもはるかに大きなデータ形状を作成するまでは高速でした。

有用な質問は、「どのコマンドが遅いか?」だけではありません。「どのデータ形状がこのコマンドを遅くし、なぜアプリケーションはRedisに一度にそれだけ多くの作業を要求しているのか?」です。

Redisのパフォーマンスを理解する

Redisのパフォーマンスは、そのインメモリ特性により、一般的に非常に優れています。ただし、いくつかの要因がコマンドのレイテンシに寄与する可能性があります:

  • コマンドの複雑さ: 特定のコマンドは、他のコマンドよりも本質的にリソースを大量に消費します(例:大規模データセットに対するKEYS vs GET)。
  • データサイズと構造: 大きなリスト、セット、ソート済みセット、または複雑なデータ構造は、それらを操作するコマンドのパフォーマンスに影響を与える可能性があります。
  • ネットワークレイテンシ: 直接的なコマンドの問題ではありませんが、クライアントとサーバー間のネットワークレイテンシが高いと、コマンドが遅く見えることがあります。
  • サーバー負荷: 高いCPU使用率、メモリ不足、またはRedisサーバー上のその他のプロセスは、パフォーマンスを低下させる可能性があります。
  • ブロッキングコマンド: 特定の操作はRedisイベントループをブロックし、後続のすべてのコマンドに影響を与える可能性があります。

SLOWLOGを使用した遅いコマンドの特定

SLOWLOGコマンドは、指定された実行時間を超えるコマンドをログに記録するためのRedisの組み込みメカニズムです。これは、問題のあるコマンドをプロアクティブに特定するための主要なツールです。

SLOWLOGの仕組み

Redisは、設定されたslowlog-log-slower-thanしきい値(マイクロ秒単位)よりも長くかかったコマンドに関する情報を格納する循環バッファを維持します。デフォルトのしきい値は通常10ミリ秒(10000マイクロ秒)です。このバッファがいっぱいになると、古いエントリは破棄されます。

主要なSLOWLOGサブコマンド

  • SLOWLOG GET [count]:スローログから最後のcountエントリを取得します。countが省略された場合は、すべてのエントリを取得します。
  • SLOWLOG LEN:スローログの現在の長さ(エントリ数)を返します。
  • SLOWLOG RESET:スローログエントリをクリアします。このコマンドはログデータを完全に削除するため、注意して使用してください。

SLOWLOGの使用例

いくつかのコマンドに時間がかかりすぎていると仮定しましょう。次のようにスローログを確認できます:

# Redisインスタンスに接続
redis-cli

# 最後の5つの遅いコマンドを取得
127.0.0.1:6379> SLOWLOG GET 5

出力は次のようになります:

1) 1) (integer) 18
   2) (integer) 1678886400
   3) (integer) 15000
   4) 1) "KEYS"
      2) "*"

2) 1) (integer) 17
   2) (integer) 1678886390
   3) (integer) 12000
   4) 1) "SMEMBERS"
      2) "my_large_set"

...

出力の説明:

  1. エントリID:スローログエントリの一意の識別子。
  2. タイムスタンプ:コマンドが実行されたUnixタイムスタンプ。
  3. 実行時間:コマンドの実行にかかった時間(マイクロ秒単位)。
  4. コマンドと引数:コマンド自体とその引数。

上記の例では、KEYS *は15000マイクロ秒(15ms)、SMEMBERS my_large_setは12000マイクロ秒(12ms)かかりました。slowlog-log-slower-thanが10000マイクロ秒に設定されている場合、これらは遅いと見なされます。

slowlog-log-slower-thanの設定

CONFIG SETコマンドを使用して、slowlog-log-slower-thanしきい値を動的に変更できます:

127.0.0.1:6379> CONFIG SET slowlog-log-slower-than 50000  # 50msより遅いコマンドをログに記録

この変更をRedis再起動後も永続的にするには、redis.confファイルを変更してRedisサーバーを再起動するか、CONFIG REWRITEを使用して変更を設定ファイルに保存する必要があります。

MONITORを使用したリアルタイムコマンド監視

SLOWLOGが履歴ビューを提供するのに対し、MONITORはRedisサーバーで実行されているすべてのコマンドのリアルタイムストリームを提供します。これは、パフォーマンス低下の特定の期間中にデバッグする場合や、コマンドトラフィックパターンを理解する場合に非常に役立ちます。

MONITORの仕組み

MONITORを有効にすると、Redisは受信して処理するすべてのコマンドに対してMONITORクライアントに応答を送信します。これにより、特にビジーなRedisインスタンスでは、非常に大量の出力が生成される可能性があります。したがって、一般的にはMONITORは控えめに、アクティブにデバッグしている場合にのみ使用することをお勧めします。

MONITORの使用例

別のredis-cliセッションから、MONITORコマンドを実行します:

# *別の*ターミナルでRedisインスタンスに接続
redis-cli

# 監視を開始
127.0.0.1:6379> MONITOR

これで、別のredis-cliセッションまたはアプリケーションによって実行されたコマンドがMONITOR出力に表示されます。たとえば、別のクライアントでSET mykey myvalueを実行すると、次のように表示されます:

1678887000.123456 [0 127.0.0.1:54321] "SET" "mykey" "myvalue"

デバッグのためのMONITORの使用

  1. 問題を再現する: 速度低下に気付いたら、すぐに専用のredis-cliセッションでMONITORを開始します。
  2. 遅い操作をトリガーする: アプリケーションに、速度低下の原因と思われるアクションを実行させます。
  3. 出力を分析する: MONITORストリーム内のコマンドを観察します。次のものを探します:
    • 表示に時間がかかるコマンド(MONITOR自体は実行時間を表示しませんが、手動でコマンドのタイミングを計ったり、遅延を観察したりすることで推測できます)。
    • 実行されている異常または予期しないコマンド。
    • サーバーに過負荷をかけている可能性のある大量のコマンド。
  4. 監視を停止する: Ctrl+Cを押してMONITORコマンドを終了します。

重要: すべてのコマンドをクライアントに送信するオーバーヘッドによりRedisのパフォーマンスに大きな影響を与える可能性があるため、本番環境でMONITORを長時間実行しないでください。

遅いコマンドの一般的な原因とその修正方法

SLOWLOGMONITORから収集した情報に基づいて、一般的な原因とその解決策を以下に示します:

1. KEYSコマンド

  • 問題: KEYSコマンドは、パターンに一致するキーを見つけるためにキースペース全体を反復処理します。数百万のキーがあるデータベースでは、これに非常に長い時間がかかり、Redisサーバーをブロックして、他のすべてのクライアントに影響を与える可能性があります。
  • 解決策: 大規模な本番キースペースではKEYSを避けてください。増分キー反復が必要な場合はSCANを使用してください。SCANは、呼び出しごとにパターンに一致するキーのサブセットを返すため、サーバーを長時間ブロックする可能性が低くなります。
      # KEYS user:* の代わりに
      redis-cli -h <host> -p <port> SCAN 0 MATCH user:* COUNT 100
    
    カーソルが0に戻るまで、前の呼び出しで返されたカーソルを使用して、SCANを複数回呼び出す必要があります。

2. 複雑なスクリプティング(Luaスクリプト)

  • 問題: EVALまたはEVALSHAを介して実行される長時間実行または非効率的なLuaスクリプトは、サーバーをブロックする可能性があります。Redisはスクリプトをアトミックに実行しますが、単一の長時間スクリプトがイベントループを独占する可能性があります。
  • 解決策: Luaスクリプトを最適化してください。複雑なロジックをより小さく管理しやすいスクリプトに分割します。スクリプトのパフォーマンスを分析します。スクリプト内のループが効率的で、正しく終了することを確認します。スクリプトをベンチマークして、実行時間を把握します。

3. 大規模なデータ構造に対する操作

  • 問題: 数百万のメンバーがいるセットに対するSMEMBERS、非常に長いリストに対するLRANGE、巨大なソート済みセットに対するZRANGEなどのコマンドは遅くなる可能性があります。
  • 解決策: 大規模なデータ構造全体をフェッチすることは避けてください。代わりに、反復コマンドを使用するか、データをチャンクで処理します:
    • セット: SMEMBERSの代わりにSSCANを使用します。
    • リスト: より小さいstartstop値を指定してLRANGEを使用し、データをページ単位で取得します。
    • ソート済みセット: LIMIT付きのZRANGEまたはZSCANを使用します。

4. キー反復を必要とするコマンド(あまり一般的ではないが可能性あり)

  • 問題: あまり一般的ではありませんが、その性質上、キーを暗黙的に反復処理する可能性のあるコマンドは、キースペースが大きい場合に遅くなる可能性があります。
  • 解決策: 特定のコマンドのRedisコマンドリファレンスを確認し、その複雑さを理解してください。特定のコマンドがボトルネックであることが判明した場合は、代替のデータ構造またはアプローチを検討してください。

5. ブロッキングコマンド(最新のRedisではまれ)

  • 問題: 古いバージョンのRedisには、サーバーをブロックする可能性のあるコマンドがいくつかありました。これらのほとんどは対処されるか、置き換えられています。
  • 解決策: 最新バージョンのRedisを使用していることを確認してください。お使いのバージョンに固有の既知のブロッキング操作については、Redisのドキュメントを参照してください。

最初に、Redisが遅いのか、クライアントが待機しているのかを判断する

誰かが「Redisが遅い」と言うとき、いくつかの異なる意味を指している可能性があります。サーバーがコマンドの実行に長い時間を費やしている可能性があります。クライアントがネットワーク上で待機している可能性があります。コネクションプールが枯渇している可能性があります。TLSプロキシが過負荷になっている可能性があります。大きな応答の転送に、コマンドの実行よりも長い時間がかかっている可能性があります。

SLOWLOGは、Redis内部のコマンド実行時間のみを記録します。ネットワーク転送時間、クライアントキューイング時間、またはアプリケーションプールからの接続待機時間は含まれません。そのため、スローログがクリーンでも、ユーザーがレイテンシを想像しているとは限りません。

3つのビューを比較します:

redis-cli --latency -h <host> -p <port>
redis-cli --latency-history -h <host> -p <port>
redis-cli SLOWLOG GET 10

レイテンシが高いがSLOWLOGが空の場合は、ネットワーク、クライアントプール、サーバーCPU飽和、フォークアクティビティ、永続性、または大きな応答を調べてください。SLOWLOGに高価なコマンドが繰り返し表示される場合は、コマンドとデータ構造の設計から始めてください。

アプリケーションでは、クライアント境界でRedis呼び出しの周りにタイミングを追加します。コマンドファミリ、キーパターン、経過時間、およびクライアントがプール接続を待機したかどうかをログに記録します。シークレットや完全なペイロードはログに記録しないでください。少量の構造化されたタイミングで、通常、遅延がRedis内部にあるのか、コマンドがRedisに到達する前にあるのかがわかります。

コマンドの複雑さをにおいテストとして使用する

Redisコマンドは、少量の境界のあるデータに触れる場合に高速です。大規模なキースペースをスキャンしたり、巨大なコレクションを返したり、大きな値に比例した作業を行う場合にリスクが高まります。

ハードウェアを非難する前に、お使いのバージョンのRedisコマンドリファレンスでコマンドの複雑さを確認してください。すべての複雑さラベルを暗記する必要はありませんが、形状が重要です:

  • GET user:123は1つの値のサイズに制限されます。
  • HGET profile:123 emailは1つのハッシュルックアップに制限されます。
  • SMEMBERS followers:celebrityはセット全体を返します。
  • KEYS *はキースペース全体をスキャンします。
  • LRANGE queue 0 -1はリスト全体を返します。
  • ZREMRANGEBYSCOREはソート済みセットの多数のメンバーを削除する可能性があります。

リスクのあるパターンは、通常「すべてをくれ」です。何ヶ月も機能しても、セットが数百のメンバーから数百万に成長したときに失敗する可能性があります。Redisが突然遅くなったのではなく、データが無制限のコマンドが可視化されるポイントを超えたのです。

一般的な遅いパターンのより安全な代替手段

キースペース全体およびコレクション全体のコマンドを、増分パターンに置き換えます。

キー探索には、SCANを使用します:

redis-cli --scan --pattern 'user:*'

セットには、SSCANを使用します:

SSCAN active_users 0 COUNT 500

ハッシュには、HSCANを使用します:

HSCAN user:123:settings 0 COUNT 200

ソート済みセットには、明示的な境界と制限を持つ範囲を優先します:

ZRANGE leaderboard 0 99 WITHSCORES
ZRANGEBYSCORE events 1716600000 1716686400 LIMIT 0 500

リストには、境界のある範囲でページングします:

LRANGE recent_jobs 0 99

SCANは増分的ですが、魔法のように無料の操作ではありません。重複を返す可能性があり、キーが変更されている間は完全に一貫性のあるスナップショットを提供しません。メンテナンス、移行、およびバックグラウンド探索には適しています。通常、正確なリアルタイムリストを必要とするユーザー向けのリクエストパスに適したプリミティブではありません。

大きな応答が実際のコストになる可能性がある

コマンドは高速に実行されても、返すデータが多すぎるとアプリケーションに悪影響を及ぼす可能性があります。巨大なセットに対するSMEMBERS、大きなハッシュに対するHGETALL、または数千の大きな値に対するMGETは、応答のシリアル化とネットワーク経由の送信に時間を費やす可能性があります。このコストは、コマンド実行時間だけでは明確に現れない場合があります。

遅い操作中にネットワーク出力とクライアントメモリを監視します。単一のリクエストが数十または数百メガバイトを返す場合は、アクセスパターンを再設計します。サマリデータを個別に保存します。結果をページングします。ソート済みセットインデックスを使用し、表示可能なスライスのみをフェッチします。アプリケーションが通常1つのフィールドを必要とする場合、大きなドキュメントをRedisに配置しないでください。

実用的な例:ダッシュボードに最新の50のジョブが表示される場合、すべてのジョブIDをリストに保存し、アプリでスライスする前にLRANGE jobs 0 -1を呼び出さないでください。リストを最新順に保存し、ページに必要なものだけを要求します:

LRANGE jobs:recent 0 49

この小さな変更で、驚くほどのレイテンシとメモリプレッシャーを取り除くことができます。

MONITORはメスであり、ダッシュボードではない

MONITORは、クライアントが送信するコマンドを正確に確認する必要がある場合、特にアプリケーションがコードレビューで示唆されているものとは異なることを行っていると疑われる場合に役立ちます。ただし、ビジーなRedisサーバーでは、MONITORはオーバーヘッドを生成し、出力の洪水を引き起こします。

短く制御されたウィンドウでのみ使用します:

redis-cli MONITOR | head -n 200

その後、停止します。本番環境では、可能であれば、アプリケーションログ、Redisコマンド統計、または短いメンテナンスウィンドウからのサンプリングを優先します。

広いビューを得るには、INFO commandstatsの方が安全なことがよくあります:

redis-cli INFO commandstats

これは、コマンドごとの呼び出し回数と累積マイクロ秒を表示します。どのキーが遅かったかはわかりませんが、アプリケーションが予想よりもはるかに多くのHGETALLKEYS、またはEVAL呼び出しを発行していることを明らかにできます。

Luaスクリプトには境界が必要

Luaスクリプトは、Redis内でアトミックに実行されるため強力です。この同じアトミックな動作により、長時間のスクリプトは実行中に他のコマンドをブロックします。遅いスクリプトは、多くの場合、大規模なコレクションに対するループ、無制限のキー探索、または小さなヘルパーからミニアプリケーションに成長したロジックから発生します。

スクリプトを同じ質問でレビューします:

  • これはいくつのキーに触れる可能性がありますか?
  • これはいくつの要素をループできますか?
  • 入力キーに100万のメンバーがある場合はどうなりますか?
  • 作業をより小さなチャンクに分割できますか?
  • スクリプトは大きなペイロードを返しますか?

スクリプトがSLOWLOGに表示される場合は、slowlog-log-slower-thanを上げるだけの誘惑に抵抗してください。ログは、1つのアトミックブロックが他のクライアントに影響を与えるのに十分な時間がかかっていることを示しています。

永続性、フォーク、および症状である「遅いコマンド」

コマンドが遅くなるのは、Redisがバックグラウンド作業でビジーであるためである場合があります。RDBスナップショットとAOF書き換え操作は、CPU、メモリプレッシャー、およびディスクI/Oを増加させる可能性があります。Linuxでは、大きなRedisプロセスをフォークすると、特にメモリのオーバーコミット、ヒュージページ、または低速ストレージが関係する場合に、レイテンシスパイクが発生する可能性もあります。

確認:

redis-cli INFO persistence
redis-cli INFO stats
redis-cli INFO memory
redis-cli LATENCY LATEST

レイテンシスパイクがバックグラウンド保存またはAOF書き換えと一致する場合は、永続性を慎重に調整してください。より高速なストレージ、調整された保存ポリシー、AOF書き換えしきい値、またはメモリ設定が必要になる場合があります。Redisが純粋に使い捨てキャッシュであり、ビジネスがデータ損失を受け入れる場合を除き、ベンチマークを良く見せるためだけに永続性を無効にしないでください。

クライアントの動作は、1つの悪いコマンドがなくてもRedisに過負荷をかける可能性がある

Redisサーバーは、1つの明らかに遅いコマンドと同じくらい、何百万もの小さな非効率的な呼び出しによっても損傷を受ける可能性があります。200回の連続したGET呼び出しを行うページは、個々のGETがすべて高速であっても、遅く感じられます。

アプリケーションが多くの独立したコマンドを必要とし、応答をまとめて受信しても問題ない場合は、パイプラインを使用します:

GET user:1
GET user:2
GET user:3

をパイプラインとして送信すると、コマンドごとのラウンドトリップが回避されます。パイプラインは優れたデータモデリングの代わりにはならず、バッチが大きすぎるとメモリ使用量が増加する可能性があります。控えめなバッチサイズから始めて、測定します。

また、コネクションプールを検査します。アプリケーションログにRedis呼び出しに500ミリ秒かかっていると表示されているが、Redisに遅いコマンドが見られない場合、アプリは空き接続を待機している可能性があります。既存の接続がビジーである理由を確認した後にのみ、プールを増やしてください。プールを大きくすると、症状を隠しながらRedisへのプレッシャーを高める可能性があります。

実用的なインシデントチェックリスト

Redisのレイテンシがユーザーに悪影響を及ぼしている場合は、次の順序で事実を収集します:

date -u
redis-cli PING
redis-cli --latency -i 1
redis-cli SLOWLOG GET 20
redis-cli INFO commandstats
redis-cli INFO clients
redis-cli INFO memory
redis-cli INFO persistence
redis-cli LATENCY LATEST

次に、何が変更されたかを尋ねます:デプロイ、新しいエンドポイント、データ成長イベント、バッチジョブ、移行、ダッシュボードクエリ、新しいキャッシュキーパターン、または永続性の書き換え。Redisの速度低下は、多くの場合、人気になった単一のアクセスパターン、または予想よりもはるかに大きくなったキーに関連しています。

遅いコマンドごとに、キーパターンと所有者を書き留めます。「遅いSMEMBERS」では不十分です。「レコメンデーションサービスが、無制限に成長する可能性のあるセットに対してSMEMBERS product:123:viewersを呼び出している」というのは、実行可能です。

パフォーマンスチューニングチェックリストの概要

  1. SLOWLOGを有効にして監視する:定期的にSLOWLOG GETを確認して、繰り返し発生する遅いコマンドを特定します。必要に応じてslowlog-log-slower-thanを調整します。
  2. MONITORを慎重に使用する:速度低下が疑われる場合のリアルタイムデバッグには使用しますが、その後すぐに無効にします。
  3. 大規模な本番キースペースではKEYSを避ける:キー探索が本当に必要な場合は、増分反復にSCANを使用します。
  4. Luaスクリプトを最適化するEVALおよびEVALSHAスクリプトが効率的で、過度に長く実行されないようにします。
  5. 大規模なデータ構造を反復的に処理する:コレクション全体をフェッチする代わりに、SSCANZSCAN、制限付きのLRANGE、またはSCANを使用します。
  6. コマンド引数を分析する:コマンドに渡される引数が予期しない動作(非常に大きなカウント、複雑なパターンなど)を引き起こしていないことを確認します。
  7. サーバーリソースを監視する:RedisサーバーのCPU、メモリ、およびネットワーク使用量に注意してください。遅いコマンドは、サーバーに負荷がかかっている症状である場合があります。
  8. クライアント側の最適化:アプリケーションがコマンドをあまりにも速く、または非効率的なバッチで送信していないことを確認します。適切な場合は、複数のコマンドにパイプラインを検討します。

最終確認

SLOWLOGを使用してRedis内部で遅いコマンドを見つけ、レイテンシツールを使用してサーバー側のスパイクをキャッチし、アプリケーションタイミングを使用してクライアントの待機をキャッチします。次に、しきい値だけでなく、アクセスパターンを修正します。境界のあるコマンド、より小さな応答、賢明なバッチ処理、および大きなキーの明確な所有権は、1回限りのチューニング変更を追いかけるよりも、Redisのパフォーマンスに大きく貢献します。