【実務・中級編】 OSスワップ発生時の挙動 – Redis

Redisのメモリ管理と最適化:OSスワップという「静かなる殺人者」の正体

テックリードの私だ。今日のコードレビュー、あるいは昨夜発生した本番障害のポストモーテム(事後分析)で、こんな疑問を持ったことはないか?

> 「なぜ、RedisのCPU使用率は低いのに、特定のAPIリクエストだけが数秒単位でタイムアウトするのか?」
> 「なぜ、メモリに十分な空きがある(ように見える)のに、突然Redisのレイテンシが跳ね上がるのか?」

犯人は、OSのメモリ管理サブシステム、そして無慈悲な「スワップ(Swap)」だ。

世の中の入門書やブログは「Redisはインメモリデータベースだから高速です」としか書かない。だが、シニアエンジニアならその裏にある現実を知るべきだ。OSのカーネルは、あなたのアプリケーションがRedisであろうがなかろうが、メモリが圧迫されれば平然とRedisのメモリページをディスク(スワップ領域)に吐き出す。

今回は、Redisがスワップ領域に追い出された瞬間に何が起きるのか、そしてインフラエンジニアやアプリ開発者がどう防衛すべきか、その極限の知見を授けよう。

—

1. なぜRedisはスワップに弱いのか?(レイテンシ悪化のメカニズム)

一般的なWebアプリケーションであれば、OSがプロセスをスワップアウトしても、ディスクアクセス(ページフォルト)が発生した瞬間に少しレイテンシが伸びる程度で、致命傷にはならないことが多い。

しかし、Redisは違う。

シングルスレッド・アーキテクチャの呪縛

Redisのコアロジックは、基本的にシングルスレッドで動作している。これは「ロック競合がないため極限まで高速」という最大の武器であると同時に、「1つの遅延が全体を殺す」という最大の弱点でもある。

RedisがOSのスワップ領域に追い出されたプロセス(メモリページ)にアクセスしようとした瞬間、以下の地獄のようなシナリオが展開される。

1. ページフォルトの発生:
Redisが特定のキー(例えば数MBのハッシュや巨大なSorted Set)にアクセスしようとする。しかし、そのデータを含むメモリページはディスク(HDDやSSD)へ退避されている。
2. CPUの待機(Block):
カーネルはディスクからデータを物理メモリに読み戻す(Page In)必要がある。この間、Redisのシングルスレッドは完全にブロックされる。 OSのI/O処理が終わるまで、CPUは次の命令を実行できない。
3. 雪崩式タイムアウト(Cascading Failures):
Redisが1つのページフォルトのI/O待ちで数ミリ秒〜数秒止まると、その背後で待機している何千ものクライアントからのコマンドがすべてキューイングされる。復帰した瞬間、溜まったリクエストを一気に処理しようとしてCPUがスパイクし、さらなるレイテンシ悪化を引き起こす。

「Redisの応答が、ある瞬間から `0.1ms` から `1500ms` に跳ね上がった」という現象の9割は、このOSスワップが原因だ。

—

2. `vm.swappiness` の罠と真実

Linuxカーネルパラメータである `/proc/sys/vm/swappiness`。
多くのエンジニアが「スワップを無効化したいなら `0` にすればいい」と誤解している。ここには、カーネルの歴史と実装に関する深い罠がある。

`swappiness = 0` の誤解

かつて、`vm.swappiness=0` に設定すると、「メモリが枯渇するまで絶対にスワップしない」という意味だった。
しかし、近年のLinuxカーネル(Linux 3.5以降など)では仕様が変わり、`0` に設定しても「匿名メモリ(Anonymous Memory: Redisのデータ領域など)をスワップアウトすることはあるが、極力ファイルキャッシュを優先する」という挙動に変わっている。

完全にスワップを防ぎたい場合、`swappiness = 0` では不十分なケースがあるのだ。

推奨されるカーネルチューニング

プロダクション環境でRedisを動かす場合、私は以下の設定を強く推奨する。

1. swappinessを極限まで下げる(カーネルの匿名メモリ退避を抑制)
sudo sysctl vm.swappiness=1

2. オーバコミットメモリの設定(重要)
3(デフォルト)の場合、メモリが足りないとOSが勝手にプロセスを殺すが、
1に設定することで、メモリ割り当ての要求を厳密に管理する
sudo sysctl vm.overcommit_memory=1

※もっとも確実なのは、OS全体のスワップを完全に無効化することだ(`sudo swapoff -a`)。しかし、クラウド環境や他のミドルウェアとの兼ね合いでスワップを完全に切れない場合は、`vm.swappiness=1` が現実的な防衛ラインとなる。

