Phần 1 của sê-ri đã nói Redis không phải cơ sở dữ liệu để truy vấn. Bài này dựng thử một ứng dụng thật trên Redis rồi đo xem điều đó nghĩa là gì cụ thể.
Cùng 100.000 đơn hàng, hai cách lưu
Trong Redis, mỗi đơn là một Hash, cộng hai tập chỉ mục ngược:
HSET dh:12345 khach 42 trang_thai xong tong 123.45 ngay 2026-03-15
SADD idx:khach:42 12345
SADD idx:tt:xong 12345
Redis 101.004 khoá 19,71 MB RAM
PostgreSQL bảng + 3 chỉ mục 8,81 MB đĩa
Redis tốn gấp 2,2 lần chỗ — và đó là RAM chứ không phải đĩa.
Bốn truy vấn, bốn kết cục
| Truy vấn | Redis | PostgreSQL |
|---|---|---|
| Đếm đơn của một khách | 0,145 ms | 0,334 ms |
| Tổng tiền một trạng thái | 48,3 ms | 8,9 ms |
| Gom nhóm cả bốn trạng thái | 203,8 ms | 8,9 ms |
| Lọc trạng thái + khoảng ngày, gom theo khách, sắp xếp, top 5 | không làm được | 6,5 ms |
Dòng đầu Redis thắng: 0,145 so với 0,334 ms, nhanh hơn 2,3 lần. Đó là điều nó được dựng để làm — tra một khoá đã biết.
Từ dòng hai trở xuống nó thua 5 đến 23 lần, hoặc không làm được.
Vì sao khoảng cách lớn đến vậy
PostgreSQL đọc một chỉ mục và quét tuần tự vài nghìn dòng nằm liền nhau trên đĩa, tất cả trong bộ đệm.
Redis phải: lấy danh sách id từ tập chỉ mục, rồi với mỗi id gọi HGET để lấy giá trị. 24.844 đơn cho một trạng thái là 24.844 lần tra bảng băm rải rác trong bộ nhớ.
Và chú ý dòng ba so với dòng hai: bốn trạng thái tốn 203,8 ms, đúng bằng bốn lần một trạng thái. Không có tối ưu hoá nào — không quét chung, không tái sử dụng, không song song. Mỗi lần thêm một chiều là nhân lên.
PostgreSQL làm cả bốn nhóm trong cùng một lần quét: 8,9 ms cho một nhóm và 8,9 ms cho bốn nhóm.
Và 203,8 ms đó chặn cả máy chủ
Con số 203,8 ms là thời gian một script Lua chạy. Như đo ở phần 14, đó cũng chính xác là thời gian mọi khách hàng khác phải chờ.
Nghĩa là mỗi lần ai đó mở trang báo cáo, toàn bộ Redis đứng yên một phần năm giây. Với ứng dụng có mười người dùng xem báo cáo, đó là hai giây mỗi phút mà không ai được phục vụ.
Cách tránh duy nhất là làm việc gom nhóm ở phía ứng dụng — nhưng khi đó bạn phải kéo 24.844 bản ghi qua mạng, và như đo ở phần 30, đó là bộ đệm đầu ra của máy chủ.
Chỉ mục phải tự duy trì
Ba lệnh cho mỗi đơn thêm mới. Và khi đơn đổi trạng thái:
SREM idx:tt:moi 12345
SADD idx:tt:xong 12345
HSET dh:12345 trang_thai xong
Ba lệnh nữa, và chúng không nằm trong một giao dịch với việc ghi dữ liệu — trừ khi bạn gói vào Lua, và khi đó bạn quay lại vấn đề chặn.
Một lỗi giữa chừng để lại chỉ mục sai: đơn nằm trong hai tập trạng thái, hoặc không nằm trong tập nào. Không có gì phát hiện điều đó ngoài việc bạn tự viết một job đối soát.
PostgreSQL cập nhật chỉ mục trong cùng giao dịch với dòng dữ liệu. Bạn không thể làm sai điều đó.
Vậy khi nào Redis làm kho chính được
Ba điều kiện, và cần cả ba:
- Mọi truy vấn đều theo khoá đã biết. Không lọc, không gom nhóm, không sắp xếp theo trường tuỳ ý.
- Dữ liệu vừa trong RAM, kể cả khi tăng gấp đôi, và bạn chấp nhận trả tiền cho RAM đó.
- Bạn đã đo mô hình bền và chấp nhận nó. Từ phần 19:
everysecmất tối đa một giây khi mất điện; từ phần 21: bản sao chậm thì mất đúng phần chậm đó.
Những hệ thống hợp: bảng phiên đăng nhập, hàng đợi công việc, bảng xếp hạng thời gian thực, bộ đếm.
Những hệ thống không hợp: bất cứ thứ gì có báo cáo, bất cứ thứ gì có màn hình quản trị lọc theo nhiều trường, bất cứ thứ gì kế toán.
Mẫu thực dụng: cả hai
Cách gần như luôn đúng là dùng cả hai, mỗi thứ cho việc nó giỏi:
- PostgreSQL giữ dữ liệu gốc, chỉ mục, ràng buộc, giao dịch, báo cáo.
- Redis giữ những gì cần đọc nhanh theo khoá: phiên, bộ đệm kết quả, bộ đếm, hàng đợi.
Điều quan trọng: Redis giữ thứ dựng lại được từ PostgreSQL. Khi Redis mất sạch, hệ thống chậm đi một lúc rồi tự hồi phục.
Đây chính là câu hỏi kiểm tra ở phần 1: "Nếu toàn bộ Redis biến mất ngay bây giờ, cái gì không dựng lại được?" Với kiến trúc này, câu trả lời là "không có gì".
Còn RedisJSON và RediSearch
Redis Stack thêm mô-đun cho phép lập chỉ mục thứ cấp và truy vấn thật — nó giải quyết phần lớn những gì đo ở trên.
Nhưng nó là một sản phẩm khác với giấy phép khác, và nó không có trong ảnh redis chính thức. Nếu bạn cần truy vấn, cân nhắc nó thay vì tự dựng chỉ mục bằng tập hợp — và cân nhắc luôn cả việc dùng một cơ sở dữ liệu vốn đã làm việc đó.
Thử ba mươi giây
Kiểm xem bạn có đang tự dựng chỉ mục trong Redis không:
redis-cli --scan --count 500 | head -3000 | \
sed 's/[0-9][0-9]*/N/g' | sort | uniq -c | sort -rn | head -15
Thấy mẫu như idx:*, by-*, *:set, *:list với số lượng lớn là dấu hiệu bạn đang mô phỏng chỉ mục của cơ sở dữ liệu.
Câu hỏi tiếp theo: ai giữ chúng đồng bộ, và bạn đã bao giờ đối soát chưa? Với gần như mọi hệ thống tôi từng nhìn, câu trả lời cho vế sau là chưa — và khi đối soát lần đầu, luôn có sai lệch.
Phần sau: Redis so với Memcached — đo cùng một bài toán bộ đệm.