Redisメモリ管理の最終防衛ライン:アクティブデフラグメンテーションの深層と実践チューニング
テックリードの私だ。コードレビューをしていると、未だに「Redisはインメモリだから速い、以上」という浅い理解で設計され、メモリ断片化(Fragmentation)によって本番環境で突然OOM(Out of Memory) Killerに屠られるシステムを見かける。
特に、頻繁な更新(`HSET` や `ZADD`)や可変長の文字列操作、そして大規模なTTL(有効期限)の乱立が発生するシステムでは、OSのメモリ管理機構とRedisのメモリアロケータ(jemalloc)の間で激しい断片化が起きる。
Redis 4.0で導入された Active Defragmentation(アクティブデフラグメンテーション) は、この物理メモリの無駄遣いをプロセスを停止させずに(オンラインで)解消する、まさに神機能だ。だが、そのメカニズムとパラメータの意味を正確に理解せずデフォルトのまま放置していれば、CPUを不必要に食いつぶすか、あるいは全くデフラグが機能しないという悲劇を生む。
今回は、Redisのアクティブデフラグの内部挙動を剥ぎ取り、実務の現場でどう設定し、どう設計すべきかをロジカルに伝授しよう。
—
1. なぜRedisでメモリ断片化が起きるのか?(根本原因の理解)
まず敵を知る必要がある。Redisはメモリ管理に `jemalloc`(または `talloc`)を採用している。
jemallocは、メモリをチャンク(サイズクラス)単位で管理し、外部断片化(External Fragmentation)を最小限に抑えるように設計されている。
しかし、以下のようなワークロードでは、jemallocの努力も虚しく断片化率(`mem_fragmentation_ratio`)が跳ね上がる。
1. 頻繁な部分更新: 大きなHashやSorted Setの要素が頻繁に削除・追加され、アロケータのページ内に「使用中」と「空き」がまだらに存在する場合。
2. 有効期限(TTL)の大量失効: 数百万件のキーが同時に失効し、メモリが解放されたものの、隣接するキーがまだ生存しているため、OSへ物理ページ(4KBや2MBの巨大ページ)を返却できないケース。
結果として、「RedisがOSから確保している仮想メモリ(RSS)は30GBだが、実際にデータが占有しているメモリ(used_memory)は10GBしかない」という、極めて非効率な状態(断片化率3.0以上)が生まれる。この「使われていないのに確保され続ける空間」を物理的に再配置してOSに返還するのが、アクティブデフラグの仕事だ。
—
2. アクティブデフラグの内部メカニズム
アクティブデフラグは、Redisのバックグラウンドタスク(`serverCron`)の一部として動作する。
その基本アルゴリズムはシンプルだが巧妙だ。
1. スキャンと移動: 辞書(Dictionary)やスキップリストを走査し、断片化している古いメモリ領域にあるオブジェクトを特定する。
2. 再アロケータ: 新しいメモリ領域を確保し、そこにデータをコピーする(Reallocation)。
3. ポインタの付け替え: 内部のハッシュテーブル等のポインタを新しいアドレスにアトミックに書き換える。
4. 解放: 古いメモリ領域を解放し、jemalloc経由でOSへ返還を試みる。
特筆すべきは、これがシングルスレッドのイベントループをブロックしすぎないよう、CPU時間を細かくスライスして実行される点だ。
—
3. 設定パラメータの完全解剖:実戦で迷わないチューニング
アクティブデフラグを有効にするには、`redis.conf` または動的に `CONFIG SET` で設定を行う。
実務で必ず調整すべき主要パラメータを、私の推奨値とともに見ていこう。
デフラグの有効化(デフォルトは no)
activedefrag yes
— 発動条件 —
RSS(Resident Set Size)がこのバイト数を超えている場合のみデフラグを開始
active-defrag-ignore-bytes 100mb
断片化率(RSS / used_memory)がこの割合を超えたら発動(例: 1.5 = 50%の無駄がある状態)
active-defrag-threshold-lower 10
上記の割合がこれを超えたら、CPUリソースを最大フル投入して積極的にデフラグする
active-defrag-threshold-upper 30
— CPU負荷の制御(ここが最重要) —
CPU時間の何パーセントをデフラグに割くかの下限・上限
active-defrag-cycle-min 1
active-defrag-cycle-max 25
— スキャンの粒度 —
処理するハッシュテーブルのスロット数や、一度にスキャンするエントリ数の制御
active-defrag-max-scan-fields 1000
チューニングの急所
- `active-defrag-cycle-min / max` の罠:
デフォルトでは `min 1`, `max 25` になっている。もし、書き込みスループット(QPS)が極めて高いプライマリRedisで `max 25` を許可すると、クライアントのレイテンシー(P99/P999)に目に見えるスパイクが発生する。
高負荷なプロダクション環境では、`max` は 10〜15 程度に抑え、時間をかけてバックグラウンドで処理させるのが定石だ。
—
4. 堅牢な運用設計:コードレビューで指摘すべきポイント
テクニカルリードとして、設計レビューでは以下の点を確認・強制してほしい。
① メトリクスの監視を怠るな
Prometheus + Grafanaなどで、以下のメトリクスを必ずアラート監視対象に組み込むこと。
- `redis_mem_fragmentation_ratio` (一般的に 1.5 を超えたら黄色信号、2.0以上で赤信号)
- `redis_active_defrag_running` (デフラグが実際に稼働しているか)
② コマンドラインでの動的制御(緊急時の対応)
本番障害時、デフラグがCPUを圧迫している場合は、即座に無効化できる。逆に、急激なメモリ逼迫時は手動でアグレッシブに動かすことも可能だ。
緊急停止
redis-cli CONFIG SET activedefrag no
アグレッシブモードへの切り替え(CPUを最大50%使って一気にデフラグ)
redis-cli CONFIG SET active-defrag-cycle-max 50
③ クラスタ構成における注意点
Redis Cluster構成の場合、マスターノードごとにデータ量と書き込み傾向が異なる。
「全ノード一律で同じ設定」にするのではなく、容量が大きいホットスポットノードや、TTLが乱立するセッションストア用のノードに絞ってチューニングを行うべきだ。
—
5. まとめ:アーキテクトとしての提言
アクティブデフラグは、メモリ管理の「最後の切り札」であって、設計の粗さを隠すための魔法の杖ではない。
本質的な対策は、「不必要なキーの肥大化を防ぐデータ構造の選択」「適切なTTL設計によるメモリの自浄作用の促進」である。その上で、どうしも避けられない物理的な断片化に対して、今回解説した `activedefrag` を適切にサイジングして適用する。
このアプローチを徹底できれば、OOM Killerによる予期せぬシステムダウンとは永遠に決別できるはずだ。
さあ、今すぐ本番環境の `INFO memory` を叩き、自社のRedisの健康状態を確認してみたまえ。
コメント