T-CREATOR

Redis OOM を根絶:maxmemory・eviction・大キー検出の実践トリアージ

Redis OOM を根絶:maxmemory・eviction・大キー検出の実践トリアージ

Redis を本番環境で運用していると、避けて通れないのが OOM(Out Of Memory)エラーです。システムが突然応答不能になったり、パフォーマンスが急激に劣化したりして、ユーザー体験を大きく損なってしまいます。

本記事では、Redis の OOM 問題を根本的に解決するための段階的なアプローチをご紹介します。maxmemory 設定による防御、eviction ポリシーによる自動メモリ管理、そして大キー検出による予防保全という 3 つの手法を組み合わせることで、安定した Redis 運用を実現できるでしょう。

実際の設定例やスクリプトも交えながら、今日からすぐに実践できる内容をお届けいたします。

背景

Redis OOM の発生メカニズム

Redis は高速なインメモリデータストアとして人気ですが、その性質上メモリ使用量の管理が極めて重要になります。Redis が OOM に陥るメカニズムを理解することで、効果的な対策を講じることができます。

Redis のメモリ使用量は主に以下の要因で増大します。データサイズの増加、接続数の増加、そして内部的なメモリ断片化です。特に注意が必要なのは、Redis が単一スレッドで動作するため、メモリ不足が発生すると処理全体が停止してしまうことでしょう。

次の図は、Redis でメモリ不足が発生する典型的なフローを示しています。

mermaidflowchart TD
    app[アプリケーション] -->|データ書き込み| redis[Redis インスタンス]
    redis -->|メモリ使用量増加| memory[物理メモリ]
    memory -->|上限に到達| oom[OOM エラー発生]
    oom -->|処理停止| failure[システム障害]
    failure -->|影響| user[ユーザー体験悪化]

Redis がメモリ上限に達すると、新しいデータの書き込みが拒否され、最悪の場合プロセス自体が強制終了されます。

メモリ管理の重要性

Redis におけるメモリ管理は、単なるリソース管理を超えた戦略的な課題です。適切なメモリ管理により、システムの可用性とパフォーマンスを両立させることができます。

メモリ管理が重要な理由は 3 つあります。まず予測可能な動作の確保です。メモリ使用量をコントロールすることで、システムの挙動を予測しやすくなります。次にパフォーマンスの維持です。メモリ不足による swap の発生を防ぎ、Redis 本来の高速性を保てます。そして障害の予防です。OOM による突然のサービス停止を未然に防げるでしょう。

Redis のメモリ管理では、使用量の監視、適切な上限設定、そして不要データの自動削除という 3 つの要素が重要になります。

本番環境での影響範囲

Redis の OOM 問題は、本番環境において深刻な影響を与える可能性があります。影響範囲を正しく理解することで、対策の優先度を適切に判断できるでしょう。

直接的な影響として、サービスの応答停止、データの書き込み失敗、既存データへのアクセス不能が発生します。これらは即座にユーザー体験の悪化につながってしまいます。

間接的な影響も見逃せません。アプリケーションサーバーでの接続タイムアウト、データベースへの負荷集中、監視アラートの大量発生などが連鎖的に発生し、システム全体の安定性を損なう可能性があります。

また、復旧作業においても、Redis の再起動、データの整合性確認、キャッシュの再構築など、相当な時間とコストが必要になることも考慮すべきでしょう。

課題

OOM エラーの典型的な症状

Redis の OOM エラーは、様々な症状として現れます。これらの症状を早期に発見することで、深刻な障害を回避できる可能性が高まります。

最も一般的な症状は、書き込み操作の失敗です。Redis が (error) OOM command not allowed when used memory > 'maxmemory' エラーを返し、新しいデータの保存ができなくなります。

パフォーマンスの急激な低下も重要な指標です。メモリ不足により swap が発生すると、Redis の応答時間が劇的に遅くなってしまいます。通常ミリ秒単位で応答していた処理が、秒単位にまで延びることも珍しくありません。

以下の表は、OOM エラーの症状とその影響度をまとめたものです。

