Redis single-threaded nghĩa là gì khi có I/O threads? Cơ bản
Thời gian đọc ước tính: 7 phút
Vấn đề
Phần tiêu đề “Vấn đề”Endpoint lấy profile bằng GET thường rất nhanh. Một hôm p99 tăng mạnh dù request không đổi và mạng sạch. Nguyên nhân: một client khác đang chạy lệnh xử lý tập dữ liệu lớn. Command trên một Redis instance chạy tuần tự, nên GET rẻ vẫn phải chờ đến lượt.
“Redis single-threaded” đúng cho thực thi command, nhưng không phải toàn bộ process:
| Thành phần | Thread |
|---|---|
| Thực thi command (đọc/ghi keyspace, Lua) | Main thread, từng command một |
| Việc nền (fsync AOF, đóng file, lazy free) | Background threads |
| Đọc/ghi socket, parse protocol | Tuỳ chọn I/O threads (io-threads) |
Redis 7, Redis 8 và Valkey 8
Phần tiêu đề “Redis 7, Redis 8 và Valkey 8”| Redis 7 (bài dùng trong demo) | Redis 8 / Valkey 8 | |
|---|---|---|
| I/O threading | Có từ Redis 6, mặc định tắt | Làm lại theo mô hình bất đồng bộ |
io-threads |
Mặc định tắt (redis.conf: “By default threading is disabled”) |
Bật bằng io-threads N |
| Ai chạy command | Main thread | Vẫn main thread |
Theo blog Redis về bản 8.0, luồng xử lý là:
- Main thread gán client cho các I/O thread.
- I/O thread đọc và parse query rồi báo main thread.
- Main thread xử lý query và sinh reply.
- I/O thread ghi reply ra socket.
io-threads mặc định là 1 (chỉ main thread). Valkey 8 cũng đổi cách dùng thread (thông báo GA của Valkey). Con số throughput phụ thuộc phần cứng và workload; bài không trích.
Kết luận không đổi với mọi phiên bản: I/O threads tăng khả năng xử lý socket khi nhiều connection; chúng không chia nhỏ một Lua script hay SMEMBERS trên set lớn thành nhiều phần chạy song song. Bật io-threads khi CPU main thread bị socket I/O chiếm, không phải để chữa lệnh chậm.
Cách hoạt động
Phần tiêu đề “Cách hoạt động”lệnh chậmClient A gửi một lệnh phải quét nhiều phần tử. Lệnh bắt đầu chiếm luồng thực thi command của Redis.
Thời gian B đo = thời gian xếp hàng + thời gian xử lý + mạng + scheduling của client. PING không chậm; nó chờ.
Tách hai khái niệm: concurrency của kết nối (nhiều client cùng chờ I/O) và parallelism của command (nhiều command dùng CPU cùng lúc). Redis có cái trước, không có cái sau trên một instance.
Atomic cho từng command, không cho cả chuỗi
Phần tiêu đề “Atomic cho từng command, không cho cả chuỗi”INCR counter là atomic: hai client cùng gửi không mất lần tăng nào. Nhưng GET, tính trong code, rồi SET là hai command, và client khác chen vào giữa được. Cần nhiều bước thì dùng command chuyên dụng, MULTI/EXEC có WATCH, hoặc Lua script. Lua chạy atomic, đổi lại mọi client khác đợi hết script.
Lua chạy quá lâu: BUSY và SCRIPT KILL
Phần tiêu đề “Lua chạy quá lâu: BUSY và SCRIPT KILL”Script không chặn vô hạn. Sau busy-reply-threshold (alias lua-time-limit, mặc định 5 giây theo redis.conf), Redis bắt đầu trả lỗi BUSY cho client khác.
- Script chưa ghi gì vào dataset: dùng được
SCRIPT KILL. - Script đã ghi: cách duy nhất là
SHUTDOWN NOSAVE.
Giữ script ngắn hơn nhiều so với 5 giây: 5 giây là ngưỡng cuối cùng, không phải ngân sách.
Lệnh nguy hiểm và cách thay
Phần tiêu đề “Lệnh nguy hiểm và cách thay”| Lệnh | Vấn đề | Thay bằng |
|---|---|---|
KEYS pattern |
Quét toàn bộ keyspace trong một lần | SCAN (theo từng lô) |
SMEMBERS, HGETALL trên collection lớn |
Trả hết phần tử một lần | SSCAN, HSCAN, hoặc giới hạn kích thước collection |
DEL key rất lớn |
Giải phóng đồng bộ trên main thread | UNLINK (giải phóng ở background) |
FLUSHALL/FLUSHDB |
Xoá đồng bộ | thêm ASYNC |
| Lua script nặng | Chặn mọi client | Chia lô, giữ script ngắn, đo thời gian |
Giao dịch nhiều GET-SET |
Race giữa các command | INCR/command phù hợp, WATCH, Lua ngắn |
Tài liệu latency của Redis gọi KEYS là nguồn gây latency rất phổ biến ở production và chỉ nên dùng để debug.
Quan sát
Phần tiêu đề “Quan sát”| Công cụ | Cho biết | Giới hạn |
|---|---|---|
SLOWLOG GET 5 |
Command vượt ngưỡng thời gian thực thi | Không gồm thời gian mạng; không phải lịch sử đầy đủ |
INFO commandstats |
Số lần gọi và usec_per_call theo tên command |
Không chỉ ra key nào gây chậm |
LATENCY DOCTOR |
Mô tả các sự kiện latency Redis ghi nhận | Cần bật latency monitor |
redis-cli --bigkeys |
Key lớn theo từng kiểu | Quét keyspace, chạy cẩn thận |
SLOWLOG sạch mà client vẫn chậm thì kiểm tra mạng, event loop phía ứng dụng, connection pool và payload lớn.
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Demo hai kết nối
Phần tiêu đề “Demo hai kết nối”Demo chạy một Lua loop CPU-bound ngắn trên Redis local; không chạy trên Redis dùng chung hoặc production.
docker compose -f examples/docker-compose.yml up -d redisnpx --prefix examples ts-node examples/src/redis-event-loop/command-queue-demo.tsdocker compose -f examples/docker-compose.yml exec -T redis redis-cli SLOWLOG GET 5Connection A chạy Lua loop chỉ tính số; 10 ms sau, B gửi PING và script in pingWaitMs rồi xác nhận Lua đã xong. Nếu vòng lặp kết thúc trước khi B gửi, thời gian đo gần như bình thường; tăng số vòng trên local để thấy hàng đợi.
Demo cần Docker nên không chạy lại trong lần rà soát này và bài không đính kèm output; chạy trên máy bạn để xem pingWaitMs.
Trade-offs
Phần tiêu đề “Trade-offs”Điểm mạnh của mô hình
- Mỗi command atomic rõ ràng trong một instance, hợp cho counter và thao tác ngắn
- Ít overhead đồng bộ giữa các worker thực thi
- I/O threads và background work tăng throughput socket mà không đổi mô hình command
Điểm nghẽn phải quản lý
- Command hoặc Lua chạy lâu kéo tail latency của mọi client
- Một instance không dùng nhiều core cho logic command; muốn scale cần cluster/sharding hoặc tách workload
- Atomic của một command không thay transaction nghiệp vụ nhiều bước
Checklist production
Phần tiêu đề “Checklist production”- Không dùng
KEYStrong code ứng dụng; dùngSCAN. - Dùng
UNLINKthayDELcho key lớn;FLUSH*kèmASYNC. - Giới hạn kích thước collection; duyệt bằng
*SCAN. - Lua script ngắn, đo thời gian; coi
busy-reply-thresholdchỉ là lưới an toàn. - Bật
io-threadschỉ khi đo thấy CPU main thread bị socket I/O chiếm (đa connection). - Theo dõi p99 phía client cùng
SLOWLOGvàINFO commandstats. - Tách workload báo cáo/batch khỏi instance phục vụ request.
Sai lầm thường gặp
Phần tiêu đề “Sai lầm thường gặp”Tóm tắt
Phần tiêu đề “Tóm tắt”Tự kiểm tra: (1) Vì sao PING có thể chậm khi client khác chạy EVAL? (2) SCRIPT KILL dùng được khi nào? (3) Nếu SLOWLOG trống nhưng request vẫn chậm, kiểm tra thêm tầng nào?