Hai tham số giữ tin trông đơn giản và đều không làm đúng cái tên của chúng. Bài này đo cả hai bằng đồng hồ và bằng du.
retention.ms là mức sàn, không phải hạn chót
Ghi 20.000 tin vào topic có retention.ms=20000 (20 giây) và segment.bytes=200KB:
| Segment | Offset đầu | Dung lượng | |
|---|---|---|---|
| ngay sau khi ghi | 22 | 0 | 4,3 MB |
| sau 25 giây | 22 | 0 | không đổi |
| sau 45 giây | 22 | 0 | không đổi |
| sau ~70 giây | 1 | 20.000 | xoá sạch |
Ở giây thứ 25 và 45, dữ liệu đáng lẽ đã quá hạn vẫn còn nguyên. Tới khoảng giây 70 thì biến mất toàn bộ trong một lần.
Lý do: việc dọn không chạy liên tục mà theo chu kỳ log.retention.check.interval.ms, mặc định 300.000 ms — năm phút. Giữa hai lần chạy, dữ liệu quá hạn vẫn nằm đó và consumer vẫn đọc được.
Cách hiểu đúng: retention.ms bảo đảm dữ liệu sống ít nhất chừng đó, không bảo đảm nó chết sau chừng đó. Độ trễ thực tế nằm đâu đó từ 0 đến log.retention.check.interval.ms, tuỳ bạn rơi vào chỗ nào trong chu kỳ.
Điều này quan trọng với yêu cầu tuân thủ kiểu "dữ liệu cá nhân phải xoá sau 30 ngày". retention.ms=2592000000 không đáp ứng được yêu cầu đó một cách chặt chẽ — nó chỉ nói dữ liệu sẽ bị xoá sau 30 ngày, có thể muộn hơn năm phút, và nếu segment chưa đóng thì còn muộn hơn nữa.
Segment là đơn vị xoá, và nó lớn hơn bạn nghĩ
Kafka không xoá từng tin. Nó xoá cả tệp segment, và chỉ khi mọi tin trong segment đó đều quá hạn.
Với segment.bytes mặc định là 1 GB, một topic ít lưu lượng có thể mất hàng tuần để đóng một segment — và trong suốt thời gian đó không có gì bị xoá, dù retention.ms là bao nhiêu.
Đây là nguyên nhân phổ biến nhất của câu hỏi "sao đặt retention 1 ngày mà dữ liệu 3 tuần vẫn còn". Chữa bằng cách hạ segment.bytes, hoặc đặt segment.ms để buộc đóng segment theo thời gian:
kafka-configs.sh --bootstrap-server kf:9092 --alter \
--entity-type topics --entity-name ten-topic \
--add-config segment.ms=3600000 # đóng segment mỗi giờ
retention.bytes cho ra nhiều hơn con số bạn viết
Topic 3 partition, retention.bytes=1048576 (1 MB), ghi 12 MB rồi đợi dọn:
| Partition | Byte thật | So với giới hạn |
|---|---|---|
| rb-0 | 1.235.651 | +18% |
| rb-1 | 1.211.898 | +16% |
| rb-2 | 1.113.636 | +6% |
| tổng | 3.561.185 | gấp 3,4 lần |
Hai bội số chồng lên nhau.
retention.bytes tính theo từng partition, không theo topic. Viết "1 MB" trên topic 3 partition là cho phép 3 MB.
Xoá theo cả segment nên nó luôn dừng ở mức lớn hơn hoặc bằng giới hạn. Kafka bỏ segment cũ nhất chừng nào bỏ xong vẫn còn ≥ giới hạn; bỏ thêm một cái nữa là tụt xuống dưới, nên nó dừng. Dung lượng thật luôn nằm giữa retention.bytes và retention.bytes + segment.bytes.
Công thức dự trù đĩa
đĩa cần = (retention.bytes + segment.bytes)
× số partition
× replication-factor
Ví dụ cụ thể: retention.bytes=10GB, 12 partition, replication-factor=3:
(10 GB + 1 GB) × 12 × 3 = 396 GB
Không phải 10 GB. Và con số đó chia cho ba broker, nên mỗi broker cần 132 GB chỉ cho một topic.
Cộng thêm phần chỉ mục. Trong phép đo trên, du báo 1,3 MB cho phần log 1,2 MB — nhưng tệp chỉ mục thô của mỗi partition là 20,9 MB không gian địa chỉ (tệp thưa, đĩa thật gần như bằng 0). Với vài nghìn partition, đây là chỗ đáng kiểm tra hạn mức tệp mở của hệ điều hành hơn là hạn mức đĩa.
Đặt cả hai
retention.ms = 604800000 # 7 ngày
retention.bytes = 10737418240 # 10 GB mỗi partition
Cái nào tới trước thì cái đó thắng.
- Chỉ đặt
retention.ms: đĩa đầy khi lưu lượng tăng đột biến. Broker đầy đĩa là sự cố toàn phần, không phải sự cố một topic. - Chỉ đặt
retention.bytes: dữ liệu biến mất sớm hơn dự kiến khi lưu lượng tăng, và consumer chậm sẽ mất tin mà không có cảnh báo nào.
Đặt cả hai với ý định rõ ràng: retention.ms là chính sách, retention.bytes là lưới an toàn. Đặt retention.bytes đủ rộng để bình thường không bao giờ chạm tới, chỉ để chặn broker chết vì đầy đĩa.
retention.ms = -1: giữ mãi mãi
--add-config retention.ms=-1
Hợp lệ và có ích thật: sự kiện dựng lại trạng thái, nhật ký kiểm toán, dữ liệu nguồn cho việc tính lại. Kafka làm được vì cách lưu của nó — đã đo ở phần 16 — đọc từ offset 0 nhanh bằng đọc từ cuối.
Nhưng phải đi kèm retention.bytes hoặc một cơ chế chuyển dữ liệu cũ đi nơi khác. "Giữ mãi mãi" mà không có trần là hẹn giờ cho một sự cố đầy đĩa.
Từ Kafka 3.6, lưu trữ phân tầng cho phép đẩy segment cũ sang S3 và giữ trên đĩa broker chỉ phần nóng. Đó là cách đúng để giữ mãi mãi, nếu hạ tầng của bạn có.
Hai giá trị mặc định đáng đổi ngay
log.retention.hours=168 (7 ngày) là mặc định của broker và nó áp cho mọi topic không khai riêng — kể cả topic tạo tự động. Với topic lưu lượng cao mà không ai để ý, bảy ngày dữ liệu có thể là hàng trăm GB.
log.retention.bytes=-1 là mặc định, nghĩa là không có trần dung lượng nào cả. Đặt một giá trị ở mức broker là cách rẻ nhất để không bao giờ gặp cảnh đầy đĩa vì một topic mới ai đó vừa tạo.
Thử ba mươi giây
Xem topic nào của bạn chưa có trần dung lượng:
for t in $(kafka-topics.sh --bootstrap-server kf:9092 --list); do
c=$(kafka-configs.sh --bootstrap-server kf:9092 --describe \
--entity-type topics --entity-name "$t")
echo "$c" | grep -q retention.bytes || echo "$t (dùng mặc định broker)"
done
Và đo dung lượng thật của một topic:
kafka-log-dirs.sh --bootstrap-server kf:9092 --describe --topic-list ten-topic \
| grep -oE '"size":[0-9]+' | cut -d: -f2 | paste -sd+ | bc
So con số đó với retention.bytes bạn đã đặt. Nếu nó lớn hơn nhiều lần, bạn vừa tìm ra hai bội số ở bài này.
Phần sau đo cleanup.policy=compact — và cho thấy vì sao "mỗi khoá còn một tin" là câu sai.