症状発生タイミング影響度検出方法
書き込みエラーメモリ上限到達時アプリケーションログ
応答時間悪化swap 発生時パフォーマンス監視
接続拒否プロセス強制終了時最高接続監視
データ不整合部分的書き込み失敗時データ整合性チェック

これらの症状が組み合わさることで、システム全体への影響が拡大していくことが特徴的です。

既存の対策手法の限界

従来の Redis OOM 対策には、いくつかの限界があります。これらの限界を理解することで、より効果的なアプローチの必要性が見えてきます。

単純なメモリ監視だけでは、問題発生後の対症療法にとどまってしまいます。アラートが発火してから手動でデータを削除しても、その間にサービスが停止してしまう可能性が高いでしょう。

手動でのデータ削除も限界があります。どのデータを削除すべきか判断に時間がかかり、誤って重要なデータを削除してしまうリスクも存在します。また、夜間や休日に発生した場合、対応が遅れがちになってしまいます。

次の図は、従来の対策手法とその限界を示しています。

mermaidflowchart LR
    monitor[メモリ監視] -->|しきい値超過| alert[アラート発火]
    alert -->|手動対応| manual[データ削除]
    manual -->|時間差| recovery[復旧]

    monitor -.->|限界1| reactive[事後対応型]
    manual -.->|限界2| human_error[人的ミス]
    recovery -.->|限界3| downtime[サービス停止]

従来手法では、問題発生から復旧まで人的な判断と作業が必要で、その間システムが不安定な状態になってしまいます。

運用上の課題点

Redis の OOM 問題は、技術的な課題だけでなく、運用面でも多くの困難を生み出します。これらの課題を解決することで、より安定したサービス運用が可能になります。

人的リソースの課題が深刻です。OOM 発生時には、熟練したエンジニアによる迅速な対応が必要になります。しかし、24 時間 365 日の対応体制を維持するのは現実的ではありません。特に深夜や休日の発生では、対応の遅れが致命的になる可能性があります。

データの優先度判断も困難な課題です。メモリ不足時にどのデータを削除すべきか、ビジネスロジックを理解した上で判断する必要があります。この判断を自動化するには、事前の設計と継続的なメンテナンスが不可欠でしょう。

また、影響範囲の特定に時間がかかることも問題です。Redis 停止の影響が他のシステムコンポーネントにどう波及するか、リアルタイムで把握するのは困難です。復旧作業中に新たな障害が発生するリスクも考慮する必要があります。

運用チームのスキルレベルの差も課題となります。Redis の内部動作を深く理解したメンバーとそうでないメンバーでは、対応速度と精度に大きな差が生まれてしまいます。

解決策

maxmemory 設定による制御

Redis の OOM 問題を根本的に解決する第一歩は、maxmemory 設定による事前の制御です。この設定により、Redis が使用できるメモリの上限を明確に定義し、予測可能な動作を実現できます。

maxmemory の設定は、物理メモリの使用量を制限することで、system 全体の安定性を保つ重要な仕組みです。設定値に達すると、Redis は新しいデータの書き込みを制御し、eviction ポリシーに従って既存データの削除を開始します。

適切な maxmemory 値の算出には、以下の要素を考慮する必要があります。利用可能な物理メモリ、OS やその他プロセスが使用するメモリ、Redis の内部オーバーヘッド、そして将来的な成長を見込んだバッファです。

一般的な算出式は次のようになります。

bash# 物理メモリが8GBの場合の例
# 推奨設定値 = (物理メモリ - OS予約 - その他プロセス) × 0.8
# 8GB - 2GB - 1GB = 5GB → 4GB (80%でバッファを考慮)

次の図は、maxmemory 設定による制御の仕組みを示しています。

mermaidflowchart TD
    app[アプリケーション] -->|書き込み要求| redis[Redis]
    redis -->|メモリ使用量チェック| check{maxmemory 以下?}
    check -->|Yes| accept[書き込み実行]
    check -->|No| eviction[eviction 実行]
    eviction -->|領域確保後| accept
    eviction -->|失敗時| reject[書き込み拒否]

