SLOWLOGコマンドを使用したRedisの遅いクエリの診断と解決

Redis SLOWLOGを使用して遅いコマンドを見つけ、しきい値を調整し、クライアントを検査し、高コストなアクセスパターンを置き換えます。

SLOWLOGコマンドを使用したRedisの遅いクエリの診断と解決

Redisは非常に高速なインメモリデータストアであり、キャッシング、リアルタイム分析、セッション管理、メッセージブローカーとして広く使用されています。そのパフォーマンスは、その上に構築されたアプリケーションの応答性にとってしばしば重要です。しかし、Redisの速度があっても、最適化されていないコマンドや予期しない負荷が遅いクエリを引き起こし、アプリケーション全体のパフォーマンスを低下させるボトルネックを生み出す可能性があります。

根本原因の特定は、Redis SLOWLOGから始まります。これは、サーバー側の実行時間がしきい値を超えたコマンドを記録し、KEYS、広範囲のLRANGE、大きなHGETALL、または遅いLuaスクリプトなどの高コストなコマンドを見つけるのに役立ちます。

Redis SLOWLOG機能の理解

SLOWLOGは、指定された実行時間を超えるクエリをログに記録するシステムです。これは基本的に、設定されたしきい値よりも長くかかったコマンドを記録するインメモリログです。従来のファイルベースのログとは異なり、SLOWLOGはRedisのメモリに直接保存されるため、ディスクI/Oのオーバーヘッドなしに高速にアクセスおよび管理できます。

各エントリには、一意のID、Unixタイムスタンプ、実行時間(マイクロ秒)、コマンド引数、および最新のRedisバージョンでは、利用可能な場合にクライアントアドレスとクライアント名が含まれます。SLOWLOGはRedis内部でのコマンド実行を測定します。ネットワークレイテンシ、クライアント側の待機時間、またはRedisがコマンドの実行を開始する前にキューに入れられた時間は含まれません。

SLOWLOGの仕組み:設定パラメータ

SLOWLOGを効果的に使用する前に、その2つの主要なパラメータを理解して設定することが重要です。これらのパラメータは、何がログに記録されるか、および保持されるエントリ数を制御します。

slowlog-log-slower-than

このパラメータは、コマンドがログに記録されるための実行時間のしきい値(マイクロ秒)を定義します。この指定された値よりも長くかかるコマンドのみがSLOWLOGに記録されます。この値を低く設定しすぎると、多くのコマンドがログに記録され、メモリを大量に消費し、分析が困難になる可能性があります。高く設定しすぎると、本当に遅いクエリを見逃す可能性があります。

  • デフォルト値: 10000(10ミリ秒)
  • 推奨: デフォルトから始めて、アプリケーションのパフォーマンス要件に基づいて調整します。高性能システムの場合は、1000マイクロ秒(1ミリ秒)または100マイクロ秒に下げることもできます。
  • 特別な値: 0に設定すると、すべてのコマンドがログに記録されます。負の値に設定すると、SLOWLOGが完全に無効になります。

このパラメータの現在の値を表示できます:

redis-cli config get slowlog-log-slower-than

新しい値を設定するには(例:5000マイクロ秒、つまり5ミリ秒):

redis-cli config set slowlog-log-slower-than 5000

この変更を永続的にするには、redis.confファイルを更新するか、Redisのバージョンと設定でサポートされている場合はCONFIG REWRITEを使用する必要があります。

slowlog-max-len

このパラメータは、RedisがSLOWLOGに保持するエントリの最大数を指定します。ログが最大長に達すると、新しいエントリによって最も古いエントリが自動的に削除されます(FIFO - 先入れ先出し)。

  • デフォルト値: 128エントリ
  • 推奨: デフォルトは、ビジーな本番システムには小さすぎることがよくあります。メモリへの影響を考慮して、十分な履歴を取得して徹底的な分析を行うために、1024または4096に増やすことを検討してください。

現在の値を表示できます:

redis-cli config get slowlog-max-len

新しい値を設定するには(例:1024エントリ):

redis-cli config set slowlog-max-len 1024

繰り返しますが、この変更をredis.confファイルに永続化することを忘れないでください。

SLOWLOGエントリの取得と分析

SLOWLOGが設定されたら、一連のコマンドを使用して操作できます。

SLOWLOG GET

このコマンドは、SLOWLOGからエントリを取得するために使用されます。オプションでcountを指定して、最新のエントリを特定の数だけ取得できます。

  • SLOWLOG GET: ログ内のすべてのエントリを取得します。
  • SLOWLOG GET <count>: 最新の<count>エントリを取得します。

