Có một con dao bếp chuyên thái, sắc và nhẹ, thái nhanh hơn hẳn — và có con dao Thụy Sĩ gấp làm được chục việc, mỗi việc đủ tốt. Người ta hay tranh luận cái nào "hơn", nhưng đó là câu hỏi sai: con dao thái thắng nếu bạn chỉ thái mãi mãi, còn con dao gấp thắng ở chỗ bạn không phải mang theo mười con dao khi chưa biết mình sẽ cần cắt gì. Redis và Memcached đúng là hai con dao đó, và bài này đo cả cái sắc lẫn cái tiện.
Câu hỏi này đã cũ và câu trả lời thường là "Redis, vì nó làm được nhiều hơn". Tôi đo cả hai trên đúng cùng một bài toán bộ đệm.
Cùng bài toán khoá–giá trị 100 byte
| Redis | Memcached | |
|---|---|---|
SET, không đường ống |
20.136/s | 20.854/s |
GET, không đường ống |
20.894/s | 20.970/s |
SET, đường ống 100 |
376.063/s | 388.538/s |
GET, đường ống 100 |
608.120/s | 944.119/s |
| Bộ nhớ 100.000 khoá | 40,37 MB | 15,82 MB |
| CPU khi 4 khách song song | 73,94% | 100,16% |
Không đường ống, hai hệ thống bằng nhau — 20.000 ops/s cho cả hai. Đúng như đo ở phần 2: nút thắt là lượt đi về, không phải máy chủ. Có đường ống, Memcached GET nhanh hơn 55%.
Vì sao Memcached nhanh hơn khi đẩy mạnh
Dòng CPU nói rõ: Redis dùng 73,94% — tức chưa hết một nhân, và nó không thể vượt 100% vì xử lý lệnh chỉ có một luồng. Memcached dùng 100,16%, tức nó đang dùng nhiều hơn một nhân.
Memcached đa luồng thật sự: -t 4 cho bốn luồng worker, mỗi luồng xử lý lệnh độc lập. Với máy nhiều nhân và tải cao, đó là lợi thế kiến trúc mà Redis không có. Redis chọn một luồng để đổi lấy tính nguyên tử miễn phí và mã đơn giản. Với phần lớn hệ thống — dưới 100.000 ops/s — lựa chọn đó không tốn gì. Trên ngưỡng đó, nó là trần cứng, và cách vượt duy nhất là Cluster (phần 23).
Và tốn ít bộ nhớ hơn 61%
Redis 40,37 MB
Memcached 15,82 MB
Cho cùng 100.000 khoá × 100 byte. Lý do đã đo ở phần 3: Redis tốn khoảng 64 byte phụ trội mỗi khoá cho robj, dictEntry, tiêu đề chuỗi và bộ cấp phát. Memcached dùng bộ cấp phát slab đơn giản hơn và không có lớp đối tượng đa kiểu — nó chỉ biết chuỗi. Với bộ đệm hàng chục triệu khoá nhỏ, chênh lệch 2,6 lần là tiền thật.
Nhưng Memcached đổi lấy điều đó bằng gì
khởi động lại -> curr_items 0
SET giá trị 2 MB -> SERVER_ERROR object too large for cache
SET giá trị 900 KB -> STORED
Không có lưu trữ. Khởi động lại là mất sạch, không có RDB, không có AOF. Với bộ đệm thuần thì không sao — nhưng nó nghĩa là mỗi lần triển khai là một lần bão cache (phần 27).
Trần 1 MB mỗi giá trị (chỉnh được bằng -I, nhưng mặc định là vậy). Redis cho tới 512 MB.
Và danh sách những gì hoàn toàn không có:
- Kiểu dữ liệu: List, Set, Sorted Set, Hash, Stream, Bitmap, HyperLogLog
- Pub/Sub, Lua, giao dịch
- Sao chép, chuyển đổi tự động
- Lệnh chặn như
BLPOP - TTL đọc lại được (
TTLtrả về thời gian còn lại)
Mỗi mục trong danh sách này là một phần của sê-ri này. Nếu bạn dùng bất kỳ cái nào, câu hỏi đã có câu trả lời.
Chọn thế nào
Memcached khi: bộ đệm thuần khoá–giá trị, dữ liệu dựng lại được, tải rất cao trên máy nhiều nhân, và bạn muốn ít thứ để hỏng nhất có thể.
Redis khi: cần bất kỳ thứ gì trong danh sách ở trên — và với phần lớn ứng dụng, bạn sẽ cần ít nhất một.
Điều đáng nói: hai hệ thống này không cạnh tranh nhau như người ta tưởng. Memcached làm đúng một việc và làm rất tốt. Redis làm nhiều việc, mỗi việc tốt vừa đủ, và giá trị của nó nằm ở chỗ bạn không phải chạy năm hệ thống.
Một góc nhìn thực dụng
Nếu đang chọn mới: bắt đầu bằng Redis. Không phải vì nó nhanh hơn — đo được là không — mà vì xác suất bạn cần thêm một kiểu dữ liệu, một hàng đợi, hoặc một khoá phân tán trong sáu tháng tới là rất cao, và lúc đó việc thêm Memcached-thứ-hai tốn hơn nhiều.
Nếu đang chạy Memcached và nó ổn: đừng đổi. Chênh lệch hiệu năng nghiêng về phía nó, và việc di chuyển một bộ đệm đang chạy tốt không mang lại gì.
Nếu đang chạy Redis chỉ để làm bộ đệm khoá–giá trị và bộ nhớ là vấn đề: 61% là con số đáng cân nhắc. Nhưng hãy đo trên dữ liệu thật trước — con số đó phụ thuộc kích thước giá trị, và với giá trị lớn phụ trội của Redis trở nên không đáng kể.
Mẫu số chung: chuyên dụng thắng ở một việc, đa năng thắng ở tương lai bất định
Cái lựa chọn ở đây — công cụ chuyên một việc (nhanh hơn, gọn hơn, ít thứ hỏng hơn) so với công cụ đa năng (mỗi việc đủ tốt, nhưng gộp mười việc vào một) — không riêng gì bộ đệm. Nó là một trong những quyết định kiến trúc lặp lại nhất, và chi phí thật thường không phải hiệu năng mà là số hệ thống bạn phải vận hành.
Trong thế giới Java, đây đúng là chọn một thư viện hẹp (một JSON parser thuần, một HTTP client tối giản) so với một framework làm-tất (Spring): thư viện hẹp nhanh và nhẹ hơn cho đúng phần việc của nó, framework "nặng" hơn nhưng bạn không phải khâu mười mảnh lại và giữ chúng hợp nhau. Chọn hẹp khi biết chắc biên giới bài toán; chọn framework khi biên giới còn sẽ nở ra.
Trong Go, đây là tinh thần "stdlib đủ rồi" so với kéo vào một framework: net/http + vài package nhỏ thắng khi phạm vi cố định và bạn muốn ít phụ thuộc; nhưng khi dự án phình, cái giá của việc tự ghép mọi thứ vượt cái giá của một khung có sẵn. Cùng một cân nhắc chuyên-dụng-vs-tích-hợp.
Còn ở cơ sở dữ liệu, đây là chuyện dùng một kho chuyên biệt (Elasticsearch cho tìm kiếm, một time-series DB cho metric) so với để Postgres làm luôn bằng full-text search và extension: kho chuyên biệt tốt hơn hẳn ở đúng việc của nó, nhưng mỗi cái là một hệ phải vận hành, sao lưu, giám sát, và đồng bộ dữ liệu — nên nhiều nhóm cố ý ở lại Postgres tới khi thật sự vượt ngưỡng, đúng logic "bắt đầu bằng Redis" ở trên.
Mẫu số chung: công cụ chuyên dụng gần như luôn nhanh hơn và gọn hơn ở đúng một việc, nhưng công cụ đa năng thắng khi tương lai bất định — vì chi phí lớn nhất hiếm khi là hiệu năng của một thao tác, mà là số hệ thống rời rạc phải vận hành, giữ đồng bộ, và ghép cho hợp nhau khi nhu cầu nở ra. Nên câu hỏi đúng khi chọn giữa chúng không phải "cái nào nhanh hơn / gọn hơn" (chuyên dụng gần như luôn thắng vế đó) mà là "phạm vi bài toán này đã đóng chưa — nếu nó còn sẽ mọc thêm nhu cầu, cái giá thêm một hệ thống nữa về sau có đáng phần hiệu năng tôi được lúc này không".
Muốn tự xem bạn có đang dùng gì ngoài khoá–giá trị không:
redis-cli info commandstats | sed 's/cmdstat_//' | cut -d: -f1 | \
grep -vE '^(get|set|del|exists|expire|ttl|mget|mset|ping|info|config|client|command)$' | sort
Danh sách in ra là những lệnh Memcached không có. Trống nghĩa là bạn đang dùng Redis như một Memcached đắt hơn 2,6 lần về bộ nhớ. Và kiểm kiểu dữ liệu đang lưu:
redis-cli --scan --count 500 | head -2000 | while read k; do redis-cli type "$k"; done | sort | uniq -c
Chỉ toàn string thì cùng kết luận. Có hash, zset, stream thì câu hỏi đã được trả lời.
Phần sau: danh sách kiểm trước khi đưa Redis lên chạy thật.