—

3. 実践:スワップ発生の検知とコードレベルでの対策

「うちのRedisはスワップしていない」という思い込みは捨てろ。データで証明するのがエンジニアだ。

① スワップ使用量の確認コマンド

特定のプロセスがどれだけスワップされているかは、`/proc//smaps` をパースするのが最も確実だ。以下のワンライナーを叩いてみてほしい。

RedisのPIDを特定し、スワップされている合計サイズ(KB)を算出する
PID=$(pgrep redis-server)
sudo awk ‘/Swap:/ {sum += $2} END {print sum ” KB”}’ /proc/$PID/smaps

もしこの値が `0` より大きければ、あなたのRedisはすでにOSの魔手に捕らえられている。即座に対策が必要だ。

② Redis側からのモニタリング

RedisのCLIから `INFO memory` を実行し、以下の指標を監視せよ。

127.0.0.1:6379> INFO memory
Memory
used_memory: 10737418240 # 10GB
used_memory_rss: 4294967296 # 4GB (RSSが極端に小さい場合は注意)
mem_fragmentation_ratio: 0.4 # 1.0未満は物理メモリ不足の強いシグナル

`used_memory`(Redisが認識している論理メモリサイズ)に対して、`used_memory_rss`(OSが実際に割り当てている物理メモリサイズ)が著しく小さい場合、OSがメモリの一部をスワップアウトしている可能性を疑うべきだ。

—

4. 堅牢なアーキテクチャ設計:どう設計すべきか?

コードレビューで「Redisのメモリが足りなくなりそうです」と言われたとき、どう返すべきか?
単に「インスタンスのメモリをスケールアップしろ」と言うのはジュニアの仕事だ。シニアエンジニアなら、以下の多層防御(Defense in Depth)を提示できなければならない。

設計パターン A: メモリ上限(maxmemory)の厳格な設定と逐次削除ポリシー

物理メモリの限界までRedisに使わせてはならない。OSのカーネルや他のプロセス、そしてRDBスナップショット(BGSAVE時)のCopy-on-Write(CoW)によるメモリバースト分を考慮する必要がある。

redis.conf の推奨設定例
物理メモリが16GBのノードの場合、安全を見て10GB〜11GBを上限とする
maxmemory 10gb

メモリ上限に達した場合のポリシー
LRU(Least Recently Used)や LFU(Least Frequently Used)を適切に選定する
maxmemory-policy volatile-lru

特に `BGSAVE`(RDB保存)や `REWRITEAOF` を行う際、Linuxはfork()を使用する。この時、親プロセスと子プロセスでメモリページが共有され、データが更新されるたびにメモリがコピーされる(Copy-on-Write)。そのため、最悪の場合、現在使用中のメモリと同等の空き容量が一時的に必要になる。物理メモリの50%以上をRedisに割り当てる設計は、それだけで「障害待ち」の爆弾を抱えていると心得よ。

設計パターン B: 巨大なオブジェクトの排除(チャンク分割)

1つのString型キーに数十MBのJSONや画像を突っ込んでいないか?
前述の通り、スワップ発生時にその巨大なキーにアクセスすると、数MB単位のページフォルトが発生し、数千サイクルのCPUが完全にブロックされる。

  • アンチパターン: 1つのキーに全ユーザーのタイムライン(数MB)をJSONで保存する。
  • 正しい設計: Hash型やSorted Setに分割し、1つの要素のサイズを数KB〜数十KB以内に収める。

—

5. テックリードからの総括

RedisとOSスワップの関係は、いわば「高速道路を走るスポーツカーのタイヤに、突然泥を塗りたくるようなもの」だ。
どれだけ強力なCPU積んでいこうが、OSがスワップ領域に手を伸ばした瞬間、Redisのパフォーマンスは崩壊する。

設計レビューやインフラ構築の場では、以下の3点を徹底してほしい。

1. OSのスワップは可能な限り無効化、または `vm.swappiness=1` で極限まで抑制する。
2. 物理メモリの全量をRedisに割り当てるな。`BGSAVE` 時のCoW(Copy-on-Write)のバースト領域を必ず残せ。
3. 巨大な単一キーの作成を禁止し、データ構造を適切に細分化せよ。

「動けばいい」ではない。「高負荷時でも絶対に遅延しない」システムを設計するのがプロのエンジニアだ。次の設計では、この知見を必ずコードとインフラ構成に反映させてくれ。期待している。

コメント

タイトルとURLをコピーしました