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.
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 |
no và everysec 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 —everyseclà quá đủ, và ngay cảnocũng không mất gì. - Sợ mất điện, hỏng máy chủ vật lý, kernel panic — lúc đó
nocó thể mất tới 30 giây (chu kỳ đẩy mặc định của kernel Linux),everysecmất tối đa 1 giây,alwaysgầ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 novàsaveđể trống — không có gì được lưu cả. Redis khởi động lại là trắng.aof_last_write_statuskhácok— 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 yesmà chỉ đặt bằngCONFIG 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.