Cách khắc phục lỗi Redis 'ERR string exceeds maximum allowed size'

intermediate🔴 Redis2026-07-28| Redis (mọi phiên bản), Linux, Docker, Cloud-managed Redis (AWS ElastiCache, Azure Cache for Redis)

Error Message

ERR string exceeds maximum allowed size
#redis#giới-hạn-chuỗi#tối-ưu-hiệu-năng#caching

Khi bộ nhớ đệm (cache) của bạn chạm giới hạn

Hãy tưởng tượng bạn đang đẩy một gói dữ liệu lớn vào bộ nhớ đệm, và bỗng nhiên mọi thứ dừng lại. Nhật ký ứng dụng của bạn bắt đầu báo lỗi cụ thể như sau:

(error) ERR string exceeds maximum allowed size

Bạn thường sẽ thấy lỗi này trong các thao tác SET, APPEND, hoặc GETSET. Nó có nghĩa là giá trị bạn đang cố gắng lưu trữ quá lớn để Redis có thể xử lý trong một khóa (key) duy nhất.

Tại sao điều này lại xảy ra?

Các chuỗi (string) trong Redis có giới hạn cứng là 512 Megabytes (MB). Đây không phải là một cài đặt mà bạn có thể thay đổi trong redis.conf; nó được mã hóa cứng (hard-coded) vào công cụ. Giới hạn này tồn tại vì một lý do chính đáng. Việc xử lý các khối bộ nhớ lớn và liên tục rất tốn kém. Các thao tác trên một chuỗi 500MB có thể làm tắc nghẽn vòng lặp sự kiện đơn luồng (single-threaded event loop) trong 200ms hoặc hơn, khiến độ trễ của ứng dụng tăng vọt.

Mặc dù 512MB nghe có vẻ là một không gian rộng rãi, nhưng bạn có thể chạm tới giới hạn này nhanh hơn bạn nghĩ. Các nguyên nhân phổ biến bao gồm:

  • Lưu trữ các khối JSON khổng lồ (ví dụ: bản xuất danh mục sản phẩm dung lượng 600MB).
  • Lưu bộ nhớ đệm các tài nguyên được mã hóa Base64 như hình ảnh độ phân giải cao hoặc các tệp PDF trên 50 trang.
  • Gộp các nhật ký (logs) vào một khóa duy nhất bằng lệnh APPEND trong nhiều ngày.

Các chiến lược hiệu quả để khắc phục lỗi

Vì bạn không thể tăng giới hạn này, bạn phải thay đổi cách ứng dụng chuẩn bị dữ liệu. Dưới đây là những cách hiệu quả nhất để giữ dung lượng dưới mức tối đa 512MB.

1. Nén dữ liệu của bạn

Dữ liệu dạng văn bản như JSON hoặc HTML thường chứa nhiều phần dư thừa. Việc nén thường có thể thu nhỏ gói dữ liệu từ 600MB xuống còn 60MB—giảm tới 90%. Hãy sử dụng một thư viện nhanh như Gzip hoặc Zstd trước khi gửi dữ liệu đến Redis.

Dưới đây là một ví dụ nhanh bằng Python sử dụng Gzip:

import redis
import gzip

r = redis.Redis(host='localhost', port=6379)
# Giả lập một chuỗi vượt quá giới hạn
large_data = "{" + "a" * 550000000 + "}" 

# Nén dữ liệu trước khi lưu trữ
compressed_payload = gzip.compress(large_data.encode('utf-8'))

# Lưu trữ khối dữ liệu nhị phân vào Redis
r.set('my_compressed_key', compressed_payload)

2. Chia nhỏ dữ liệu thành các phần (Chunks)

Nếu dữ liệu của bạn vẫn quá lớn sau khi nén, bạn cần phải chia nhỏ nó ra. Hãy chia chuỗi thành các phân đoạn nhỏ hơn và lưu trữ chúng qua nhiều khóa bằng cách sử dụng hậu tố như :part:1 hoặc :part:2.

Logic Node.js này minh họa cho khái niệm đó:

const redis = require('redis');
const client = redis.createClient();

