Redisの「ERR invalid expire time」エラー(TTLが0または負)の修正方法

beginner🔴 Redis2026-06-18| Linux、macOS、またはDocker上で動作するRedis(バージョン5.0から7.x)

Error Message

(error) ERR invalid expire time in 'set' command
#redis#ttl#キャッシュ#バックエンド#ioredis

エラーのシナリオ

Node.jsサービス向けのセッションキャッシュを構築していた際、ユーザーのログイン有効期限が終了する直前にのみ発生するバグに遭遇しました。私のコードでは、セッションの有効期限タイムスタンプから現在時刻を引くことで、TTL(Time To Live)を動的に計算していました。高トラフィック時のテストではすべて正常に見えましたが、セッションの残り時間が数ミリ秒になった途端、ログが次のような内容で埋め尽くされ始めたのです。

(error) ERR invalid expire time in 'set' command

このエラーはRedis CLIで簡単に再現できます。SETコマンドで、0以下の値をEX(秒)またはPX(ミリ秒)引数として使用しようとすると、Redisは操作を完全に拒否します。

# 0は正の整数ではないため失敗します
redis> SET session:user:123 "data" EX 0
(error) ERR invalid expire time in 'set' command

# 負の値もエラーを発生させます
redis> SET session:user:123 "data" EX -10
(error) ERR invalid expire time in 'set' command

分析:なぜSETコマンドは厳格なのか

RedisはEXおよびPXオプションが正の整数であることを要求します。これは、スタンドアロンのEXPIREコマンドがはるかに寛容であるため、開発者が陥りやすい罠です。EXPIRE key 0を実行すると、単にキーが削除されます。しかし、SET ... EXはアトミックな操作です。Redisは、曖昧なコマンドが部分的に実行されるのを防ぐために、ここではより厳格な検証を強制します。

ほとんどのクラッシュは、TTLの計算が以下のようになっている場合に発生します。 patterns:

# Pythonの例
ttl = target_expiry - current_time
# current_time >= target_expiry の場合、ttlは0または負になります
redis_client.set("session_key", "value", ex=ttl)

アプリケーションロジック内のわずか5ミリ秒の遅延でさえ、正のTTLをゼロに変えてしまう可能性があります。これにより、データが正常に期限切れになるのではなく、データベースへの書き込み全体が失敗してしまいます。

SET EX と EXPIRE の違い

SET ... EXを作成コマンド、EXPIREをメンテナンスコマンドと考えると分かりやすいでしょう。SETは新しいデータを書き込もうとしているため、Redisは値をメモリにコミットする前に有効期限のロジックが妥当であることを確認しようとします。時間が無効な場合、コマンド全体を拒否します。データは保存されず、アプリケーションはおそらくハンドルされていない例外をスローします。

解決策1:TTLロジックのサニタイズ

最も直接的な修正方法は、TTLが常に少なくとも1秒(またはPXの場合は1ミリ秒)であることを保証することです。計算結果が0以下の場合、そのデータは技術的にはすでに期限切れです。その場合、整合性を維持するために、SETを完全にスキップするか、既存のキーを削除する必要があります。

Python (redis-py) での実装

def safe_set(key, value, ttl_seconds):
    # TTLが少なくとも1であることを確認するか、書き込みをスキップします
    if ttl_seconds > 0:
        redis_client.set(key, value, ex=int(ttl_seconds))
    else:
        # データはすでに古くなっています。確実に削除されるようにします
        redis_client.delete(key)

Node.js (ioredis) での実装

async function setWithExpiry(key, value, ttlMs) {
    if (ttlMs > 0) {
        await redis.set(key, value, "PX", ttlMs);
    } else {
        // 処理中にセッションが期限切れになったエッジケースを処理します
        await redis.del(key);
    }
}

解決策2:絶対時間による期限切れ(EXAT/PXAT)を使用する

Redis 6.2以降を使用している場合は、EXATまたはPXATを使用することでTTLの計算を回避できます。これらのオプションを使用すると、相対的なオフセットではなく、特定のUnixタイムスタンプ(例:1735689600)を指定できます。これはセッション管理において非常に信頼性が高い方法です。

# 特定のタイムスタンプで期限切れになるようにキーを設定します
redis> SET session:user:123 "data" EXAT 1735689600

プロのヒント: EXとは異なり、EXATに過去のタイムスタンプを指定した場合、Redis 6.2+はOKを返しますが、既存のキーを削除するか、新しいキーを作成しません。この動作は、"invalid expire time"によるクラッシュを完全に防ぐことができるため、動的なアプリケーションにとって非常に安全です。

修正の検証

安定性を確保するために、以下の3つのシナリオで実装をテストしてください。

  • 通常ケース: 60秒のTTLを指定します。キーが存在し、TTL keyが約60を返すことを確認します。
  • エッジケース: コード内でTTLを強制的に0または-1にします。アプリケーションがクラッシュせず、Redisにキーが存在しないことを確認します。
  • 時刻同期: アプリとRedisが異なるサーバーで動作している場合は、NTPを使用して時刻を同期させてください。セッションが制限に近い場合、1秒のズレが大きな問題を引き起こす可能性があります。

まとめ

ERR invalid expire timeが発生する場合、それはほとんどの場合、TTL計算における競合状態の兆候です。RedisのSETは正の数を期待しています。単純なif ttl > 0チェックを追加するか、EXATに切り替えることで、キャッシュレイヤーを「残り1秒」のセッションバグに対してより堅牢にすることができます。

Related Error Notes