7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel で発生した Redis OOM 障害の調査:280 万件以上の不要 Key を特定し、根本原因を解消するまで

Posted at

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 のメトリクスを確認すると、

  • Memory 使用率 100%
  • 傾斜が一定に増加するグラフ(典型的なメモリリーク)
    Screenshot 2025-12-09 at 08.12.46.png
  • Eviction なし(volatile-lru 設定)

明らかに 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 つが主因である。

  1. 大きすぎる Key
  2. 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 は高速で便利だが、運用設計を誤るとメモリリークに直結する。

本記事の調査・対策が、同様のトラブルを未然に防ぐ参考になれば幸いです。

7
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?