async function saveInChunks(baseKey, largeString) {
    const CHUNK_SIZE = 100 * 1024 * 1024; // Sử dụng các phần 100MB an toàn
    const totalChunks = Math.ceil(largeString.length / CHUNK_SIZE);

    for (let i = 0; i < totalChunks; i++) {
        const chunk = largeString.substring(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
        await client.set(`${baseKey}:part:${i}`, chunk);
    }
    // Lưu trữ metadata để bạn biết cần lấy bao nhiêu phần sau này
    await client.set(`${baseKey}:meta`, JSON.stringify({ total: totalChunks }));
}

3. Chuyển sang sử dụng Redis Hashes

Hãy ngừng coi Redis như một hệ thống tệp phẳng cho các đối tượng JSON khổng lồ. Redis Hashes tiết kiệm bộ nhớ hiệu quả hơn đáng kể. Thay vì lưu một chuỗi lớn, hãy chia các trường JSON của bạn thành các trường Hash riêng lẻ. Điều này cho phép bạn cập nhật hoặc truy xuất các trường cụ thể mà không cần đụng đến toàn bộ khối dữ liệu 500MB.

# Thay vì lưu trữ một khối dữ liệu khổng lồ:
SET user:data "{ 'bio': '...', 'history': '...', 'settings': '...' }"

# Sử dụng HSET để giữ các phân đoạn dễ quản lý hơn:
HSET user:data bio "...text..." history "...text..." settings "...text..."

4. Chuyển sang lưu trữ đối tượng (Object Storage)

Redis được xây dựng cho tốc độ, không phải để lưu trữ khối lượng lớn. Nếu bạn thường xuyên chạm trán giới hạn 512MB, Redis có thể không phải là công cụ phù hợp cho tác vụ cụ thể đó. Một kiến trúc có khả năng mở rộng tốt hơn bao gồm:

  • Tải tệp lớn lên AWS S3 hoặc Google Cloud Storage.
  • Chỉ lưu trữ signed URL hoặc Object Key trong Redis để tra cứu nhanh.

Xác nhận bản sửa lỗi của bạn

Sau khi bạn đã thực hiện sửa lỗi, hãy xác minh kết quả trong Redis CLI. Sử dụng STRLEN để kiểm tra độ dài thô hoặc MEMORY USAGE để xem tác động thực tế lên RAM.

# Kiểm tra độ dài byte của chuỗi
STRLEN my_large_key

# Kiểm tra mức tiêu thụ RAM thực tế bao gồm cả chi phí nội bộ
MEMORY USAGE my_large_key

Nếu STRLEN trả về giá trị thấp hơn nhiều so với 536.870.912 (tương đương với 512MB tính bằng byte), bạn đã an toàn.

Tránh các nút thắt cổ chai trong tương lai

Các khóa lớn gây ra các sự kiện "Stop-the-world". Khi Redis xử lý một khóa khổng lồ, nó sẽ bỏ qua tất cả các yêu cầu khác cho đến khi hoàn thành. Điều này dẫn đến tình trạng hết thời gian chờ (timeout) và gây khó chịu cho người dùng. Để ngăn điều này xảy ra lần nữa:

  • Theo dõi kích thước gói dữ liệu (Payload): Thiết lập cảnh báo trong APM của bạn (như Datadog hoặc New Relic) cho bất kỳ thao tác ghi Redis nào vượt quá 50MB.
  • Xác thực dữ liệu trước: Nếu bạn đang lưu bộ nhớ đệm cho các tệp cấu hình lớn, tôi thường sử dụng YAML ↔ JSON Converter trên ToolCraft. Đây là một cách tiện lợi để đảm bảo tệp của bạn không bị sai định dạng trước khi đưa vào cache. Vì nó chạy trong trình duyệt, dữ liệu của bạn luôn được giữ riêng tư.
  • Rào chắn phía máy khách (Client-side): Thêm một bước kiểm tra trong mã nguồn của bạn để báo lỗi nếu giá trị vượt quá 256MB. Thà thất bại sớm ở lớp ứng dụng còn hơn là làm tắc nghẽn toàn bộ cơ sở dữ liệu của bạn.

Related Error Notes