Vấn đề: Lúc thấy lúc không
Đây là một hiện tượng kỳ lạ điển hình. Bạn đang rà soát instance Redis của mình để tìm các key đang chiếm dụng RAM. Bạn chạy SCAN, xác định được một mục tiêu và ngay lập tức thử kiểm tra OBJECT ENCODING hoặc TTL của nó. Thay vì nhận được metadata như mong đợi, Redis lại gây trở ngại cho bạn:
(error) ERR no such key
Thật khó chịu vì bạn vừa thấy key đó được liệt kê chỉ vài mili giây trước. Redis CLI tiêu chuẩn thường trả về (nil) hoặc -2 cho các key bị thiếu. Tuy nhiên, các thư viện và proxy như Twemproxy thường chuyển đổi chúng thành lỗi cứng ERR no such key. Điều này xảy ra nếu key hết hạn ngay tại thời điểm thực thi.
Phân tích: Tại sao key biến mất
Thường có ba nguyên nhân chính đứng sau sự biến mất bí ẩn này.
1. Tình trạng tranh chấp (Race Condition - Hết hạn TTL)
Đây là nguyên nhân hàng đầu. Nếu bạn đang kiểm tra các session token hoặc khóa tạm thời (temporary lock) có thời gian sống ngắn (ví dụ: 500ms), key có thể hết hạn giữa lệnh thứ nhất và lệnh thứ hai. Redis rất nhanh, nhưng không phải là tức thời. Độ trễ mạng dù chỉ 10ms cũng có thể biến một key sắp hết hạn thành một ngõ cụt.
2. Các ký tự ẩn và lỗi Encoding
Hãy nhìn kỹ hơn vào chuỗi (string). Nếu bạn sao chép một key từ log, bạn có thể đã lấy nhầm các ký tự ẩn như khoảng trắng thừa, ký tự xuống dòng hoặc các byte null. Với Redis, "user:100" và "user:100\r" là hai thực thể hoàn toàn khác nhau. Nếu code của bạn vô tình thêm một ký tự xuống dòng (carriage return), lệnh TTL "user:100" tiêu chuẩn sẽ luôn thất bại.
3. Logic trong Lua Script
Khi bạn chạy redis.call() bên trong một script Lua, Redis xử lý lỗi rất nghiêm ngặt. Nếu script của bạn mong đợi một key tồn tại nhưng nó đã hết hạn, toàn bộ script có thể bị crash với lỗi no such key. Điều này thường thấy trong các script không kiểm tra sự tồn tại của key trước khi thực hiện các thao tác lấy metadata.
Cách sửa nhanh: Xử lý key bị thiếu
Nếu bạn thấy lỗi này trong CLI, "cách sửa" đơn giản là chấp nhận rằng key đã hết hạn. Đối với các công cụ tự động hóa hoặc giám sát, bạn phải xây dựng khả năng phục hồi tốt hơn. Đừng chạy OBJECT ENCODING một cách mù quáng.
Hãy thử sử dụng một dòng lệnh Lua để lấy metadata một cách an toàn. Điều này đảm bảo cả hai bước kiểm tra đều diễn ra trong cùng một thao tác atomic (nguyên tử):
EVAL "if redis.call('EXISTS', KEYS[1]) == 1 then return redis.call('OBJECT', 'ENCODING', KEYS[1]) else return nil end" 1 my_key
Đối với TTL, hãy nhớ rằng giá trị trả về -2 có nghĩa là key không còn tồn tại. Nếu thư viện ứng dụng của bạn ném ra một ngoại lệ (exception) thay vì trả về -2, bạn nên bắt (catch) lỗi cụ thể đó hoặc bao bọc lệnh gọi trong một bước kiểm tra an toàn.
Cách sửa triệt để: Các mẫu code bền vững
Cải thiện các script Lua của bạn
Đừng bao giờ giả định một đối số tồn tại. Hãy sử dụng redis.call('EXISTS', KEYS[1]) trước khi bạn chạm vào bất kỳ metadata nào. Điều này ngăn script kết thúc sớm và để lại ứng dụng của bạn trong trạng thái không nhất quán.
-- Ví dụ về kiểm tra metadata an toàn
if redis.call("EXISTS", KEYS[1]) == 1 then
return {
redis.call("OBJECT", "ENCODING", KEYS[1]),
redis.call("TTL", KEYS[1])
}
else
return nil
end
Làm sạch tên Key của bạn
Các byte ẩn là một cơn ác mộng khi debug. Điều này thường xảy ra khi các key được tạo từ dữ liệu nhị phân hoặc URL. Nếu một key xuất hiện hai lần trong kết quả SCAN nhưng trông giống hệt nhau, bạn đã tìm thấy một ký tự ẩn.
Mẹo chuyên gia: Tôi đã lãng phí hàng giờ để truy tìm các key "mất tích" chỉ để tìm thấy một ký tự \n ở cuối trong log Python. Hãy sử dụng một công cụ như ToolCraft's Base64 Encoder để kiểm tra chuỗi thô. Bằng cách mã hóa tên key sang Base64, bạn có thể phát hiện các byte ẩn mà terminal của bạn không hiển thị.
Kiểm tra cấu hình Redis Proxy
Các proxy như Envoy hoặc Nutcracker đôi khi cố gắng "giúp đỡ" quá mức. Chúng có thể chuyển đổi phản hồi nil thành một lỗi để cảnh báo bạn về khả năng bị cache miss. Nếu hành vi này làm hỏng ứng dụng của bạn, hãy kiểm tra cấu hình proxy hoặc cập nhật logic ứng dụng để xử lý các chuỗi lỗi cụ thể này một cách nhẹ nhàng.
Xác minh: Cách xác nhận lỗi đã được sửa
Giả lập một tình trạng tranh chấp (race condition) để đảm bảo logic mới của bạn hoạt động ổn định:
- Tạo một key ngắn hạn:
SET temp_key "test" EX 2(hết hạn sau 2 giây). - Chờ một lát: Đợi 3 giây.
- Chạy kiểm tra: Thực thi script Lua hoặc wrapper đã cập nhật của bạn.
- Xác minh đầu ra: Bạn sẽ thấy kết quả
(nil)được kiểm soát hoặc một giá trịnulltùy chỉnh thay vì bị crash với lỗiERR no such key.
Để dập tắt các vấn đề về encoding, hãy chạy redis-cli --raw SCAN 0. Lệnh này giữ nguyên các ký tự ẩn lén lút đó, giúp bạn dễ dàng xác định và loại bỏ chúng khỏi logic tạo key của mình.