例:

# 最新の10件のスローログエントリを取得
redis-cli slowlog get 10

出力例(簡略化):

1) 1) (integer) 12345        # ログエントリの一意のID
   2) (integer) 1678886400   # Unixタイムスタンプ(例:2023年3月15日 12:00:00 UTC)
   3) (integer) 25000        # 実行時間(マイクロ秒)(25ミリ秒)
   4) 1) "LRANGE"           # コマンド
      2) "mybiglist"       # 引数1
      3) "0"               # 引数2
      4) "-1"              # 引数3
   5) "127.0.0.1:54321"  # クライアントIPとポート
   6) "client-name-app" # クライアント名(設定されている場合)
...

SLOWLOG LEN

このコマンドは、SLOWLOG内の現在のエントリ数を返します。

redis-cli slowlog len

出力:

(integer) 5

SLOWLOG RESET

このコマンドは、SLOWLOGからすべてのエントリをクリアします。これは、既存のエントリを分析した後、新しいパフォーマンスデータをキャプチャするために新しいログを開始したい場合に便利です。

redis-cli slowlog reset

出力:

OK

SLOWLOG出力の解釈

各エントリは重要な情報を提供します:

  1. 一意のID: シーケンシャルな識別子。特定のイベントを追跡するのに役立ちます。
  2. タイムスタンプ: コマンドが実行された時刻。遅いクエリをアプリケーションのデプロイ変更や特定の負荷期間と関連付けるのに役立ちます。
  3. 実行時間(マイクロ秒): 最も重要なメトリック。これは、コマンドの完了にかかった正確な時間を示します。高い値は潜在的なボトルネックを示します。
  4. コマンドと引数: 正確なRedisコマンドとそのパラメータ。これは、どの操作が遅かったかを理解するために重要です(例:KEYS *、非常に大きなリストに対するLRANGE 0 -1LIMITなしのSORT)。
  5. クライアントアドレス: コマンドを発行したクライアントのIPアドレスとポート。ソースアプリケーションまたはサービスを追跡するのに役立ちます。
  6. クライアント名: アプリケーションがCLIENT SETNAMEを設定している場合(可観測性を高めるために強く推奨)、これにより追加のコンテキストが提供され、アプリケーションのどの部分が遅いクエリを実行したかが示されます。

実践例:遅いコマンドの特定

遅いコマンドをシミュレートし、SLOWLOGがそれをどのようにキャプチャするかを見てみましょう。

まず、デモンストレーションのためにslowlog-log-slower-thanを低い値(例:1000マイクロ秒(1ミリ秒))に設定します:

redis-cli config set slowlog-log-slower-than 1000

次に、大規模なデータセットに適用すると遅くなる可能性があることが知られている操作を実行します。例えば、KEYS *や多くの要素を持つリストに対するLRANGEなどです。

大きなリストを作成しましょう:

for i in {1..100000}; do redis-cli LPUSH mybiglist $i; done

次に、この大きなリストからすべての要素を取得するLRANGEコマンドを実行します:

redis-cli LRANGE mybiglist 0 -1

このコマンドは、おそらく1ミリ秒以上かかります。

最後に、SLOWLOGを確認します:

redis-cli slowlog get 1

次のような出力が表示されるはずです(値は異なります):

1) 1) (integer) 12346
   2) (integer) 1678886450
   3) (integer) 15432 # これが遅い実行時間(マイクロ秒)
   4) 1) "LRANGE"
      2) "mybiglist"
      3) "0"
      4) "-1"
   5) "127.0.0.1:54322"
   6) ""

出力は、LRANGE mybiglist 0 -1コマンド、その実行時間(15432マイクロ秒または15.432ミリ秒)、および発生時刻を明確に示しています。これにより、大きなリスト全体をフェッチすることがかなりの時間を消費していることがすぐにわかります。

遅いクエリを解決するための戦略

