TL;DR
Redis báo lỗi ERR value is not a valid float khi INCRBYFLOAT gặp một key không thể parse thành số — ví dụ chuỗi rỗng, "1,5" dùng dấu phẩy thay dấu chấm thập phân, hoặc "NaN". Cách sửa: đảm bảo key chứa chuỗi số hợp lệ trước khi gọi INCRBYFLOAT.
Nguyên nhân gây ra lỗi
Thử lệnh sau và bạn sẽ thấy lỗi ngay lập tức:
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
Chuỗi rỗng và số định dạng theo locale cũng gặp lỗi tương tự:
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" # dùng dấu phẩy thập phân — không hợp lệ trong Redis
OK
127.0.0.1:6379> INCRBYFLOAT ratio 0.25
(error) ERR value is not a valid float
Bên trong, Redis gọi strtod() để parse giá trị đang lưu. Hàm này chấp nhận định dạng giống literal double trong C: dấu tùy chọn, chữ số, dấu chấm, số mũ — nên 1.5, -3e2, và .75 đều hợp lệ. Mọi định dạng khác đều bị từ chối.
Bốn cách lỗi này lọt vào môi trường production
Hầu hết các team gặp lỗi này qua một trong những tình huống sau:
- Chuỗi đã định dạng từ tầng ứng dụng. Ứng dụng Python hoặc PHP gọi
SET price "1,500.00"với dấu phẩy phân cách hàng nghìn, hoặc lưu chuỗi tiền tệ như"$9.99". - Placeholder chuỗi rỗng. Ai đó viết
SET counter ""để "khởi tạo" key thay vì dùngSET counter 0. - Kiểu dữ liệu sai bị lọt vào. Một giá trị boolean (
"true"), blob JSON ("{\"v\":1}"), hoặc giá trị được echo từ shell kèm ký tự xuống dòng ở cuối. - Collection được serialize lưu dưới dạng chuỗi. Giá trị như
"[1,2,3]"là kiểu string hợp lệ (không gây lỗiWRONGTYPE), nhưng vẫn không thể parse thành float.
Cách sửa 1 — Kiểm tra giá trị đang lưu
Đừng đoán mò. Hãy xem trước:
127.0.0.1:6379> TYPE mykey
string
127.0.0.1:6379> GET mykey
"not-a-number"
Nếu TYPE trả về giá trị khác string, bạn đang gặp vấn đề sai kiểu dữ liệu — không phải lỗi định dạng float. Các cách sửa còn lại bên dưới chỉ áp dụng cho string key.
Cách sửa 2 — Ghi đè giá trị sai
Cách đơn giản nhất: thay thế bằng một số hợp lệ.
# Đặt lại về 0
127.0.0.1:6379> SET price 0
OK
127.0.0.1:6379> INCRBYFLOAT price 1.5
"1.5"
# Hoặc bắt đầu từ một giá trị cụ thể
127.0.0.1:6379> SET price 9.99
OK
127.0.0.1:6379> INCRBYFLOAT price 0.01
"10"
Cách sửa 3 — Khởi tạo an toàn với SETNX
Nhiều worker cùng tranh nhau tạo một key? Dùng SET … NX để tránh ghi đè lên giá trị hợp lệ đã tồn tại:
127.0.0.1:6379> SET price 0 NX
OK # chỉ set nếu key chưa tồn tại
127.0.0.1:6379> INCRBYFLOAT price 2.50
"2.5"
Lệnh này không làm gì nếu key đã chứa giá trị hợp lệ. Đây là pattern hầu hết các Redis client dùng trong các bộ đếm có độ đồng thời cao.
Cách sửa 4 — Validate trước khi ghi từ code ứng dụng
Cách sửa thực sự nằm ở upstream: bắt giá trị sai trước khi chúng đến được 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) # raise ValueError nếu giá trị đang lưu không hợp lệ
except ValueError:
r.set(key, 0) # đặt lại trước khi tăng
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'
Cách sửa 5 — Check-and-increment nguyên tử với Lua
Các ví dụ Python và Node.js ở trên có khoảng thời gian race giữa GET và INCRBYFLOAT. Nếu điều đó quan trọng, hãy chuyển logic vào Lua script — Redis thực thi nó theo kiểu nguyên tử:
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])
Chạy trực tiếp:
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
Các trường hợp đặc biệt cần lưu ý
- Ký tự xuống dòng từ shell echo.
redis-cli SET key $(echo "1.5")lưu"1.5\n"— Redis từ chối. Dùngprintfhoặc đặt giá trị trong dấu ngoặc kép tường minh. - Số định dạng theo locale. Cả
"1.234,56"(kiểu châu Âu) lẫn"1,234.56"(kiểu Mỹ có dấu phân cách hàng nghìn) đều không parse được. Hãy bỏ định dạng trước khi lưu. - NaN và Infinity. Cả hai đều là float hợp lệ theo IEEE 754 nhưng Redis từ chối tường minh. Chỉ các chuỗi số hữu hạn mới qua được
strtod(). - Bản thân giá trị tăng. Tham số thứ hai của
INCRBYFLOATcũng phải là float hợp lệ — tuy nhiên hầu hết các thư viện client đã bắt lỗi này trước khi lệnh đến Redis.
Các bước xác nhận
Sau khi áp dụng cách sửa, chạy chuỗi lệnh sau để xác nhận mọi thứ hoạt động:
# 1. Xác nhận giá trị đang lưu là số
127.0.0.1:6379> GET price
"0"
# 2. INCRBYFLOAT phải trả về một số, không phải lỗi
127.0.0.1:6379> INCRBYFLOAT price 9.99
"9.99"
# 3. Tăng thêm để kiểm tra tính tích lũy
127.0.0.1:6379> INCRBYFLOAT price 0.01
"10"
# 4. GET giá trị cuối cùng để chắc chắn
127.0.0.1:6379> GET price
"10"
Đang debug trên production mà không thể chỉnh sửa giá trị? Dùng cách thăm dò không phá hủy này:
# INCRBYFLOAT với 0 vẫn validate giá trị đang lưu mà không thay đổi nó
127.0.0.1:6379> INCRBYFLOAT suspicious_key 0
# có lỗi → giá trị đang lưu không phải float hợp lệ
# không có lỗi → giá trị đang lưu bình thường
Tóm tắt nhanh
- Giá trị hợp lệ:
"0","1.5","-3.14","2e3",".5" - Giá trị không hợp lệ:
"","abc","1,5","$9.99","NaN","Infinity","true" - Pattern khởi tạo an toàn:
SET key 0 NXtrước lầnINCRBYFLOATđầu tiên - Cần tính nguyên tử: đặt init + increment trong Lua script

