Chuyện gì vừa xảy ra?
Bạn chạy lệnh INCR, INCRBY, DECR, hoặc DECRBY và Redis trả về:
(error) ERR value is not an integer or out of range
Các lệnh counter của Redis chỉ hoạt động khi giá trị được lưu trông giống một số nguyên thập phân thuần túy — không phải float, không phải JSON blob, không phải chuỗi rỗng. Ngay khi có thứ gì khác nằm dưới key đó, lệnh sẽ dừng ngay lập tức.
Tái hiện lỗi trong 30 giây
# Lưu một số float
127.0.0.1:6379> SET page_views 3.5
OK
127.0.0.1:6379> INCR page_views
(error) ERR value is not an integer or out of range
# Lưu một chuỗi ký tự
127.0.0.1:6379> SET page_views "hello"
OK
127.0.0.1:6379> INCR page_views
(error) ERR value is not an integer or out of range
# Lưu chuỗi rỗng
127.0.0.1:6379> SET page_views ""
OK
127.0.0.1:6379> INCR page_views
(error) ERR value is not an integer or out of range
# Số nguyên vượt quá phạm vi signed 64-bit
127.0.0.1:6379> SET big_num 9999999999999999999999
OK
127.0.0.1:6379> INCR big_num
(error) ERR value is not an integer or out of range
Chẩn đoán key trước tiên
Chưa cần làm gì vội. Hãy bắt đầu bằng cách kiểm tra chính xác những gì đang được lưu:
127.0.0.1:6379> TYPE your_key
string
127.0.0.1:6379> GET your_key
"3.5"
127.0.0.1:6379> STRLEN your_key
(integer) 3
Đây là những nguyên nhân thường gặp nhất:
- Một số float như
"1.0"hoặc"0.5"— code ứng dụng serialize một số mà không cắt bỏ phần thập phân - Một JSON blob như
"{\"count\": 1}"— ai đó lưu cả object thay vì trích xuất trường số nguyên - Chuỗi rỗng
""— key được khởi tạo mà không có giá trị - Một số nằm ngoài phạm vi signed 64-bit (
-9223372036854775808đến9223372036854775807) - Một key cũ còn sót lại từ mô hình dữ liệu trước, nay được dùng lại làm counter
Sửa nhanh: đặt lại key về số nguyên hợp lệ
Cần counter hoạt động ngay lập tức? Ghi đè giá trị:
# Kiểm tra giá trị hiện tại
127.0.0.1:6379> GET page_views
"3.5"
# Ghi đè bằng số nguyên gần nhất
127.0.0.1:6379> SET page_views 3
OK
# Bây giờ INCR hoạt động được rồi
127.0.0.1:6379> INCR page_views
(integer) 4
Muốn bắt đầu lại từ đầu? Chỉ cần xóa key và gọi INCR trực tiếp. Redis tự khởi tạo key bị thiếu về 0 trước khi tăng:
127.0.0.1:6379> DEL page_views
(integer) 1
127.0.0.1:6379> INCR page_views
(integer) 1
Xử lý nguyên nhân gốc rễ trong code ứng dụng
Trường hợp 1: Code của bạn ghi giá trị float thay vì số nguyên
Float là thủ phạm phổ biến nhất. Python, JavaScript, và một số ngôn ngữ khác có thể serialize một số như 1 thành "1.0" mà không có cảnh báo nào — và Redis từ chối tăng giá trị đó.
# Python — sai
import redis
r = redis.Redis()
r.set('page_views', 1.0) # Lưu "1.0", không phải "1"
r.incr('page_views') # Lỗi
# Python — đúng
r.set('page_views', int(1.0)) # Lưu "1"
r.incr('page_views') # Hoạt động
// Node.js (ioredis) — sai
await redis.set('page_views', parseFloat('3.5')); // Lưu "3.5"
// Node.js — đúng
await redis.set('page_views', Math.floor(3.5)); // Lưu "3"
await redis.incr('page_views'); // Hoạt động
Trường hợp 2: Bạn đang lưu một JSON object và cố tăng giá trị của một trường bên trong
Redis string không hỗ trợ cập nhật từng phần. Hãy lưu counter dưới dạng hash field riêng biệt và dùng HINCRBY thay thế:
# Lưu từng trường riêng biệt dưới dạng hash
127.0.0.1:6379> HSET user:42 page_views 10 login_count 3
(integer) 2
# Tăng một trường theo kiểu atomic
127.0.0.1:6379> HINCRBY user:42 page_views 1
(integer) 11
# Giảm giá trị
127.0.0.1:6379> HINCRBY user:42 login_count -1
(integer) 2
Trường hợp 3: Counter kiểu float — dùng INCRBYFLOAT thay thế
Đang theo dõi điểm số, tỷ lệ, hoặc bất kỳ giá trị nào cần độ chính xác thập phân? Dùng INCRBYFLOAT. Giá trị được lưu vẫn phải là số, nhưng phần thập phân được chấp nhận:
127.0.0.1:6379> SET score 10.5
OK
127.0.0.1:6379> INCRBYFLOAT score 0.5
"11"
127.0.0.1:6379> INCRBYFLOAT score 1.25
"12.25"
Lưu ý một điểm: sau đủ nhiều lần gọi INCRBYFLOAT, giá trị được lưu sẽ chứa dấu thập phân. Nếu sau đó chạy INCR thuần trên key đó sẽ bị lỗi. Hãy tách riêng key float và key số nguyên.
Trường hợp 4: Race condition khi khởi tạo key
Hai tiến trình cùng khởi tạo một key là race condition kinh điển. Một tiến trình ghi giá trị không phải số nguyên; tiến trình kia ngay lập tức gọi INCR và gặp lỗi. Cách sửa rất đơn giản — hãy để INCR tự xử lý việc khởi tạo:
# Đừng làm thế này (race condition)
IF key not exists:
SET counter 0
INCR counter
# Hãy làm thế này — INCR là atomic và tự khởi tạo từ 0 nếu key chưa tồn tại
INCR counter
Xác nhận đã sửa xong
# Kiểm tra giá trị là chuỗi số nguyên hợp lệ
127.0.0.1:6379> GET page_views
"4"
# INCR phải trả về số nguyên tiếp theo
127.0.0.1:6379> INCR page_views
(integer) 5
# DECR cũng phải hoạt động
127.0.0.1:6379> DECR page_views
(integer) 4
# INCRBY và DECRBY với bước nhảy
127.0.0.1:6379> INCRBY page_views 10
(integer) 14
127.0.0.1:6379> DECRBY page_views 3
(integer) 11
Cả bốn lệnh đều chạy không lỗi? Key đã sạch.
Thêm kiểm tra bảo vệ trong code trước khi tăng giá trị
Counter trên môi trường production xứng đáng có một lớp bảo vệ. Hàm helper Python này xác thực giá trị được lưu trước, đặt lại về 0 nếu bị hỏng, và ghi log cảnh báo để bạn biết điều đó đã xảy ra:
# Python helper
def safe_incr(r, key):
val = r.get(key)
if val is not None:
try:
int(val) # Sẽ raise nếu không phải chuỗi số nguyên hợp lệ
except (ValueError, TypeError):
# Đặt lại về 0 và ghi log cảnh báo
r.set(key, 0)
print(f"WARNING: reset corrupt counter key '{key}' (was: {val})")
return r.incr(key)
Tham khảo nhanh: các lệnh bị ảnh hưởng
INCR key— tăng thêm 1INCRBY key amount— tăng thêm amount (số nguyên)DECR key— giảm đi 1DECRBY key amount— giảm đi amount (số nguyên)
Cả bốn lệnh đều yêu cầu giá trị phải là biểu diễn chuỗi của số nguyên signed 64-bit. INCRBYFLOAT là ngoại lệ duy nhất — nó chấp nhận số thập phân, nhưng nếu dùng lẫn lộn với các lệnh số nguyên trên cùng một key, sớm muộn bạn cũng sẽ quay lại gặp lỗi này.