maxmemory 設定により、Redis の動作が予測可能になり、システム全体の安定性が向上します。

eviction ポリシーの最適化

メモリ上限に達した際の動作を制御する eviction ポリシーは、Redis の自動メモリ管理において核となる機能です。適切なポリシーの選択により、アプリケーションの要件に応じた最適なデータ保持戦略を実現できます。

Redis では、以下の eviction ポリシーが利用可能です。

LRU 系ポリシー

  • allkeys-lru: 全キーから最も古いアクセスのものを削除
  • volatile-lru: TTL 設定済みキーから最も古いアクセスのものを削除

LFU 系ポリシー

  • allkeys-lfu: 全キーから最も使用頻度の低いものを削除
  • volatile-lfu: TTL 設定済みキーから最も使用頻度の低いものを削除

TTL 系ポリシー

  • volatile-ttl: TTL 設定済みキーから最も早く期限切れになるものを削除
  • volatile-random: TTL 設定済みキーからランダムに削除

その他

  • allkeys-random: 全キーからランダムに削除
  • noeviction: 削除せず、新規書き込みを拒否

各ポリシーの特徴を以下の表にまとめました。

ポリシー対象キー削除基準適用ケース
allkeys-lru全キーアクセス時間一般的なキャッシュ
volatile-lruTTL 付きのみアクセス時間セッション管理
allkeys-lfu全キーアクセス頻度ホットデータ重視
volatile-ttlTTL 付きのみ残存時間期限管理重視

アプリケーションの性質に応じて最適なポリシーを選択することで、重要なデータを保持しつつメモリ効率を最大化できます。

大キー検出システムの構築

メモリ使用量の大部分を占める大きなキーの存在は、Redis の OOM 問題における重要な要因です。大キー検出システムを構築することで、問題の予防と早期対処が可能になります。

大キーの定義は、データサイズ(一般的に 1MB 以上)、要素数(リストやセットで 10,000 要素以上)、そしてメモリ使用量への影響度で判断されます。これらの基準を明確にしておくことで、効果的な検出が可能になります。

大キー検出の手法には、複数のアプローチがあります。

リアルタイム監視手法

  • MEMORY USAGE コマンドによる定期チェック
  • SCAN コマンドと組み合わせた全キースキャン
  • クライアント側でのサイズ監視

ログベース分析手法

  • slowlog を活用した重い操作の特定
  • 書き込み時のサイズ記録
  • 定期的なレポート生成

次の図は、大キー検出システムの全体構成を示しています。

mermaidflowchart LR
    redis[Redis インスタンス] -->|監視| detector[大キー検出システム]
    detector -->|サイズチェック| memory_cmd[MEMORY USAGE]
    detector -->|キースキャン| scan_cmd[SCAN コマンド]
    detector -->|しきい値判定| analysis[分析エンジン]
    analysis -->|大キー発見| alert[アラート送信]
    analysis -->|レポート生成| report[定期レポート]

大キー検出システムにより、メモリ使用量の異常を早期に発見し、予防的な対応を取ることができるようになります。

具体例

maxmemory 設定の実装

Redis の maxmemory 設定を実装する際の具体的な手順と、環境に応じた設定値の決定方法をご紹介します。設定の変更は本番環境への影響を慎重に検討して行う必要があります。

まず、現在のメモリ使用状況を確認しましょう。

bash# 現在のメモリ使用量確認
redis-cli INFO memory

この出力から、used_memory_humanused_memory_peak_human の値を確認し、現在の使用傾向を把握します。

次に、システムの物理メモリ情報を取得します。

bash# システムメモリ情報の確認
free -h
# または
cat /proc/meminfo | grep MemTotal

適切な maxmemory 値を算出し、設定を行います。

bash# redis.conf での設定例
# 物理メモリ8GBのサーバーで、4GBを Redis に割り当てる場合
maxmemory 4294967296

# または人間が読みやすい形式で
maxmemory 4gb

