Tại sao lỗi này xảy ra
Bạn có thể đang chạy lệnh SETBIT hoặc GETBIT để theo dõi hoạt động người dùng hoặc feature flag thì Redis đột ngột chặn thao tác. Thông báo lỗi ERR bit offset is not an integer or out of range là cách Redis báo rằng giá trị offset của bạn không hợp lệ về mặt toán học hoặc vượt quá giới hạn kiến trúc của engine.
Vì Redis Bitmaps về mặt kỹ thuật được lưu trữ dưới dạng chuỗi (strings), chúng kế thừa giới hạn kích thước chuỗi tối đa là 512MB. Ràng buộc này quyết định chính xác số lượng bit bạn có thể thao tác trong một key duy nhất.
Ba nguyên nhân chính
Hầu hết các nhà phát triển gặp phải rào cản này vì một trong ba lý do sau. Xác định nguyên nhân của bạn là bước đầu tiên để khắc phục.
1. Giới hạn cứng 512MB
Chuỗi Redis không được vượt quá 512MB. Vì một byte chứa 8 bit, phép tính rất đơn giản: 512 × 1024 × 1024 × 8 = 4.294.967.296 bit. Điều này có nghĩa là offset tối đa cho phép của bạn là 4.294.967.295 (2^32 - 1). Nếu ứng dụng của bạn cố gắng sử dụng ID người dùng là 5 tỷ làm offset, Redis sẽ từ chối ngay lập tức.
2. Kiểu dữ liệu không hợp lệ
Offset phải là một số nguyên không âm. Mã nguồn của bạn có thể vô tình truyền vào một số thực dấu phẩy động, giá trị null hoặc một số nguyên âm. Ví dụ, nếu một phép tính trả về 1024.5 hoặc NaN, Redis sẽ không biết bit nào cần thay đổi và sẽ ném ra lỗi này.
3. Lỗi cấp phát bộ nhớ
Khi bạn đặt một bit ở offset cao, Redis phải cấp phát toàn bộ bộ nhớ dẫn đến bit đó. Nếu bạn chạy SETBIT mykey 2000000000 1 trên một key mới, Redis sẽ ngay lập tức cố gắng chiếm khoảng 250MB RAM. Nếu máy chủ của bạn sắp hết bộ nhớ hoặc đạt giới hạn maxmemory trong redis.conf, việc cấp phát sẽ thất bại, đôi khi biểu hiện dưới dạng lỗi out-of-range.
Khắc phục sự cố ngay lập tức
Kiểm tra Offset thủ công
Kiểm tra xem offset của bạn có nằm trong giới hạn 4,29 tỷ hay không bằng cách sử dụng redis-cli. Việc so sánh giúp xác định vấn đề nằm ở chính giá trị đó hay ở logic ứng dụng của bạn.
# Lệnh này hoạt động: offset 1MB
SETBIT login_tracker 1048576 1
# Lệnh này thất bại: Vượt quá giới hạn 2^32 - 1
SETBIT login_tracker 5000000000 1
Làm sạch dữ liệu đầu vào
Luôn ép kiểu offset của bạn thành số nguyên tuyệt đối trước khi gửi tới thư viện client. Nếu bạn đang sử dụng JavaScript, Math.floor() hoặc parseInt() là lựa chọn phù hợp. Trong Python, hãy đảm bảo bạn không truyền một biến float từ một phép chia.
Giải pháp lâu dài
1. Triển khai Bitmap Sharding
Nếu ID của bạn vượt quá 4,2 tỷ, bạn không thể sử dụng một key duy nhất. Thay vào đó, hãy chia nhỏ dữ liệu của bạn trên nhiều key. Kỹ thuật này, được gọi là sharding, giúp giữ cho các key riêng lẻ luôn nhỏ gọn và dễ quản lý.
Dưới đây là một mô hình logic đơn giản:
# Ví dụ: ID mục tiêu là 5.000.000.000
# Chúng ta muốn 1 triệu bit mỗi shard
ID = 5000000000
SHARD_SIZE = 1000000
shard_number = ID // SHARD_SIZE
local_offset = ID % SHARD_SIZE
# Lệnh kết quả: SETBIT activity:shard:5000 0 1
redis.setbit(f"activity:shard:{shard_number}", local_offset, 1)
2. Chuyển sang Sets cho dữ liệu thưa thớt
Bạn có đang sử dụng offset cao chỉ cho một vài người dùng không? Bitmaps rất tốn bộ nhớ đối với dữ liệu thưa thớt. Thiết lập bit 4.000.000.000 tiêu tốn 500MB RAM ngay cả khi chỉ có một bit được đặt. Nếu dữ liệu của bạn nằm rải rác, hãy sử dụng Redis Set (SADD) để thay thế. Nó chỉ lưu trữ các ID thực tế mà bạn cung cấp, giúp tiết kiệm một lượng lớn bộ nhớ.
3. Theo dõi áp lực bộ nhớ
Kiểm tra tình trạng bộ nhớ hiện tại để xem Redis còn dung lượng để mở rộng hay không. Chạy lệnh INFO memory trong terminal của bạn. Hãy chú ý cụ thể đến used_memory_human và maxmemory_human. Nếu bạn sắp chạm giới hạn, Redis có thể từ chối mở rộng chuỗi bitmap sang một offset mới cao hơn.
Xác minh cuối cùng
Để đảm bảo việc sửa lỗi hiệu quả, hãy xác minh kiểu key và phạm vi. Sử dụng TYPE <key> để xác nhận đó là một chuỗi. Sau đó, chạy STRLEN <key> để xem nó hiện đang chiếm bao nhiêu byte. Độ dài 536.870,912 byte có nghĩa là bạn đã chạm trần tuyệt đối. Nếu đạt đến điểm đó, sharding không còn là tùy chọn nữa—đó là một yêu cầu bắt buộc.

