Khắc phục lỗi Redis "ERR invalid expire time" với TTL bằng 0 hoặc số âm

beginner🔴 Redis2026-06-18| Redis (phiên bản 5.0 đến 7.x) chạy trên Linux, macOS hoặc Docker.

Error Message

(error) ERR invalid expire time in 'set' command
#redis#ttl#caching#backend#ioredis

Kịch bản Lỗi

Trong khi xây dựng bộ nhớ đệm phiên (session cache) cho một dịch vụ Node.js, tôi đã gặp phải một lỗi chỉ xuất hiện khi người dùng sắp hết thời hạn đăng nhập. Mã của tôi tính toán TTL (Time To Live) một cách linh hoạt bằng cách lấy dấu thời gian hết hạn của phiên trừ đi thời gian hiện tại. Trong quá trình kiểm tra lưu lượng truy cập cao, mọi thứ có vẻ ổn, nhưng ngay khi một phiên chỉ còn vài mili giây, các nhật ký (logs) bắt đầu tràn ngập thông báo này:

(error) ERR invalid expire time in 'set' command

Bạn có thể dễ dàng kích hoạt lỗi này trong Redis CLI. Nếu bạn cố gắng sử dụng SET với đối số EX (giây) hoặc PX (mili giây) bằng 0 hoặc nhỏ hơn, Redis sẽ từ chối hoàn toàn thao tác này.

# Lệnh này thất bại vì 0 không phải là một số nguyên dương
redis> SET session:user:123 "data" EX 0
(error) ERR invalid expire time in 'set' command

# Các giá trị âm cũng kích hoạt lỗi
redis> SET session:user:123 "data" EX -10
(error) ERR invalid expire time in 'set' command

Phân tích: Tại sao lệnh SET lại nghiêm ngặt

Redis yêu cầu các tùy chọn EXPX phải là các số nguyên dương. Đây là một cái bẫy phổ biến đối với các nhà phát triển vì lệnh EXPIRE độc lập linh hoạt hơn nhiều. Chạy EXPIRE key 0 đơn giản là xóa key đó. Tuy nhiên, SET ... EX là một thao tác nguyên tử (atomic operation). Redis thực thi xác thực nghiêm ngặt hơn ở đây để ngăn các lệnh mơ hồ bị thực thi một phần.

Hầu hết các sự cố xảy ra khi phép tính TTL trông như thế này:

# Ví dụ Python
ttl = target_expiry - current_time
# Nếu current_time >= target_expiry, ttl sẽ bằng 0 hoặc âm
redis_client.set("session_key", "value", ex=ttl)

Thậm chí một sự chậm trễ 5ms trong logic ứng dụng của bạn cũng có thể biến một TTL dương thành số không. Điều này khiến toàn bộ thao tác ghi vào cơ sở dữ liệu của bạn bị thất bại thay vì chỉ hết hạn dữ liệu một cách suôn sẻ.

Sự khác biệt giữa SET EX và EXPIRE

Sẽ hữu ích nếu coi SET ... EX là một lệnh khởi tạo và EXPIRE là một lệnh bảo trì. Vì SET đang cố gắng ghi dữ liệu mới, Redis muốn đảm bảo logic hết hạn có ý nghĩa trước khi cam kết giá trị vào bộ nhớ. Nếu thời gian không hợp lệ, nó sẽ từ chối toàn bộ lệnh. Dữ liệu không được lưu và ứng dụng của bạn có khả năng sẽ ném ra một ngoại lệ chưa được xử lý.

Giải pháp 1: Làm sạch logic TTL của bạn

Cách khắc phục tức thời nhất là đảm bảo TTL của bạn luôn ít nhất là một giây (hoặc một mili giây đối với PX). Nếu kết quả tính toán của bạn bằng 0 hoặc nhỏ hơn, về mặt kỹ thuật dữ liệu đã hết hạn. Trong trường hợp đó, bạn nên bỏ qua hoàn toàn lệnh SET hoặc xóa bất kỳ key hiện có nào để duy trì tính nhất quán.

Triển khai Python (redis-py)

def safe_set(key, value, ttl_seconds):
    # Đảm bảo TTL ít nhất là 1, nếu không thì bỏ qua việc ghi
    if ttl_seconds > 0:
        redis_client.set(key, value, ex=int(ttl_seconds))
    else:
        # Dữ liệu đã cũ; chỉ cần đảm bảo nó đã được xóa
        redis_client.delete(key)

Triển khai Node.js (ioredis)

async function setWithExpiry(key, value, ttlMs) {
    if (ttlMs > 0) {
        await redis.set(key, value, "PX", ttlMs);
    } else {
        // Xử lý trường hợp biên khi phiên hết hạn trong quá trình xử lý
        await redis.del(key);
    }
}

Giải pháp 2: Sử dụng thời gian hết hạn tuyệt đối (EXAT/PXAT)

Nếu bạn đang chạy Redis 6.2 trở lên, bạn có thể tránh hoàn toàn các phép tính TTL bằng cách sử dụng EXAT hoặc PXAT. Các tùy chọn này cho phép bạn cung cấp một dấu thời gian Unix cụ thể (ví dụ: 1735689600) thay vì một khoảng thời gian tương đối. Điều này đáng tin cậy hơn nhiều cho việc quản lý phiên.

# Đặt key hết hạn tại một dấu thời gian cụ thể
redis> SET session:user:123 "data" EXAT 1735689600

Mẹo chuyên nghiệp: Không giống như EX, nếu bạn cung cấp một dấu thời gian trong quá khứ cho EXAT, Redis 6.2+ sẽ trả về OK nhưng sẽ xóa key hiện có hoặc không bao giờ tạo key mới. Hành vi này an toàn hơn nhiều cho các ứng dụng động vì nó ngăn chặn hoàn toàn sự cố "invalid expire time".

Xác minh bản sửa lỗi

Kiểm tra triển khai của bạn với ba kịch bản sau để đảm bảo tính ổn định:

  • Trường hợp bình thường: Cung cấp TTL là 60 giây. Xác minh key tồn tại và TTL key trả về xấp xỉ 60.
  • Trường hợp biên: Ép buộc TTL là 0 hoặc -1 trong mã của bạn. Đảm bảo ứng dụng không bị lỗi và key không tồn tại trong Redis.
  • Đồng bộ hóa đồng hồ: Nếu ứng dụng của bạn và Redis chạy trên các máy chủ khác nhau, hãy sử dụng NTP để giữ cho đồng hồ đồng bộ. Sự chênh lệch 1 giây có thể gây ra rắc rối khi các phiên gần đến giới hạn.

Kết luận

Khi bạn thấy lỗi ERR invalid expire time, đó hầu như luôn là dấu hiệu của một race condition trong phép tính TTL của bạn. Redis SET mong đợi một số dương. Bằng cách thêm một kiểm tra if ttl > 0 đơn giản hoặc chuyển sang EXAT, bạn có thể làm cho lớp bộ nhớ đệm của mình linh hoạt hơn nhiều trước lỗi phiên "giây cuối cùng".

Related Error Notes