設定を動的に変更する場合は以下のコマンドを使用します。

bash# 動的な設定変更(再起動不要)
redis-cli CONFIG SET maxmemory 4294967296

# 設定の永続化
redis-cli CONFIG REWRITE

設定変更後は、必ず動作確認を行います。

bash# 設定値の確認
redis-cli CONFIG GET maxmemory

# メモリ使用量の監視
watch 'redis-cli INFO memory | grep used_memory'

eviction ポリシーの選択と設定

eviction ポリシーの選択は、アプリケーションの特性を深く理解した上で行う必要があります。実際の設定例と、それぞれのポリシーの適用シナリオを詳しく説明します。

Web アプリケーションのセッション管理の場合

セッションデータは TTL が設定されており、ユーザーの最近のアクセス履歴を保持したい場合です。

bash# セッション管理に適した設定
redis-cli CONFIG SET maxmemory-policy volatile-lru

# TTL を適切に設定
redis-cli SETEX session:user123 3600 "{\"user_id\":123,\"last_active\":\"2024-01-15\"}"

API のレスポンスキャッシュの場合

すべてのキーがキャッシュデータで、アクセス頻度に基づいて保持したい場合です。

bash# レスポンスキャッシュに適した設定
redis-cli CONFIG SET maxmemory-policy allkeys-lfu

# キャッシュデータの例
redis-cli SET cache:api:users:list "{\"data\":[...],\"cached_at\":\"2024-01-15\"}"

混在環境での設定

重要なデータ(TTL なし)とキャッシュデータ(TTL あり)が混在する場合です。

bash# 混在環境に適した設定
redis-cli CONFIG SET maxmemory-policy volatile-ttl

# 重要データ(TTL なし)
redis-cli SET important:config "{\"app_version\":\"1.0.0\"}"

# キャッシュデータ(TTL あり)
redis-cli SETEX cache:temp:data 300 "temporary_cache_data"

各ポリシーの動作を検証するためのテストスクリプトも用意しておくと安心です。

javascript// eviction ポリシーのテストスクリプト
const redis = require('redis');
const client = redis.createClient();

async function testEvictionPolicy() {
  // メモリ使用量を上限近くまで増やす
  for (let i = 0; i < 1000; i++) {
    await client.set(`test:key:${i}`, 'x'.repeat(1024));
  }

  // どのキーが削除されたかを確認
  const remaining = await client.keys('test:key:*');
  console.log(`Remaining keys: ${remaining.length}`);
}

大キー検出スクリプトの開発

実際の本番環境で使用できる大キー検出スクリプトを、段階的に構築していきます。まずは基本的な検出機能から実装し、その後監視機能を追加していくアプローチを取ります。

基本的な大キー検出スクリプトの実装から始めましょう。

pythonimport redis
import sys
from datetime import datetime

def detect_large_keys(host='localhost', port=6379, threshold_mb=1):
    """
    大キーを検出するメイン関数

    Args:
        host: Redis サーバーのホスト
        port: Redis サーバーのポート
        threshold_mb: サイズしきい値(MB)
    """
    r = redis.Redis(host=host, port=port, decode_responses=True)
    threshold_bytes = threshold_mb * 1024 * 1024

    large_keys = []
    scan_cursor = 0

    print(f"[{datetime.now()}] 大キー検出を開始します(しきい値: {threshold_mb}MB)")

    while True:
        scan_cursor, keys = r.scan(cursor=scan_cursor, count=1000)

        for key in keys:
            try:
                # キーのメモリ使用量を取得
                memory_usage = r.memory_usage(key)

                if memory_usage and memory_usage > threshold_bytes:
                    key_type = r.type(key)
                    large_keys.append({
                        'key': key,
                        'size_mb': round(memory_usage / (1024 * 1024), 2),
                        'type': key_type,
                        'ttl': r.ttl(key)
                    })

            except Exception as e:
                print(f"Error checking key {key}: {e}")
                continue

        if scan_cursor == 0:
            break

    return large_keys

検出結果を整理して表示する機能を追加します。

