Bốn mươi phần, mỗi phần một phép đo chạy trong container rồi dọn đi. Bài này gom lại thành thứ dùng được.

Mười con số đáng nhớ, ba điều lặp lại, và ba lần đo sai

Mười con số đáng nhớ nhất

Con số Ý nghĩa Phần
64 byte phụ trội mỗi khoá chuỗi 3
44 byte ranh giới embstr sang raw 3
2,6 byte mỗi số trong intset — rẻ nhất trong Redis 4
×18,6 khi Set số nguyên vượt 512 phần tử 4
14.384 byte HyperLogLog, không tăng dù đếm bao nhiêu 6
×19,7 bộ nhớ khi tiêu thụ Stream quên XACK 7
×44 thông lượng khi thêm đường ống 64 2
×5.183 bộ đệm đầu ra khi giá trị 10 MB thay vì 1 KB 30
0 bản ghi mất khi SIGKILL, kể cả appendfsync no 19
79% bản ghi mất khi bản sao chậm rồi máy chính chết 21

Hai dòng cuối đứng cạnh nhau là bức tranh đầy đủ về độ bền của Redis: nó tốt hơn nhiều người nghĩ trong trường hợp thường, và tệ hơn nhiều người nghĩ trong trường hợp xấu.

Ba điều lặp lại ở nhiều phần nhất

1. Nút thắt thường là lượt đi về, không phải Redis.

Một kết nối không đường ống: 20.000 ops/s. Cùng kết nối đó với đường ống 64: 1.136.364 ops/s. Redis dùng chưa hết một nhân trong trường hợp đầu.

Điều này xuất hiện lại ở phần 12 (đường ống), 23 (chuyển hướng Cluster), 29 (khoá nóng không làm chậm), 32 (TLS miễn phí khi có đường ống). Trước khi tối ưu bất cứ thứ gì, hãy kiểm xem bạn có đang tốn một lượt đi về cho mỗi lệnh không.

2. Vượt một ngưỡng là nhảy bậc, không tăng dần.

512 trường Hash, 129 phần tử Set, 513 số nguyên, 45 byte giá trị, 10.001 kết nối — mỗi con số là một vách đá. Thêm một đơn vị và chi phí nhân lên 2 đến 18 lần.

Đây là lý do thử nghiệm ở quy mô nhỏ không dự đoán được hành vi ở quy mô lớn. Không có đường cong để ngoại suy; có bậc thang.

3. Redis hiếm khi báo lỗi. Nó chậm dần hoặc lớn dần.

Khoá không TTL, cache script không dọn, danh sách chờ Stream, chỉ mục tự dựng lệch, mã hoá đắt sau khi khoá đã nhỏ lại — không cái nào sinh ra một dòng lỗi. Chúng chỉ tích lại.

Hệ quả: giám sát Redis là giám sát xu hướng, không phải chờ báo động.

Danh sách kiểm trước khi đưa lên chạy thật

Cấu hình

  • maxmemory đã đặt, khoảng 60–70% RAM khả dụng (18, 36)
  • maxmemory-policy phù hợp — allkeys-lfu cho bộ đệm, không để volatile-* khi không có TTL (9)
  • requirepass hoặc ACL, và default đã tắt (32)
  • bind không phải mọi giao diện; cổng không ánh xạ ra 0.0.0.0 (36)
  • timeout khác 0 (31)
  • appendonly yes nếu dữ liệu quan trọng (19, 20)
  • client-output-buffer-limit normal đã đặt nếu có giá trị lớn (12, 30)

Mô hình dữ liệu

  • Mọi khoá tạm có TTL — so keys với expires trong INFO keyspace (8, 39)
  • Dùng Hash cho đối tượng, không một khoá mỗi trường (39)
  • Kiểm OBJECT ENCODING của khoá lớn — có cái nào đang mang mã hoá đắt không cần thiết (5)
  • Thẻ băm {} đủ hẹp nếu dùng Cluster (29)
  • Không giá trị nào trên 1 MB nếu tránh được (30)

