Redis 6.0以降のアクティブデフラグ:限界領域を突破するメモリ管理とチューニングの極意
テックリードの私だ。今日のコードレビューで、あるジュニアエンジニアがこんな質問をしてきた。
> 「Redisのメモリ使用量が物理RAMの限界に達していないのに、OSの `used_memory_rss` が高騰してOOM Killerに殺されました。何が起きているんでしょうか?」
君たちも、この恐怖を味わったことがあるはずだ。Redisはインメモリデータベースの王者だが、その足元は常に「メモリの断片化(Fragmentation)」という見えない地雷原の上にある。
今回は、Redis 6.0以降でプロダクションの武器として完全に実用域に達した「アクティブデフラグ(Active Defragmentation)」を取り上げる。教科書的なリファレンスをなぞる気はない。現場でどう動き、どう設定し、どのリスクを踏み越えていくべきか、その極限の知見を授けよう。
—
1. なぜRedisで「断片化」が致命傷になるのか
まず、敵を知ることから始めよう。
Redisはデータを保持するために独自のアロケータ(デフォルトでは `jemalloc`)を使用している。アプリケーションが頻繁にデータの書き込み・削除・サイズ変更(特にHashやZSetなどの複合データ型)を繰り返すと、メモリ上に「細かい空き領域のパッチワーク」が生まれる。
これがメモリ断片化だ。
- `used_memory`: Redisが論理的に認識しているデータのサイズ。
- `used_memory_rss` (Resident Set Size): OSがRedisプロセスに割り当てている実際の物理メモリサイズ。
この差(RSS ÷ used_memory)が「断片化率(mem_fragmentation_ratio)」である。
これが `1.5` を超え、さらに高負荷時に突然大きなデータ(数MBのJSONや巨大なHash)を突っ込まれた瞬間、OSのメモリマネージャは連続した物理メモリを確保できず、スワップが発生するか、最悪の場合はLinuxのOOM Killerが発動してRedisが即死する。
従来の解決策の限界
Redis 4.0以前、我々はこれを防ぐために「サーバーの再起動(フェイルオーバー)」か「Luaスクリプトによる全キーの再ハッシュ(keyspace traversal)」という力技に頼っていた。しかし、これらはプロダクション環境においてあまりにもリスクが高かった。
そこで登場したのが、Redis 2.4から萌芽があり、Redis 6.0でマルチスレッドIOの文脈とともに劇的に洗練された「アクティブデフラグ」である。
—
2. アクティブデフラグのメカニズム:裏で何が起きているのか?
アクティブデフラグ(`activedefrag`)は、Redisがバックグラウンドでメモリ上のオブジェクトをスキャンし、断片化の少ない新しいメモリ領域へとデータを「コピー&ペースト(リロケート)」し、古い領域を解放する機能だ。
ここで重要なのは、Redisがシングルスレッド(正確にはメインスレッド)で動作するデータベースであるという点だ。メモリのコピーを無制限に行えば、レイテンシ(P99/P999)が跳ね上がり、クライアントからのリクエストがタイムアウトする。
そのため、アクティブデフラグは以下の制御アルゴリズム(Jemallocの機能と連携)で緻密にコントロールされている。
1. CPUサイクルの厳密な制限: メインループのCPU時間をどれだけデフラグに割くかを動的に調整。
2. ポインタの付け替え: メモリをコピーした後、Redis内部のハッシュテーブルやデータ構造が持つポインタをアトミックに書き換える。
—
3. プロダクション必須:チューニングパラメータの全貌
アクティブデフラグは、デフォルトでは無効(disabled)になっている。安易にデフォルト値のまま本番環境で有効化すると、CPU使用率が跳ね上がったり、逆に全くデフラグが進まなかったりする。
`redis.conf` または `CONFIG SET` で設定すべき主要パラメータの「実務的最適値」を解説しよう。
デフラグの有効化
activedefrag yes
断片化率がこの値を超えたらデフラグを開始する(例: 10%以上の無駄がある場合)
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
active-defrag-threshold-upper 30
CPU使用率の制御(最小〜最大)
メインスレッドのCPU時間を何%消費してデフラグを行うか
active-defrag-cycle-min 5
active-defrag-cycle-upper 25
処理するオブジェクト数や長さを制限し、レイテンシスパイクを防ぐ
active-defrag-max-scan-fields 1000
チューニングの急所
- `threshold-lower` (10) と `threshold-upper` (30):
断片化率が1.10(10%増)を超えるとデフラグが緩やかに始まり、1.30(30%増)に達すると、設定された最大のCPUリソースを投入して全力でデフラグを叩く。このレンジの設定が甘いと、常にCPUがデフラグに奪われるか、あるいは手遅れになる。
- `cycle-min` (5) と `cycle-upper` (25):
ここが最もシビアだ。`cycle-upper` を50などと高く設定すると、デフラグ中のレイテンシが急激に悪化する。高スループットが求められるAPI基盤であれば、上限は `25` 程度に抑え、時間をかけてバックグラウンドで綺麗にさせるのが鉄則だ。
—
4. 実戦での運用と監視:CLIを使ったインスペクション
アクティブデフラグが現在どのように動作しているか、そのライブ状態を把握するには `INFO memory`コマンドを使用する。
$ redis-cli INFO memory
注目すべき出力フィールドは以下の通りだ。
Memory
used_memory:1073741824 # 1GB (論理サイズ)
used_memory_rss:1610612736 # 1.5GB (物理サイズ)
mem_fragmentation_ratio:1.50 # 断片化率 1.5 (危険水域)
active-defrag-running:1 # デフラグ稼働中 (0なら停止中)
defrag_hits:124500 # リロケートされたポインタの数
defrag_misses:43200 # スキャンされたが移動されなかった数
defrag_key_hits:15000 # 再配置が行われたキーの数
defrag_key_misses:82000 # 再配置がスキップされたキーの数
もし `active-defrag-running: 0` でありながら `mem_fragmentation_ratio` が高い場合、`threshold-lower` の条件に達していないか、`active-defrag-ignore-bytes` に引っかかっている可能性が高い。
—
5. テックリードが警告する「設計上の罠」
アクティブデフラグは強力だが、魔法の杖ではない。以下のアンチパターンにハマると、かえってシステムを破壊することになる。
罠1: 巨大な単一キー(Mega-Keys)の存在
数百万要素を持つ単一のHashやZSetが存在する場合、デフラグ処理がそのキーをスキャン・再配置する瞬間に、メインスレッドが数ミリ秒〜数十ミリ秒間完全にブロックされる。
- 対策: キーあたりの要素数は数千〜数万程度に適切にシャーディング(分割)せよ。
罠2: 子プロセス(RDBスナップショット/AOFリライト)との競合
Redisが `BGSAVE` や `BGREWRITEAOF` を実行している最中(OSのCopy-on-Writeが発生している状態)、アクティブデフラグを同時に高負荷で回すと、物理メモリの書き換えが頻発し、実質的なメモリ消費量が一時的に倍増するリスクがある。
- 対策: `jemalloc` の挙動を理解し、メモリに余裕がない環境では、バックアップ時間帯とデフラグのピークが重ならないようにチューニングするか、アラートを連動させよ。
—
まとめ
Redisのアクティブデフラグは、もはや「知っている人だけが使う高度な機能」ではなく、大規模なプロダクション環境を運用する上での必須のインフラ設定である。
1. 断片化率のモニタリングを常時行い、閾値を超えたら自動発動するように設定する。
2. CPUを食いつぶさないよう、`cycle-min`/`cycle-upper` はトラフィックの特性に合わせて厳しく制限する。
3. 巨大なキーを作らないという、データモデリングの基本原則を絶対に忘れない。
この3点を押さえておけば、OOM Killerの夜間呼び出しに怯える日々とはおさらばできるはずだ。
さあ、今すぐ本番環境の `INFO memory` を叩いて、君のシステムの健康状態を確認してみたまえ。
コメント