RedisのINCRBYFLOATで「ERR value is not a valid float」エラーを修正する方法

beginner🔴 Redis2026-05-15| Redis 4.x、5.x、6.x、7.x — 任意のOS(Linux、macOS、Windows WSL)

Error Message

ERR value is not a valid float
#redis#incrbyfloat#float#データ型#数値

TL;DR

RedisはINCRBYFLOATが数値として解析できないキー(空文字列、カンマ区切りの"1,5""NaN"など)に当たるとERR value is not a valid floatをスローします。解決策:INCRBYFLOATを呼び出す前に、キーに正しい数値文字列が格納されていることを確認してください。

このエラーが発生する原因

以下を試すと即座に確認できます:

127.0.0.1:6379> SET price "not-a-number"
OK
127.0.0.1:6379> INCRBYFLOAT price 1.5
(error) ERR value is not a valid float

空文字列やロケール形式の数値も同じエラーになります:

127.0.0.1:6379> SET counter ""
OK
127.0.0.1:6379> INCRBYFLOAT counter 0.1
(error) ERR value is not a valid float

127.0.0.1:6379> SET ratio "1,75"   # カンマ区切りの小数 — Redisでは無効
OK
127.0.0.1:6379> INCRBYFLOAT ratio 0.25
(error) ERR value is not a valid float

内部的には、Redisは格納された値を解析するためにstrtod()を呼び出します。この関数はCのdoubleリテラルと同じ形式を受け付けます:オプションの符号、数字、ドット、指数——つまり1.5-3e2.75はすべて通過します。それ以外はすべて拒否されます。

本番環境でよく見られる4つのパターン

ほとんどのチームは以下のいずれかのパターンでこのエラーに遭遇します:

  • **アプリ層からのフォーマット済み文字列。**PythonやPHPアプリがSET price "1,500.00"(千の位のカンマ区切り)を呼び出したり、"$9.99"のような通貨文字列を格納したりした場合。
  • 空文字列のプレースホルダー。SET counter 0の代わりにSET counter ""でキーを「初期化」してしまった場合。
  • **間違ったデータ型の混入。**ブール値("true")、JSONブロブ("{\"v\":1}")、または末尾に改行が含まれたシェルのエコー値。
  • 文字列として格納されたシリアライズされたコレクション。"[1,2,3]"のような値は有効な文字列型(WRONGTYPEは発生しない)ですが、解析可能なfloatではありません。

修正1 — 実際に格納されている値を確認する

推測しないこと。まず確認してください:

127.0.0.1:6379> TYPE mykey
string
127.0.0.1:6379> GET mykey
"not-a-number"

TYPEstring以外を返す場合、それはfloat形式の問題ではなく型の問題です。以下の修正はすべて文字列キーにのみ適用されます。

修正2 — 不正な値を上書きする

最もシンプルな修正:実際の数値に置き換えるだけです。

# ゼロにリセット
127.0.0.1:6379> SET price 0
OK
127.0.0.1:6379> INCRBYFLOAT price 1.5
"1.5"

# または特定の値から開始
127.0.0.1:6379> SET price 9.99
OK
127.0.0.1:6379> INCRBYFLOAT price 0.01
"10"

修正3 — SETNXで安全に初期化する

複数のワーカーが同じキーの作成を競合している場合は、SET … NXを使用して既存の有効な値を上書きしないようにします:

127.0.0.1:6379> SET price 0 NX
OK   # キーが存在しない場合のみセット
127.0.0.1:6379> INCRBYFLOAT price 2.50
"2.5"

キーにすでに正常な値が格納されている場合は何もしません。これは高並行カウンターで多くのRedisクライアントが使用するパターンです。

修正4 — アプリケーションコードから書き込む前にバリデーションする

本質的な修正は上流にあります:不正な値がRedisに到達する前に検出してください。

Python (redis-py)

import redis

r = redis.Redis()

def safe_incrbyfloat(key: str, increment: float) -> float:
    current = r.get(key)
    if current is not None:
        try:
            float(current)  # 格納値がおかしい場合はValueErrorを発生させる
        except ValueError:
            r.set(key, 0)  # インクリメント前にリセット
    return r.incrbyfloat(key, increment)

result = safe_incrbyfloat("price", 1.5)
print(result)  # 1.5

Node.js (ioredis)

const Redis = require('ioredis');
const client = new Redis();

async function safeIncrByFloat(key, increment) {
  const current = await client.get(key);
  if (current !== null && isNaN(parseFloat(current))) {
    await client.set(key, 0);
  }
  return client.incrbyfloat(key, increment);
}

const result = await safeIncrByFloat('price', 1.5);
console.log(result); // '1.5'

修正5 — Luaによるアトミックなチェックとインクリメント

上記のPythonとNode.jsの例では、GETINCRBYFLOATの間に競合ウィンドウがあります。これが問題になる場合は、ロジックをLuaスクリプトに移してください——Redisはアトミックに実行します:

local val = redis.call('GET', KEYS[1])
if val == false or tonumber(val) == nil then
  redis.call('SET', KEYS[1], 0)
end
return redis.call('INCRBYFLOAT', KEYS[1], ARGV[1])

直接実行する場合:

redis-cli EVAL "
  local val = redis.call('GET', KEYS[1])
  if val == false or tonumber(val) == nil then
    redis.call('SET', KEYS[1], 0)
  end
  return redis.call('INCRBYFLOAT', KEYS[1], ARGV[1])
" 1 price 1.5

知っておくべきエッジケース

  • シェルechoからの末尾改行。redis-cli SET key $(echo "1.5")"1.5\n"を格納します——Redisはこれを拒否します。printfを使用するか、値を明示的にクォートしてください。
  • ロケール形式の数値。"1.234,56"(ヨーロッパ式)も"1,234.56"(千の位区切りのUS式)も解析されません。格納前にフォーマットを取り除いてください。
  • **NaNとInfinity。**どちらも有効なIEEE 754のfloatですが、Redisは明示的に拒否します。有限の数値文字列のみがstrtod()を通過します。
  • インクリメント値自体。INCRBYFLOATの第2引数も有効なfloatでなければなりませんが、ほとんどのクライアントライブラリはコマンドがRedisに到達する前にこれを検証します。

確認手順

修正を適用した後、以下の手順ですべて正常に動作することを確認してください:

# 1. 格納された値が数値であることを確認
127.0.0.1:6379> GET price
"0"

# 2. INCRBYFLOATがエラーではなく数値を返すことを確認
127.0.0.1:6379> INCRBYFLOAT price 9.99
"9.99"

# 3. 再度インクリメントして累積を確認
127.0.0.1:6379> INCRBYFLOAT price 0.01
"10"

# 4. 最終値をGETして確認
127.0.0.1:6379> GET price
"10"

本番環境でデバッグ中で値に触れられない場合は、この非破壊的なプローブを使用してください:

# 0でのINCRBYFLOATは値を変更せずに格納値をバリデーションする
127.0.0.1:6379> INCRBYFLOAT suspicious_key 0
# エラー → 格納値が有効なfloatではない
# エラーなし → 格納値は正常

クイックリファレンス

  • 有効な値:"0""1.5""-3.14""2e3"".5"
  • 無効な値:"""abc""1,5""$9.99""NaN""Infinity""true"
  • 安全な初期化パターン:最初のINCRBYFLOATの前にSET key 0 NX
  • アトミック性が必要な場合:初期化とインクリメントをLuaスクリプトにまとめる

Related Error Notes