Bốn phần trước đo chi phí bên trong ứng dụng. Bài này đo cái nằm ngay sau nó: tiến trình đọc tệp log và chuyển đi.

Tài nguyên của bộ thu thập log

Bố trí

Một container ghi log JSON vào một volume dùng chung, một container khác đọc tệp đó và xuất ra đích rỗng (null của Fluent Bit, blackhole của Vector). Đích rỗng để đo đúng phần đọc và phân tích, không lẫn chi phí mạng.

Tốc độ ghi Bộ thu thập CPU RAM µs mỗi dòng
1.000 dòng/giây Fluent Bit 0,21% 3,59 MiB 2,1
1.000 dòng/giây Vector 1,01% 20,11 MiB 10,1
20.000 dòng/giây Fluent Bit 3,45% 6,72 MiB 1,73
20.000 dòng/giây Vector 6,34% 72,18 MiB 3,17

Thu thập đắt gấp ba lần ghi

1,73 µs mỗi dòng với Fluent Bit ở tốc độ cao.

Phần 2 đo việc ghi một dòng JSON: 548 ns với slog của Go, 518 ns với Node. Nghĩa là chuyển một dòng log đi tốn gấp khoảng ba lần việc tạo ra nó — và với Vector là gấp sáu.

Đây là phần chi phí gần như không bao giờ được tính vào. Khi ai đó nói "ghi log rẻ mà", họ đang nói về 0,5 µs của lời gọi trong ứng dụng, không phải 2,2 µs của toàn bộ đường đi.

Và đường đi còn chưa hết: sau bộ thu thập là nén, gửi qua mạng, nhận ở phía kia, dựng chỉ mục, lưu trữ. Bài này chỉ đo chặng đầu tiên.

Fluent Bit rẻ hơn Vector 10 lần về RAM

72,18 MiB so với 6,72 MiB ở cùng tải.

Khác biệt đến từ thiết kế. Fluent Bit viết bằng C, nhắm thẳng vào việc chạy trên thiết bị nhỏ và trong sidecar. Vector viết bằng Rust và mang theo một bộ máy biến đổi dữ liệu đầy đủ — nó có ngôn ngữ riêng để cắt, gộp, làm giàu bản ghi.

Bạn trả 72 MiB cho những khả năng đó, kể cả khi cấu hình chỉ có một nguồn và một đích như ở đây.

Đáng chú ý hơn là cách RAM tăng theo tải:

1.000 dòng/s 20.000 dòng/s Tăng
Fluent Bit 3,59 MiB 6,72 MiB 1,9×
Vector 20,11 MiB 72,18 MiB 3,6×

Tải tăng 20 lần. Fluent Bit tăng 1,9 lần, Vector tăng 3,6 lần. Cả hai đều tốt — không tuyến tính — nhưng Vector bắt đầu từ điểm cao hơn nhiều và tăng nhanh hơn.

Sidecar hay DaemonSet

Đây là quyết định mà con số trên trả lời được ngay.

Sidecar — mỗi container ứng dụng đi kèm một bộ thu thập. Với 20 container trên một node:

RAM tổng
Fluent Bit 20 × 6,72 = 134 MiB
Vector 20 × 72,18 = 1.444 MiB

DaemonSet — một bộ thu thập mỗi node, đọc log của mọi container:

RAM tổng
Fluent Bit khoảng 20 MiB
Vector khoảng 150 MiB

Cần nói rõ: cột sidecar là phép nhân từ số đo một instance, không phải phép đo trực tiếp, và cột DaemonSet là ước lượng. Một bộ thu thập đọc 20 tệp không tốn đúng bằng 20 lần đọc một tệp — phần bộ nhớ nền được dùng chung. Nhưng thứ tự độ lớn thì đúng, và nó đủ để ra quyết định.

Với Vector, chọn sidecar cho 20 container tốn 1,4 GB RAM chỉ để chuyển log đi — nhiều hơn toàn bộ bộ nhớ mà phần lớn dịch vụ web thực sự dùng.

