【Redisアーキテクチャ診断】`maxmemory`の境界線:OSとRedisのメモリ生死闘を制する設計論
テックリードの私だ。コードレビューや設計レビューで、こんな甘い設定を見かけるたびに私は冷や汗をかく。
> 「`maxmemory`?とりあえずサーバーの物理メモリの8割くらいにしておけばいいか」
素人か。
この設定をナメてかかると、本番環境のピークタイムに突然RedisがOSのOOM Killer(Out-Of-Memory Killer)に処刑され、データベース全体のキャッシュが吹き飛ぶ。あるいは、データは守れたものの、書き込みが一切拒絶される地獄を見る。
Redisのメモリ管理は、単に「上限を決める変数」ではない。OSのカーネルメモリ、Redisの内部オーバーヘッド、そして悲惨なデータロストを防ぐための防衛ラインが複雑に絡み合う、極めてスリリングな領域だ。
今回は、実務の現場で絶対に踏んづけてはならない`maxmemory`の本質と、プロとして生き残るための設計パターンを授けよう。
—
1. なぜ「物理メモリ=`maxmemory`」が死を意味するのか?
まず大前提として、Redisのデータ構造(String, Hash, Zsetなど)がメモリ上に保持されるのは周知の事実だ。しかし、Redisが消費するメモリは「ユーザーが投入したデータのサイズ」だけではない。
Redisの裏側で消費される「隠れたメモリ」
1. プロセス自体のベースメモリ: バイナリやライブラリのフットプリント。
2. fork() 時のCopy-on-Write(CoW): `BGSAVE`(RDBスナップショット)やAOFの書き換え時に、OSはプロセスをフォークする。この瞬間、最悪の場合でRedisのメモリ消費量が「2倍」に跳ね上がる。
3. フラグメンテーション(断片化): jemalloc(Redisのメモリアロケータ)がメモリを効率的に管理しようとする過程で発生する、プロセス空間内の「隙間」。
ここに物理メモリ限界まで`maxmemory`を設定していたらどうなるか?
OSの空きメモリは枯渇し、Linuxカーネルは容赦なくRedisプロセスをKillする。これが、夜中にページャーが鳴り響く悪夢の正体だ。
—
2. `maxmemory` と eviction(退避ポリシー)のメカニズム
`maxmemory`に達した際、Redisがどう振る舞うかを決めるのが `maxmemory-policy` だ。ここを誤ると、ビジネス上重要なセッションデータやマスタキャッシュが消え去る。
実務で採用すべきポリシーは基本的に以下の2択に絞られる。
① `noeviction`(デフォルト・危険な要塞)
メモリが上限に達すると、書き込みコマンド(SET, HSETなど)に対してエラーを返す(`OOM command not allowed when used memory > ‘maxmemory’`)。
- 向いている用途: キャッシュではなく、永続的なデータストアとしてRedisを使っている場合(厳密にはRedisはDBではないが、キューイングやカウンターなど)。
② `volatile-lru` / `allkeys-lru`(現実的な戦場)
メモリが上限に達すると、LRU(Least Recently Used:最近最も使われていない)アルゴリズムに基づいて古いデータを自動削除する。
- `volatile-`: `EXPIRE` が設定されているキーのみ対象。
- `allkeys-`: すべてのキーが対象。
- 向いている用途: 純粋なキャッシュ層。消えてもDBから再取得できるデータ。
—
3. 【実践】プロダクション環境における `redis.conf` の最適解
では、実務ではどのように設定すべきか。物理メモリが 32GB の専用インスタンスを例に、具体的な設定と計算式を示そう。
安全なサイジングの方程式
$$\text{maxmemory} = \text{物理メモリ} – (\text{OSバッファ} + \text{CoW安全マージン})$$
32GBのサーバーであれば、Redisに割り当てるべき最大値はせいぜい 20GB 〜 24GB が限界だ。残りはOSのファイルキャッシュや `fork()` の一時的なスパイクに耐えるためのバッファとして空けておく。
以下が、プロダクションクオリティの `redis.conf` のスニペットだ。
=====================================================================
Memory Optimization Configuration
=====================================================================
1. メモリ上限を 24GB に厳格に制限 (バイト単位、またはGB指定)
maxmemory 24gb
2. 上限到達時の挙動:純粋なキャッシュであれば allkeys-lru を推奨
セッションストア等で勝手に消えて困る場合は noeviction にすること
maxmemory-policy allkeys-lru
3. LRUのサンプル数 (デフォルトの5から増やすと精度が上がるがCPU負荷増)
実務では 5〜10 の間でチューニング
maxmemory-samples 5
4. アクティブメモリーフラグメンテーション解放の有効化
OSのメモリ断片化を自動検知して解放を試みる
activedefrag yes
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
active-defrag-threshold-upper 30
—
4. コードレビューで使える「設計アンチパターン」
私がレビューの現場で即座に差し戻す、よくある悪手を紹介しよう。
アンチパターンA: クラスタ全体でメモリ制限を考えていない
アプリケーション側から大量のRedisインスタンスにバラバラにデータを突っ込み、各インスタンスの`maxmemory`が無制限(あるいは物理限界ギリギリ)になっているケース。
- 修正方針: Redis Clusterを組む場合、各ノードのメモリ上限を均等にし、かつノードごとの物理マシンのキャパシティ(CPUコア数やメモリ帯域)を超えないようにスケーリング設計を行うこと。
アンチパターンB: 有効期限(TTL)のないキャッシュの放置
`allkeys-lru`を設定しているから安心だと思って、TTLを一切設定せずにデータを放り込み続ける設計。
- 修正方針: LRUは「最近使われていないもの」を消すが、一度アクセスされた巨大なゴミデータがメモリを圧迫し続ける原因になる。すべてのキャッシュキーには必ず適切なTTLを付与せよ。
—
5. チーフアーキテクトからの最終提言
Redisのメモリ管理は、「ギリギリまでリソースを使い倒すこと」ではない。「システム全体の堅牢性を保ちながら、予測可能な範囲でパフォーマンスを最大化すること」だ。
1. 物理メモリの100%を`maxmemory`に設定する愚を犯すな。 必ず `fork()` とOSの領域を残せ。
2. データの性質(キャッシュか、永続データか)に合わせて `maxmemory-policy` をコードベースで合意しろ。
3. モニタリングを怠るな。 `INFO memory` コマンドの `used_memory`, `used_memory_rss`, `mem_fragmentation_ratio` を常時監視し、断片化率が1.5を超えたらアラートが飛ぶ仕組みを作れ。
この境界線を正しく理解し、コントロールできた者だけが、高負荷に耐えうる真に堅牢なRedisシステムを構築できる。次の設計レビューでは、この知見が反映されていることを期待する。
コメント