pythondef print_large_keys_report(large_keys):
    """
    大キー検出結果のレポートを出力
    """
    if not large_keys:
        print("大キーは検出されませんでした。")
        return

    # サイズ順でソート
    large_keys.sort(key=lambda x: x['size_mb'], reverse=True)

    print(f"\n検出された大キー: {len(large_keys)}個")
    print("-" * 80)
    print(f"{'キー名':<40} {'サイズ(MB)':<10} {'型':<10} {'TTL':<10}")
    print("-" * 80)

    for key_info in large_keys[:20]:  # 上位20件を表示
        ttl_str = str(key_info['ttl']) if key_info['ttl'] != -1 else "無期限"
        print(f"{key_info['key']:<40} {key_info['size_mb']:<10} {key_info['type']:<10} {ttl_str:<10}")

def main():
    if len(sys.argv) > 1:
        threshold = float(sys.argv[1])
    else:
        threshold = 1.0  # デフォルト1MB

    large_keys = detect_large_keys(threshold_mb=threshold)
    print_large_keys_report(large_keys)

if __name__ == "__main__":
    main()

このスクリプトを定期実行するための cron 設定例も示します。

bash# crontab -e で以下を追加
# 毎日午前2時に大キー検出を実行し、結果をログに保存
0 2 * * * /usr/bin/python3 /opt/redis-tools/detect_large_keys.py 1.0 >> /var/log/redis-large-keys.log 2>&1

監視・アラート設定

Redis の OOM 対策を完成させるには、適切な監視とアラート設定が不可欠です。ここでは、実用的な監視システムの構築方法を段階的に説明します。

まず、基本的なメトリクス監視の設定から始めます。

bash# メモリ使用率監視スクリプト
#!/bin/bash

REDIS_HOST="localhost"
REDIS_PORT="6379"
THRESHOLD=80  # 使用率80%でアラート

# メモリ情報取得
MEMORY_INFO=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO memory)
USED_MEMORY=$(echo "$MEMORY_INFO" | grep "used_memory:" | cut -d: -f2 | tr -d '\r')
MAX_MEMORY=$(echo "$MEMORY_INFO" | grep "maxmemory:" | cut -d: -f2 | tr -d '\r')

if [ "$MAX_MEMORY" -gt 0 ]; then
    USAGE_PERCENT=$((USED_MEMORY * 100 / MAX_MEMORY))

    if [ "$USAGE_PERCENT" -gt "$THRESHOLD" ]; then
        echo "ALERT: Redis memory usage is ${USAGE_PERCENT}% (${USED_MEMORY}/${MAX_MEMORY} bytes)"
        # ここでアラート送信処理を追加
    fi
fi

Prometheus を使用した監視設定の例も示します。

yaml# prometheus.yml の設定例
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'redis'
    static_configs:
      - targets: ['localhost:9121'] # redis_exporter のポート

Grafana ダッシュボードでの可視化設定です。

json{
  "dashboard": {
    "title": "Redis OOM 監視",
    "panels": [
      {
        "title": "メモリ使用率",
        "type": "stat",
        "targets": [
          {
            "expr": "redis_memory_used_bytes / redis_config_maxmemory * 100",
            "legendFormat": "使用率 (%)"
          }
        ],
        "thresholds": [
          { "color": "green", "value": 0 },
          { "color": "yellow", "value": 70 },
          { "color": "red", "value": 85 }
        ]
      }
    ]
  }
}

アラート設定では、複数の段階でのアラートを定義します。

yaml# alertmanager のルール設定例
groups:
  - name: redis_oom_alerts
    rules:
      - alert: RedisMemoryUsageHigh
        expr: redis_memory_used_bytes / redis_config_maxmemory > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: 'Redis メモリ使用率が高くなっています'
          description: 'Redis の メモリ使用率が80%を超えています'

      - alert: RedisMemoryUsageCritical
        expr: redis_memory_used_bytes / redis_config_maxmemory > 0.9
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: 'Redis メモリ使用率が危険レベルです'
          description: 'Redis の メモリ使用率が90%を超えています'