Mã ứng dụng

  • Bể kết nối, không mở kết nối mỗi lệnh (31)
  • Đường ống hoặc MGET/MSET cho thao tác hàng loạt (12)
  • SET ... KEEPTTL khi ghi đè khoá có hạn (8, 39)
  • SCAN thay KEYS, UNLINK thay DEL cho khoá lớn (11)
  • EVALSHA chứ không EVAL lặp lại; script dùng KEYS/ARGV (14)
  • Tiêu thụ Stream có gọi XACK, và có bộ dọn danh sách chờ (7)
  • Khoá phân tán kiểm giá trị trả về lúc giải phóng (24)

Vận hành

  • Đã khôi phục thử từ RDB/AOF ít nhất một lần (20)
  • Cảnh báo trên evicted_keys chứ không chỉ trên phần trăm bộ nhớ (33)
  • slowlog-log-slower-than hạ xuống 1.000 µs (34)
  • CLIENT SETNAME trong mọi ứng dụng (34)
  • Biết khoảng cách bản sao tính bằng byte, không chỉ lag (21)

Ba lần tôi đo sai rồi phải sửa

Phần 2 — tôi chạy khách benchmark trong cùng container với Redis và đo được 181% CPU cho một máy chủ một luồng. Con số bất khả thi, và đó là cách tôi biết docker stats đang cộng cả máy chủ lẫn khách.

Phần 28 — tôi đo bốn chiến lược cập nhật bộ đệm và kết luận "xoá trước khi cập nhật" an toàn (0 lệch). Đo lại với người đọc song song thì nó để lại dữ liệu cũ vĩnh viễn trong một trên ba lần chạy. Phép đo đầu thiếu đúng thành phần làm lộ vấn đề.

Phần 36 — tôi đặt maxmemory 80mb trong container 128 MB và vẫn bị OOMKilled, tưởng đã tìm ra lỗi của Redis. Thực ra là tôi nạp dữ liệu bằng script Lua, và Redis chỉ kiểm maxmemory một lần ở đầu script — điều chính tôi đã đo ở phần 9.

Cả ba đều lộ ra vì cùng một lý do: một con số vượt qua giới hạn vật lý, hoặc không khớp với phép đo trước đó. Khi điều đó xảy ra, hãy nghi phép đo trước khi nghi hệ thống.

Điều còn lại sau bốn mươi phần

Redis là một bảng băm rất nhanh sống trong RAM, có sẵn mạng và vài kiểu dữ liệu dựng sẵn. Mọi thứ khác — độ bền, nhân bản, phân mảnh, truy vấn — là thứ được thêm vào xung quanh cái lõi đó, và mỗi thứ đều có một cái giá đo được.

Bốn mươi phép đo trong sê-ri này bác bỏ ba điều tôi tưởng là hiển nhiên khi bắt đầu: Redis không nhanh hơn PostgreSQL 100 lần (phần 1), appendfsync no không mất dữ liệu khi tiến trình bị giết (phần 19), và TLS không làm chậm khi có đường ống (phần 32).

Cụm của bạn khác cụm của tôi: khác phiên bản, khác phần cứng, khác hình dạng dữ liệu. Mọi con số ở đây nên coi là giả thuyết cần kiểm lại. Mỗi phần đều kết thúc bằng một lệnh chạy được — đó mới là phần quan trọng.

Thử ba mươi giây

Lệnh cuối của sê-ri, gộp năm thứ hay sai nhất:

redis-cli info | grep -E 'redis_version|used_memory_human|maxmemory_human|maxmemory_policy|evicted_keys|keyspace_hits|keyspace_misses|connected_clients|rdb_changes_since_last_save|aof_enabled'
echo "---"
redis-cli info keyspace
echo "---"
redis-cli config get requirepass timeout appendonly slowlog-log-slower-than | paste - -
echo "---"
redis-cli info commandstats | grep -E 'cmdstat_(keys|flushall|flushdb):' || echo "khong ai goi KEYS/FLUSH — tot"

Bốn khối, bốn câu hỏi: bộ nhớ có trần chưa, dữ liệu có tự dọn không, có ai vào được không, và có ai đang gọi lệnh nguy hiểm không.

Chạy nó trên hệ thống của bạn ngay bây giờ. Với mỗi dòng bất thường, sê-ri này có một phần đo được hậu quả cụ thể của nó.

Cảm ơn bạn đã theo hết bốn mươi phần.