Redis OOM 障害の調査と対処まとめ(Laravel / ElastiCache)
日々の開発では問題なく進むこともあれば、朝一番で Slack に「Queue が止まりました」「サーバーにアクセスできません」といった通知が飛んでくることもあります。
本記事では、開発環境で 280 万以上の Redis Key が蓄積され、Redis OOM が発生した事例をもとに、調査手順と再発防止策を整理します。
1. 障害発生の兆候
Slack で以下のエラーが連続発生。
OOM command not allowed when used memory > 'maxmemory'.
RedisException: OOM command not allowed when used memory > 'maxmemory'.
Redis OOM が発生すると以下の影響が出る。
- Redis が書き込みを拒否
- Queue / Horizon が停止
- Laravel が Job を push できない
- Redis を利用する処理がほぼ全て失敗
AWS ElastiCache のメトリクスを確認すると、
明らかに Redis 内部に不要な Key が大量生成されている可能性が高い。
2. Redis 内部の調査
ECS コンテナ(Alpine)に入り、まず redis-cli を導入した。
apk add redis vim
対象の Redis(DB1)へ接続:
redis-cli -h <redis-host> -n 1 --scan
目的は RAM を消費している Key の種類を特定すること。
Redis のメモリ肥大化は以下 2 つが主因である。
- 大きすぎる Key
- Key 数が多すぎる
今回の状況から後者を疑った。
3. 不自然な Key パターンの発見
ソースコードを grep しながら調査したところ、以下のパターンに大量の Key が生成されていることに気づいた。
laravel_dev_cache_:App\\Jobs*
試しに Key 数をカウント:
redis-cli -h <redis-host> -n 1 --scan --pattern "laravel_dev_cache_:App\\\\Jobs*" | wc -l
結果:
2800000
約 280 万 Key
これがメモリ圧迫の要因で確定。
4. TTL が設定されていない “不死 Key”
TTL を確認:
redis-cli -h <redis-host> -n 1 TTL "laravel_dev_cache_:App\\Jobs\\JobName:1234567890"
結果:
-1
TTL = -1 は 有効期限なし(永続)。
つまり、
- Job 実行ごとに新規 Key が生成される
- TTL がないため削除されない
- 毎分のように Key が増え続ける
典型的な Redis の誤用によるメモリリーク。
5. 280 万 Key を安全に削除する方法
KEYS コマンドは Redis をブロックするため、OOM 状態では危険。
安全な方法は SCAN + DEL を少しずつ実行すること。
Bash で Key を順次削除
while IFS= read -r key; do
echo "Deleting key: '$key'"
redis-cli -h <redis-host> -n 1 DEL "$key"
done < <(redis-cli -h <redis-host> -n 1 --scan --pattern "laravel_dev_cache_:App\\\\Jobs*")
数十万単位でも Redis を止めずに削除できる。
必要であればバックグラウンドで実行:
nohup sh -c '
while IFS= read -r key; do
echo "Deleting key: '\''$key'\''"
redis-cli -h <redis-host> -n 1 DEL "$key"
done < <(redis-cli -h <redis-host> -n 1 --scan --pattern "laravel_dev_cache_:App\\\\Jobs*")
' > /tmp/redis_delete.log 2>&1 &
削除完了後:
- Memory 使用率:100% → 10%
- Horizon / Queue が復旧
6. 根本原因の分析
コードを確認したところ、
- Job 実行時に metadata を Redis に保存
- Key prefix:
laravel_dev_cache_:App\\Jobs:* - TTL なし
- Cleanup 処理なし
- Scheduler の動作が想定以上に多頻度
という状況で、結果的に 永続的な Key が指数的に増加していた。
7. 再発防止策
7.1 全ての Key に TTL を付与する
Laravel:
Cache::put($key, $value, now()->addMinutes(60));
Redis:
Redis::setex($key, 3600, $value);
有効期限なしの Key は極力禁止。
7.2 Key を個別に作るのではなく Hash に集約する
Before(数百万 Key):
laravel_dev_cache_:App\\Jobs:<jobId>
After(1 日あたり数十 Key):
HSET laravel_dev:jobs:<date> <jobId> <data>
EXPIRE laravel_dev:jobs:<date> 86400
7.3 ログ量を制御する
例:最新 200 件のみ保持
Redis::ltrim('laravel_dev:jobs_log', -200, -1);
8. 障害から得られた学び
8.1 Redis は DB ではない
ログや大量の履歴データ保存には不向き。
8.2 Key には必ず TTL を付ける
例外は最小限にすべき。
8.3 Key 数もメトリクスとして監視対象
memory だけでなく key-count も重要。
8.4 アラート閾値を設定
- Memory > 80%
- Key-count 異常増加
- Queue lag
8.5 OOM が発生しても SCAN+DEL で復旧可能
KEYS は絶対に使わない。
9. まとめ
今回の Redis OOM 障害は、
- 不適切な Key 設計
- TTL 未設定
- Job の増加
が複合的に重なった結果として発生した。
Redis は高速で便利だが、運用設計を誤るとメモリリークに直結する。
本記事の調査・対策が、同様のトラブルを未然に防ぐ参考になれば幸いです。
