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"
TYPEがstring以外を返す場合、それは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の例では、GETとINCRBYFLOATの間に競合ウィンドウがあります。これが問題になる場合は、ロジックを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スクリプトにまとめる