Sidecar DaemonSet
RAM nhân theo số container không nhân
Cách ly hoàn toàn dùng chung
Cấu hình riêng từng ứng dụng dễ phải qua nhãn
Một bộ thu thập chết mất log một container mất log cả node
Kích thước ảnh phải kéo về nhân theo số pod một lần

Kích thước ảnh cũng đáng nhìn: Fluent Bit 142 MB, Vector 343 MB. Với sidecar, con số đó nhân theo số pod khi node kéo ảnh về lần đầu.

Quy tắc thực dụng: DaemonSet là mặc định đúng. Chọn sidecar khi một ứng dụng cần cấu hình phân tích đặc thù, hoặc khi yêu cầu cách ly bắt buộc — và khi đó hãy dùng Fluent Bit.

Một lỗi cấu hình tôi vấp

Lần chạy đầu tiên, Vector báo 0% CPU và 0 byte RAM ở cả hai tốc độ. Trông như nó nhẹ tới mức không đo được.

Nó đã chết ngay khi khởi động:

ERROR vector::topology::builder: Configuration error.
error=Source "f": data_dir "/tmp/vector" does not exist

Vector cần thư mục lưu trạng thái đọc tệp, và nó không tự tạo. Container thoát với mã 78, và docker stats của một container đã chết trả về số 0 chứ không báo lỗi.

Bài học: 0% CPU không phải "rất nhẹ", nó thường là "không chạy". Luôn kiểm tra trạng thái container trước khi tin số liệu:

docker inspect <ten> --format '{{.State.Status}} exit={{.State.ExitCode}}'

Đây là biến thể của bài học đã gặp nhiều lần trong sê-ri trước: con số quá đẹp là dấu hiệu, không phải kết quả.

Ba việc chỉnh được ngay

Đặt giới hạn bộ đệm. Nếu đích đến chậm hoặc chết, bộ thu thập tích log trong RAM cho tới khi hết:

Mem_Buf_Limit  32MB          # Fluent Bit
storage.type   filesystem    # tran ra dia thay vi giu trong RAM

Giảm tần suất quét tệp. Mặc định của nhiều bộ thu thập là quét mỗi giây; với hàng trăm tệp thì bản thân việc quét đã tốn.

Đừng phân tích JSON hai lần. Nếu ứng dụng đã ghi JSON và đích đến cũng nhận JSON, việc bắt bộ thu thập phân tích rồi dựng lại là công vô ích. Nhiều cấu hình mặc định làm đúng chuyện đó.

Chỗ phép đo này không nói tới

Tôi xuất ra đích rỗng. Trong thực tế, bộ thu thập còn nén, mã hoá TLS, gửi qua mạng, và xử lý áp lực ngược khi phía nhận chậm. Phần đó thường lớn hơn phần đọc tệp.

Tôi cũng không đo Promtail, Filebeat, hay OpenTelemetry Collector — ba lựa chọn phổ biến không kém. Và tôi chỉ đo một nguồn tệp; cấu hình thật thường có nhiều nguồn cùng lúc.

Thử ba mươi giây

Xem bộ thu thập trên node của bạn đang ăn bao nhiêu:

docker stats --no-stream --format '{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}' \
  | grep -iE 'fluent|vector|promtail|filebeat|otel'

# Kubernetes
kubectl top pods -A --containers 2>/dev/null \
  | grep -iE 'fluent|vector|promtail|filebeat|otel'

# So voi so dong log thuc su sinh ra
kubectl logs -n <ns> <pod> --since=60s 2>/dev/null | wc -l

Lấy CPU của bộ thu thập chia cho số dòng mỗi giây. Nếu kết quả vượt xa 2 µs mỗi dòng, phần chênh nằm ở việc phân tích và biến đổi — và đó thường là chỗ cắt được nhiều nhất mà không mất thông tin nào.

Phần sau: metric và bộ đếm — đo chi phí thật của một chỉ số Prometheus.