SLOWLOGを使用して遅いクエリを特定したら、次のステップはそれらを最適化することです。以下に一般的な戦略を示します:

  1. データ構造とアクセスパターンの最適化:

    • 大規模なデータセットでのO(N)コマンドを避ける: LRANGE 0 -1(すべての要素を取得)、SMEMBERS(すべてのセットメンバーを取得)、HGETALL(すべてのハッシュフィールド/値を取得)、SORTLIMITなし)などのコマンドは遅くなる可能性があります。大きなコレクションを処理する必要がある場合は、一度にすべてをフェッチするのではなく、SCANSSCANHSCAN、またはZSCANを使用して反復処理することを検討してください。
    • 適切なデータ構造を使用する: たとえば、オブジェクトの属性を頻繁に取得する必要がある場合は、各属性に個別のキーを保存する代わりにハッシュを使用します。
    • 結果を制限する: リストまたはソート済みセットの場合は、構造全体をフェッチする代わりに、適切な制限を指定してLRANGE <start> <end>またはZRANGE <start> <end>を使用します。
  2. パイプライン処理: コマンドを1つずつ送信する代わりに、パイプライン処理を使用して複数のコマンドを1つのリクエストにバッチ処理します。これにより、ネットワークのラウンドトリップ時間(RTT)のオーバーヘッドが削減され、個々のコマンドが高速であってもアプリケーションを大幅に高速化できます。

    # パイプライン処理なし(複数のRTTのため遅い)
    r.set('key1', 'value1')
    r.set('key2', 'value2')
    
    # パイプライン処理あり(1回のRTTで高速)
    pipe = r.pipeline()
    pipe.set('key1', 'value1')
    pipe.set('key2', 'value2')
    pipe.execute()
    
  3. Luaスクリプト(EVAL): アトミックに実行する必要がある、またはRTTを最小限に抑える必要がある複数のRedisコマンドを含む複雑な操作の場合は、Luaスクリプトの使用を検討してください。スクリプトはRedisサーバー上で直接実行されるため、ネットワークレイテンシが削減され、アトミック性が保証されます。ただし、長時間実行されるLuaスクリプトはRedisをブロックする可能性があるため、注意深く最適化する必要があります。

  4. 本番環境でのKEYSを避ける: KEYSコマンドはO(N)(Nはデータベース内のキーの数)であり、特に大規模なデータベースではRedisサーバーを長時間ブロックする可能性があります。本番環境では、キーを反復処理するためにSCANを使用してください。SCANは、一時停止および再開可能なイテレータのような機能を提供し、長時間のブロッキング操作を回避します。

    # 本番環境では悪い
    redis-cli KEYS *
    
    # 本番環境での反復処理には良い
    redis-cli SCAN 0 MATCH user:* COUNT 100
    
  5. コネクションプーリング: アプリケーションがRedisへの接続を効率的に管理するために、適切なコネクションプーリングを使用していることを確認してください。すべてのコマンドで接続を開いたり閉じたりすると、リソースを大量に消費する可能性があります。

  6. シャーディングとクラスタリング: データセットまたはワークロードが単一のRedisインスタンスで処理できる容量を超えた場合は、データを複数のRedisインスタンスにシャーディングするか、Redis Clusterを採用することを検討してください。これにより、負荷とデータが分散され、単一のインスタンスがボトルネックになるのを防ぎます。

  7. 読み取りレプリカ: 読み取り負荷の高いワークロードの場合は、読み取りクエリをRedisの読み取りレプリカにオフロードします。これにより、読み取りスループットが拡張され、プライマリインスタンスの負荷が軽減され、書き込みに集中できるようになります。

SLOWLOGを使用するためのベストプラクティス

  • 定期的な監視: 設定したらそのままにしないでください。特にデプロイ後やピーク負荷時には、定期的にSLOWLOGエントリを確認してください。
  • 適切なしきい値: アプリケーションの許容可能なレイテンシに基づいてslowlog-log-slower-thanを調整してください。あるアプリケーションにとって遅いことが、別のアプリケーションにとっては正常である場合があります。
  • 十分なログ長: 意味のある履歴を保持するのに十分な大きさにslowlog-max-lenを設定しますが、過剰なメモリを消費するほど大きくしないでください。
  • 定期的にクリアする: エントリを分析した後はSLOWLOG RESETを使用して新しいデータを取得するか、SLOWLOGを監視システムと統合している場合はこのプロセスを自動化することを検討してください。
  • クライアントの命名: アプリケーションコードでCLIENT SETNAME <name>を使用してください。これにより、SLOWLOGエントリに貴重なコンテキストが追加され、遅いコマンドをアプリケーションの特定の部分にトレースしやすくなります。

まとめ

SLOWLOGを使用して高コストなRedisコマンドを特定し、しきい値を上げるだけでなく、アクセスパターンを修正してください。遅いエントリが巨大なキーからの広範な読み取りを示している場合は、結果をページ分割し、段階的にスキャンし、データモデルを変更するか、重い処理をリクエストパスから移動してください。