AOF ghi từng lệnh vào tệp thay vì chụp ảnh định kỳ. Ba chế độ appendfsync được mô tả khắp nơi là "nhanh / cân bằng / an toàn". Bài này đo cả tốc độ lẫn lượng mất thật.

Thông lượng ba chế độ và kết quả đo mất dữ liệu khi giết tiến trình

Ba chế độ, và cái giá phụ thuộc cách gửi lệnh

appendfsync Từng lệnh một Đường ống 100
no 20.009 ops/s 433.630 ops/s
everysec 21.472 ops/s 467.770 ops/s
always 1.717 ops/s 97.488 ops/s

noeverysec giống nhau ở cả hai cột — chênh lệch nằm trong nhiễu. Điều đó hợp lý: everysec gọi fsync mỗi giây trên một luồng nền, nên nó gần như không chạm vào đường đi của lệnh.

always mới là chỗ trả giá, và cái giá phụ thuộc cách bạn gửi lệnh:

  • Gọi từng lệnh một: chậm 12,5 lần.
  • Có đường ống 100: chậm 4,8 lần.

Lý do: Redis gọi fsync một lần cho mỗi vòng lặp sự kiện, không phải một lần cho mỗi lệnh. Một đường ống 100 lệnh được xử lý trong cùng một vòng và tốn đúng một fsync.

Nghĩa là con số "always chậm 10 lần" mà bạn đọc ở đâu đó chỉ đúng cho một kiểu ứng dụng. Đo trên chính cách ứng dụng của bạn gửi lệnh.

Giết tiến trình: mất bao nhiêu

Đây là phép đo tôi vào với một dự đoán rõ ràng, và dự đoán đó sai.

Tôi cho một khách ghi liên tục, đếm số lệnh đã được xác nhận, rồi SIGKILL Redis giữa chừng và đếm lại sau khi nó khởi động lại.

Lần Chế độ Đã xác nhận Còn lại Mất
1 no 90.181 90.181 0
1 everysec 86.587 86.587 0
1 always 6.965 6.964 1
2 no 68.900 68.900 0
2 everysec 70.127 70.127 0
3 no 68.639 68.639 0
3 everysec 73.881 73.881 0

Kể cả appendfsync no cũng không mất gì. Lặp ba lần, đều vậy.

Vì sao — và điều này quan trọng

SIGKILL giết tiến trình, không giết hệ điều hành.

Redis đã gọi write() để đưa dữ liệu vào hệ tệp. write() chỉ chép dữ liệu vào bộ đệm trang của kernel; fsync() mới là thứ ép kernel đẩy xuống đĩa. Nhưng bộ đệm trang thuộc về kernel, không thuộc về tiến trình — tiến trình chết thì nó vẫn còn nguyên, và tiến trình mới đọc lại thấy đủ.

Nên appendfsync không quyết định điều gì xảy ra khi Redis chết. Nó quyết định điều gì xảy ra khi máy mất điện hoặc kernel sụp.

Đây là phân biệt bị làm mờ trong gần như mọi giải thích về AOF, và nó đổi hẳn cách chọn:

  • Sợ Redis bị OOM, bị SIGKILL, bị container khởi động lại — everysec là quá đủ, và ngay cả no cũng không mất gì.
  • Sợ mất điện, hỏng máy chủ vật lý, kernel panic — lúc đó no có thể mất tới 30 giây (chu kỳ đẩy mặc định của kernel Linux), everysec mất tối đa 1 giây, always gần như không mất.

Tôi không đo được trường hợp mất điện — không tắt được kernel trong container. Đó là giới hạn của phép đo này, và tôi nêu rõ thay vì suy diễn con số.

always mất 1 bản ghi

Dòng always lần 1 mất đúng một bản ghi. Không phải lỗi đo.

Có một khe hở nhỏ: Redis ghi vào bộ đệm AOF, trả lời khách, rồi mới fsync ở cuối vòng lặp sự kiện. Lệnh cuối cùng được xác nhận ngay trước khi bị giết có thể chưa kịp qua fsync.

Nghĩa là always không phải bảo đảm tuyệt đối — nó là "mất tối đa một lệnh" chứ không phải "không bao giờ mất". Với hệ thống thật sự cần bảo đảm ghi bền, cần nhân bản đồng bộ, không chỉ cần fsync.

Chọn thế nào

Nếu Chọn
Bộ đệm, dựng lại được Tắt AOF, dùng RDB
Dữ liệu quan trọng, chạy trên máy ảo/đám mây everysec
Chấp nhận mất tối đa 1 giây everysec
Cần bền hơn nữa always + nhân bản, và đo lại thông lượng

everysec là mặc định và đúng cho gần như mọi trường hợp. Nó nhanh bằng no và mất tối đa một giây khi mất điện.

Và nhớ điều đã đo ở phần trước: chỉ bật AOF thôi cũng đã tốt hơn hẳn RDB một mình, vì RDB mặc định có thể mất tới một giờ.

Ba điều dễ vấp

CONFIG SET appendonly yes không bền qua khởi động lại. Đây là chỗ tôi vấp ngay trong bài này: lần đo đầu tiên cả ba chế độ đều mất 100% dữ liệu, và tôi tưởng đã phát hiện điều gì to tát. Thực ra là Redis khởi động lại với appendonly no và nạp từ RDB. Phải ghi vào tệp cấu hình, hoặc gọi CONFIG REWRITE.

AOF cần viết lại định kỳ. Tệp chỉ nối thêm, nên SET k 1 một triệu lần tạo một triệu dòng. BGREWRITEAOF viết lại thành trạng thái hiện tại. Redis tự làm khi tệp lớn gấp đôi lần viết lại trước (auto-aof-rewrite-percentage 100).

Viết lại AOF cũng fork, nên nó mang đúng chi phí copy-on-write đã đo ở phần 18. Bật cả RDB lẫn AOF nghĩa là hai nguồn fork, và chúng có thể trùng nhau.

Thử ba mươi giây

Xem bạn đang hứa gì với dữ liệu của mình:

redis-cli config get appendonly appendfsync save | paste - -
redis-cli info persistence | grep -E \
  'aof_enabled|aof_last_write_status|aof_rewrite_in_progress|aof_last_bgrewrite_status|rdb_changes_since_last_save'

Ba dấu hiệu:

  • appendonly no save để trống — không có gì được lưu cả. Redis khởi động lại là trắng.
  • aof_last_write_status khác ok — Redis đang không ghi được AOF, thường vì đĩa đầy. Nó vẫn phục vụ bình thường, nên bạn sẽ không biết cho tới lúc cần khôi phục.
  • appendonly yes mà chỉ đặt bằng CONFIG SET — kiểm bằng cách đọc tệp cấu hình. Nếu nó không có ở đó, bạn sẽ mất AOF ở lần khởi động lại tiếp theo.

Phần sau: RDB so với AOF — đo thời gian khôi phục thật của từng cách.