Redisメモリ管理の極意:ピークパフォーマンスを実現する
Redisのメモリ管理テクニックを習得し、ピークパフォーマンスを引き出しましょう。この包括的なガイドでは、Redisのメモリフットプリントの理解、`INFO memory`や`MEMORY USAGE`による監視、データ構造の最適化など、重要な側面を網羅します。アクティブデフラグによる断片化対策、効率的なエビクションポリシー(`maxmemory`、`allkeys-lru`)の設定、レイジーフリーイングを活用したスムーズな運用方法を学びます。これらの実践的な戦略を実装して、Redisのスループットを向上させ、レイテンシを低減し、安定した高性能なキャッシュおよびデータストレージを実現しましょう。
Redisメモリ管理の極意:ピークパフォーマンスを実現する
Redisはデータをメモリに保持するため高速ですが、その設計ゆえにメモリに関する問題がすぐに顕在化します。有効期限のないキャッシュは、書き込みが失敗するまで成長し続けます。巨大なキーがいくつかあると、削除時にレイテンシのスパイクが発生します。バックグラウンド保存では、コピーオンライトにより予想以上のメモリが必要になることがあります。低速なクライアントは、問題の一部となるほど大きな出力バッファを構築する可能性があります。
優れたRedisメモリ管理とは、「単により多くのRAMを購入する」ことだけではありません。何が保存されているか、どのくらいの期間存続すべきか、メモリ制限に達したときに何が起こるか、そしてキーが大きい場合にどの操作がサーバーをブロックする可能性があるかを理解することです。
Redisのメモリ使用量を理解する
Redisはシステムメモリを使用してすべてのデータを保存します。SETコマンドでキーと値のペアを設定すると、Redisはキー文字列と値の両方にメモリを割り当て、内部データ構造のためのオーバーヘッドも追加します。メモリ使用量のさまざまな構成要素を理解することが、効果的な管理への第一歩です。
- データメモリ: 実際のデータ(キー、値、およびキーを値にマッピングするための辞書などの内部データ構造)によって消費されるメモリです。サイズは、キーと値の数とサイズ、および選択するデータ構造(文字列、ハッシュ、リスト、セット、ソート済みセット)によって異なります。
- オーバーヘッドメモリ: Redisは各キーに、ポインタ、メタデータ、有効期限情報、エビクション関連データなどのオーバーヘッドを追加します。小さな集約構造は、Redisのバージョンとデータ型に応じて、listpackやintsetなどのコンパクトなエンコーディングを使用する場合がありますが、大きな構造はより一般的な表現を使用します。
- バッファメモリ: Redisは、クライアント出力バッファ、レプリケーションバックログバッファ、AOFバッファを使用します。大規模または低速なクライアント、またはビジーなレプリケーション設定は、かなりのバッファメモリを消費する可能性があります。
- フォークメモリ: RedisがRDBスナップショットの保存やAOFファイルの書き換えなどのバックグラウンド操作を実行するとき、子プロセスを
forkします。この子プロセスは、最初はコピーオンライト(CoW)を介して親とメモリを共有します。ただし、fork後に親プロセスがデータセットに書き込みを行うと、ページが複製され、メモリフットプリントが増加します。
Redisメモリの監視
Redisメモリを定期的に監視することは、問題が拡大する前に潜在的な問題を特定するために重要です。このための主要なツールは、INFO memoryコマンドとMEMORY USAGEです。
INFO memoryコマンド
redis-cli INFO memory
INFO memoryからの主要なメトリクス:
used_memory: Redisがアロケータ(jemalloc、glibcなど)を使用して割り当てたバイト数の合計。データ、内部データ構造、一時バッファによって使用されるメモリの合計です。used_memory_human: 人間が読み取り可能な形式のused_memory。used_memory_rss: 常駐セットサイズ(RSS)。オペレーティングシステムによって報告されるRedisプロセスが消費するメモリ量。これには、Redis自身の割り当てに加えて、オペレーティングシステムのメモリ管理、共有ライブラリ、およびOSにまだ解放されていない可能性のある断片化されたメモリが含まれます。mem_fragmentation_ratio: おおよそused_memory_rss / used_memory。1.0を超える値は正常です。はるかに高い値は、断片化、アロケータの動作、またはOSに返されていないRSSを示している可能性があります。1.0未満の値は調査する価値のある警告サインであり、メモリがスワップアウトされているか、測定タイミングの影響を示している可能性があります。allocator_frag_bytes: メモリアロケータによって報告された断片化のバイト数。lazyfree_pending_objects: 非同期で解放されるのを待っているオブジェクトの数。
MEMORY USAGEコマンド
個々のキーのメモリ使用量を検査するには:
redis-cli MEMORY USAGE mykey
redis-cli MEMORY USAGE myhashkey SAMPLES 0 # 集約の推定
このコマンドは、特定のキーの推定メモリ使用量を提供し、大規模または非効率的に保存されたデータポイントを特定するのに役立ちます。
主要なメモリ最適化戦略
Redisのメモリを最適化するには、適切なデータ型の選択から断片化の管理まで、いくつかの積極的な手順が必要です。
1. データ構造の最適化
Redisはいくつかのデータ構造を提供しており、それぞれに独自のメモリ動作があります。適切な構造は、アプリケーションがデータをどのように読み書きするかによって異なります。
- 文字列: 最もシンプルですが、大きな文字列には注意してください。非常に大きな文字列(MB単位)に対して
SETやGETを使用すると、ネットワークとメモリ転送のオーバーヘッドによりパフォーマンスに影響を与える可能性があります。 - ハッシュ、リスト、セット、ソート済みセット(集約): Redisは小さな集約データ型をコンパクトにエンコードできます。正確なエンコーディング名としきい値はRedisのバージョンによって異なるため、古い
ziplistの用語がどこにでも適用されると想定せず、redis.confとOBJECT ENCODINGの出力を確認してください。- ヒント: 個々の集約メンバーは小さく保ちます。ハッシュの場合は、少数の大きなフィールドよりも、多くの小さなフィールドを優先してください。
- 設定: Redisのバージョンによってここは異なります。古いバージョンでは
*-ziplist-*設定を使用していましたが、新しいバージョンでは一部の構造に*-listpack-*設定を一般的に使用します。実際のデータで注意深く調整してテストしてください。コンパクトなエンコーディングはメモリを節約しますが、特定のアクセスパターンではCPUコストがかかる可能性があるためです。
2. キーデザインのベストプラクティス
通常、値の方が多くのメモリを消費しますが、キー名の最適化も重要です。
- 短く、説明的なキー: キーが短いほど、特に数百万のキーがある場合、メモリを節約できます。ただし、極端な簡潔さのために明確さを犠牲にしないでください。説明的でありながら簡潔なキー名を目指してください。
- 悪い例:
user:1000:profile:details:email - 良い例:
user:1000:email(メールのみを保存する場合)
- 悪い例:
- プレフィックス: 整理目的で一貫したプレフィックス(例:
user:、product:)を使用します。これはメモリへの影響は最小限ですが、管理に役立ちます。
3. オーバーヘッドの最小化
すべてのキーと値にはある程度の内部オーバーヘッドがあります。特に小さなキーの数を減らすことは効果的です。
- 複数の文字列の代わりにハッシュ: エンティティに関連するフィールドが多数ある場合は、複数の
STRINGキーではなく、単一のHASHに保存します。これにより、トップレベルのキーの数とそれに関連するオーバーヘッドが削減されます。- 例:
user:1:name、user:1:email、user:1:ageの代わりに、フィールドname、email、ageを持つHASHキーuser:1を使用します。
- 例:
4. メモリ断片化の管理
メモリ断片化は、メモリアロケータが必要な正確なサイズの連続したメモリブロックを見つけられず、未使用のギャップが生じるときに発生します。これにより、used_memory_rssがused_memoryよりも大幅に高くなる可能性があります。
- 原因: さまざまなサイズのキーの頻繁な挿入と削除。特に、メモリアロケータが長時間実行された後。
- 検出:
mem_fragmentation_ratioが1.0(例:1.5~2.0)を大幅に超える場合は、高い断片化を示しています。 - 解決策:
- Redis 4.0以降のアクティブデフラグ: Redisは再起動せずにメモリをアクティブにデフラグできます。
redis.confでactivedefrag yesを有効にし、active-defrag-max-scan-timeとactive-defrag-cycle-min/maxを設定します。これにより、Redisはデータを移動してメモリを圧縮できます。 - Redisの再起動: メモリをデフラグする最も簡単な(ただし破壊的な)方法は、Redisサーバーを再起動することです。これにより、すべてのメモリがOSに解放され、アロケータは新たに開始します。永続的なインスタンスの場合は、再起動する前にRDBスナップショットまたはAOFファイルが保存されていることを確認してください。
- Redis 4.0以降のアクティブデフラグ: Redisは再起動せずにメモリをアクティブにデフラグできます。
# アクティブデフラグのredis.conf設定
activedefrag yes
active-defrag-ignore-bytes 100mb # 断片化が100MB未満の場合はデフラグしない
active-defrag-threshold-lower 10 # 断片化率が10%を超えたらデフラグを開始
active-defrag-threshold-upper 100 # 断片化率が100%を超えたらデフラグを停止
active-defrag-cycle-min 1 # デフラグの最小CPU負荷(1~100%)
active-defrag-cycle-max 20 # デフラグの最大CPU負荷(1~100%)
エビクションポリシー:maxmemoryの管理
Redisをキャッシュとして使用する場合、メモリが事前定義された制限に達したときに何が起こるかを定義することが重要です。redis.confのmaxmemoryディレクティブはこの制限を設定し、maxmemory-policyはエビクション戦略を指示します。
maxmemory 2gb # 最大メモリを2ギガバイトに設定
maxmemory-policy allkeys-lru # すべてのキーから最も最近使用されていないキーをエビクション
一般的なmaxmemory-policyオプション:
noeviction: (デフォルト)maxmemoryに達すると、新しい書き込みがブロックされます。読み取りは引き続き機能します。これはデバッグには適していますが、通常は本番キャッシュには適していません。allkeys-lru: すべてのキースペースから最も最近使用されていない(LRU)キーをエビクションします(有効期限の有無にかかわらず)。volatile-lru: 有効期限が設定されているキーのみからLRUキーをエビクションします。allkeys-lfu: すべてのキースペースから最も頻繁に使用されていない(LFU)キーをエビクションします。volatile-lfu: 有効期限が設定されているキーのみからLFUキーをエビクションします。allkeys-random: すべてのキースペースからランダムにキーをエビクションします。volatile-random: 有効期限が設定されているキーのみからランダムにキーをエビクションします。volatile-ttl: 有効期限が設定されているキーのみから最も短いTime To Live(TTL)を持つキーをエビクションします。
適切なポリシーの選択:
- 一般的なキャッシュの場合、データの有用性の指標として新しさと頻度のどちらが適切かによって、
allkeys-lruまたはallkeys-lfuがしばしば良い選択です。 - 主にセッション管理や明示的な有効期限を持つオブジェクトにRedisを使用する場合は、
volatile-lruまたはvolatile-ttlの方が適切な場合があります。
警告: maxmemory-policyがnoevictionに設定されていてmaxmemoryに達すると、書き込み操作が失敗し、アプリケーションエラーが発生します。
maxmemory値の選択
maxmemoryをサーバーの総RAMと等しく設定しないでください。オペレーティングシステム、Redisプロセスのオーバーヘッド、クライアントバッファ、レプリケーションバックログ、永続化のコピーオンライト、監視エージェント、緊急SSHアクセスのための余地を残してください。
キャッシュ専用のRedisインスタンスの場合、簡単な開始点は、物理RAMよりも快適なマージンを持ってmaxmemoryを設定し、ピーク負荷時およびバックグラウンド永続化中の実際のメトリクスを監視することです。永続化が重いインスタンスの場合は、RDBスナップショットとAOF書き換えが一時的にメモリプレッシャーを増加させる可能性があるため、より多くのヘッドルームを残してください。
危険な設定は、共有ホストで制限をまったく設定しないことです。RedisはOSや他のサービスと競合し、カーネルのOOMキラーが何を強制終了するかを決定するまで続く可能性があります。明確なmaxmemoryと意図的なエビクションポリシーの方が、推論が容易です。
大きなキーはレイテンシの問題でもある
メモリ管理は総バイト数だけではありません。1つの巨大なキーが、一見無害に見える操作に悪影響を与える可能性があります。
例:
redis-cli MEMORY USAGE huge:hash
redis-cli HLEN huge:hash
redis-cli LLEN queue:events
DELで非常に大きなキーを削除すると、Redisがメモリを解放している間、イベントループがブロックされる可能性があります。Redisのバージョンがサポートしている場合は、大きなキーにはUNLINKを優先してください。
redis-cli UNLINK huge:hash
UNLINKはキーを切り離し、メモリを非同期で解放します。キーはキースペースからすぐに消えますが、コストのかかる解放作業はバックグラウンドで行われます。
大きなコレクションの場合は、境界のあるサイズを考慮して設計してください。ストリームとリストをトリミングします。アクセスパターンに合う場合は、大きなハッシュをテナント、時間バケット、またはオブジェクトタイプごとに分割します。100万フィールドのハッシュは、トップレベルのキーのオーバーヘッドをいくらか節約できますが、移行、期限切れ、検査、削除が厄介になる可能性があります。
永続化とメモリオーバーヘッド
Redisの永続化メカニズム(RDBとAOF)もメモリと相互作用します。
- RDBスナップショット: RedisがRDBファイルを保存するとき、子プロセスを
forkします。スナップショットプロセス中に、親によるRedisデータセットへの書き込みは、コピーオンライト(CoW)によりメモリページの複製を引き起こします。これにより、特に頻繁なRDB保存が行われるビジーなインスタンスでは、メモリフットプリントが一時的に2倍になる可能性があります。 - AOF書き換え: 同様に、AOFファイルが書き換えられるとき(例:
BGREWRITEAOF)、forkが発生し、一時的なメモリ重複が発生します。AOFバッファ自体もメモリを消費します。
ヒント: 可能であれば、RDB保存とAOF書き換えをオフピーク時にスケジュールするか、サーバーにCoWオーバーヘッドを処理するための十分な空きRAMがあることを確認してください。
レイジーフリーイング
Redis 4.0では、大きなキーの削除やデータベースのフラッシュ時にサーバーがブロックされるのを防ぐために、レイジーフリーイング(非ブロッキング削除)が導入されました。Redisはメモリを同期的に再利用する代わりに、メモリ解放のタスクをバックグラウンドスレッドに配置できます。
lazyfree-lazy-eviction yes: エビクション中に非同期でメモリを解放します。lazyfree-lazy-expire yes: キーの有効期限が切れたときに非同期でメモリを解放します。lazyfree-lazy-server-del yes: サポートされているパスでのサーバー側の削除のために、非同期でメモリを解放します。lazyfree-lazy-user-del yes: ユーザーが発行したDELを、サポートされているRedisバージョンでUNLINKのように動作させます。
ビジーなインスタンスでは、同期的なメモリ再利用によるレイテンシスパイクを減らすために、レイジーフリーイングを慎重に有効にしてください。その後、lazyfree_pending_objectsを監視します。これが高いままの場合、バックグラウンドの解放作業が追いついていないことを意味します。
クライアントバッファとパイプライン
パイプラインは主にネットワーク最適化手法ですが、コマンド処理をより効率的にすることで、間接的にメモリパフォーマンスに影響を与える可能性があります。複数のコマンドを1回のラウンドトリップでRedisに送信することにより、ネットワークレイテンシとクライアントとサーバー側の両方でのコマンドあたりのCPUオーバーヘッドが削減されます。これにより、Redisは大きなコマンドキューを蓄積することなく、1秒あたりにより多くの操作を処理できるようになります。大きなコマンドキューは、クライアントバッファのメモリ使用量増加や、時間の経過とともにメモリアロケータに負荷をかける処理の遅延につながる可能性があります。
パイプラインはスループットを向上させることができますが、無制限のパイプラインはクライアント出力バッファを増加させる可能性があります。巨大なパイプラインを送信し、応答をゆっくりと読み取るクライアントは、Redisに大量の保留中の出力を保持させる可能性があります。
INFO clientsでクライアントバッファメトリクスを監視し、通常、レプリカ、およびpub/subクライアントに対して適切な制限を設定します。
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
これらは例示的なデフォルトであり、普遍的な推奨事項ではありません。重要なのは、遅れる可能性のあるクライアントに対して無制限の成長を避けることです。
便利なメモリレビュールーチン
Redisインスタンスが予想よりも多くのメモリを使用し始めた場合は、広い範囲から特定の範囲へと調査を進めてください。
INFO memoryでused_memory、RSS、断片化、アロケータ統計、保留中のレイジーフリーを確認します。INFO keyspaceで、1つのデータベースまたはキーファミリが増加しているかどうかを確認します。MEMORY USAGE、SCAN、およびタイプ固有の長さコマンドを使用して、大きなキーをサンプリングします。- キャッシュのようなキーにTTLがあることを確認します。
- 最近のデプロイで、新しいキー名、より長い値、または欠落している有効期限がないか確認します。
- 永続化のタイミングとフォーク関連のメモリプレッシャーを確認します。
- トラフィックスパイク中にメモリが急増した場合は、クライアントバッファを確認します。
たとえば、メモリ増加が新機能のリリースに続く場合は、有効期限のない新しいキーを探します。
redis-cli --scan --pattern 'feature-x:*' | head
redis-cli TTL feature-x:example
TTLが-1の場合、キーに有効期限がないことを意味します。これは永続データには正しい場合がありますが、キャッシュデータには通常間違っています。
Redisのメモリ問題は、インスタンスがいっぱいになる前に修正するのが最も簡単です。意図的なmaxmemoryを設定し、インスタンスの役割に一致するエビクションポリシーを選択し、キャッシュキーをTTLで管理し、過度に大きなキーを避け、フォークとバッファのためのヘッドルームを残してください。その後、主要なデータ形状の変更のたびに実際のメモリメトリクスを確認してください。Redisのメモリ動作が安定していれば、高速を維持できます。