Slack 通知の設定例も含めて、総合的な監視システムを完成させます。

pythonimport requests
import json
from datetime import datetime

def send_slack_alert(webhook_url, memory_usage_percent, used_memory, max_memory):
    """
    Slack にアラートを送信
    """
    message = {
        "text": f"🚨 Redis OOM アラート",
        "attachments": [
            {
                "color": "danger" if memory_usage_percent > 90 else "warning",
                "fields": [
                    {
                        "title": "メモリ使用率",
                        "value": f"{memory_usage_percent}%",
                        "short": True
                    },
                    {
                        "title": "使用メモリ",
                        "value": f"{used_memory / (1024*1024):.1f}MB",
                        "short": True
                    },
                    {
                        "title": "最大メモリ",
                        "value": f"{max_memory / (1024*1024):.1f}MB",
                        "short": True
                    },
                    {
                        "title": "発生時刻",
                        "value": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                        "short": True
                    }
                ]
            }
        ]
    }

    response = requests.post(webhook_url, data=json.dumps(message),
                           headers={'Content-Type': 'application/json'})
    return response.status_code == 200

まとめ

実装効果の検証

Redis OOM 対策の実装により、どのような効果が得られるかを具体的に検証していきましょう。適切な測定指標を設定することで、施策の効果を定量的に評価できます。

メモリ使用量の安定化

maxmemory 設定と eviction ポリシーの導入により、メモリ使用量の上限が明確になります。実装前は不規則に増減していたメモリ使用量が、設定した上限内で安定して推移するようになるでしょう。

以下の指標で効果を測定できます。

指標実装前実装後改善率
OOM 発生回数(月)3-5 回0 回100%
メモリ使用率の分散高い(0-100%)低い(60-85%)70%改善
応答時間の安定性不安定安定80%改善
障害復旧時間30-60 分5-10 分80%短縮

システム全体の安定性向上

Redis の OOM 問題が解決されることで、システム全体の可用性が大幅に向上します。アプリケーションサーバーでのタイムアウトエラーの減少、データベースへの負荷集中の回避、そして監視アラートの最適化が実現されます。

運用負荷の軽減

自動化された仕組みにより、深夜や休日の緊急対応が大幅に減少します。手動でのデータ削除作業が不要になり、運用チームの作業効率が向上するでしょう。

継続的な運用のポイント

Redis OOM 対策を長期的に成功させるには、継続的な改善と監視が重要です。一度設定して終わりではなく、システムの成長に合わせて定期的な見直しを行う必要があります。

定期的な設定見直し

アプリケーションの成長に伴い、データ量やアクセスパターンが変化します。3 ヶ月から 6 ヶ月ごとに、以下の項目を見直すことをお勧めします。

  • maxmemory 値の適切性
  • eviction ポリシーの効果
  • 大キー検出のしきい値
  • アラート条件の調整

チーム内での知識共有

Redis の OOM 対策に関する知識を、チーム全体で共有することが重要です。新しいメンバーが加わった際の教育、定期的な勉強会の開催、そしてトラブルシューティングのドキュメント整備を継続的に行いましょう。

技術動向の追跡

Redis の新しいバージョンでは、メモリ管理機能が継続的に改善されています。新機能の活用可能性を定期的に検討し、より効率的な運用方法を模索することが大切です。

Redis 7.0 以降では、ACL(Access Control List)機能の強化、新しい eviction アルゴリズムの追加、そして監視機能の向上などが行われています。これらの新機能を活用することで、さらに効果的な OOM 対策が可能になります。

継続的な運用により、Redis の OOM 問題を根絶し、安定したサービス提供を実現できるでしょう。

関連リンク

Redis の OOM 対策について、さらに深く学習したい方のために、有用な公式ドキュメントと参考資料をご紹介します。

公式ドキュメント

監視・運用関連

パフォーマンス・チューニング

これらのリソースを活用して、Redis の OOM 対策をさらに強化し、より安定したシステム運用を実現してください。