Phần 2 đo chi phí ghi một dòng log. Bài này đo ba thứ còn lại: nó chiếm bao nhiêu chỗ, nén được bao nhiêu, và tìm trong nó mất bao lâu.
Bố trí
200.000 dòng log, cùng thông tin, hai định dạng:
2026-09-01T10:00:00Z INFO req=42 user=3871 path=/api/v1/don-hang status=200 ms=87.31
{"ts":"2026-09-01T10:00:00Z","level":"info","req":42,"user":3871,"path":"/api/v1/don-hang","status":200,"ms":87.31}
Kích thước
| Log chữ | Log JSON | Chênh | |
|---|---|---|---|
| Mỗi dòng | 88,0 byte | 132,0 byte | 1,50× |
| Cả tệp | 17.598.589 byte | 26.398.589 byte | 1,50× |
| Sau khi gzip | 2.265.102 byte | 2.392.925 byte | 1,06× |
| Tỷ lệ nén | 7,77× | 11,03× |
JSON to hơn 50% khi còn nguyên, và chỉ to hơn 6% sau khi nén.
Lý do nằm ở chính thứ làm nó to: tên khoá lặp lại ở mọi dòng. "level":, "path":, "status": xuất hiện 200.000 lần, và đó là món ăn lý tưởng của mọi thuật toán nén dựa trên từ điển. Log JSON nén được 11,03 lần; log chữ chỉ 7,77 lần.
Hệ quả thực dụng: nếu log của bạn được nén trước khi lưu — và gần như mọi hệ thống thu thập đều nén — thì lập luận "JSON tốn chỗ hơn" gần như biến mất. 6% không đáng để đánh đổi bất cứ thứ gì.
Ngược lại, nếu log nằm nguyên trên đĩa máy chủ và bị xoay vòng theo dung lượng, 50% là con số thật và nó rút ngắn khoảng thời gian bạn giữ được log đi một phần ba.
Truy vấn: chỗ trực giác sai hoàn toàn
Câu hỏi: tìm mọi yêu cầu tới /api/v1/thanh-toan chậm hơn 500 ms.
# log chu
grep 'path=/api/v1/thanh-toan' text.log | awk -F'ms=' '{if ($2+0 > 500) c++} END{print c+0}'
# log JSON
jq -c 'select(.path=="/api/v1/thanh-toan" and .ms>500)' json.log | wc -l
| Cách | Ba lần đo | So với nhanh nhất |
|---|---|---|
Log chữ + grep/awk |
18 / 14 / 19 ms | 1× |
| Log JSON + Python | 236 / 233 / 232 ms | 14× |
Log JSON + jq |
323 / 305 / 302 ms | 19× |
Cả ba đều trả về đúng 22.172 dòng. Kết quả giống nhau, thời gian chênh 19 lần.
Nói lại lần thứ hai vì nó ngược hẳn với điều thường được nói: "log có cấu trúc dễ truy vấn hơn" là sai khi bạn truy vấn trên tệp thô. grep chỉ so byte và bỏ qua 95% số dòng ngay ở bước đầu; jq phải phân tích cú pháp JSON của từng dòng trước khi biết có nên giữ nó không.
Câu nói đó chỉ đúng khi có chỉ mục — Loki, Elasticsearch, CloudWatch Logs Insights. Ở đó, hệ thống đã tách trường sẵn lúc nhận, và truy vấn theo trường nhanh hơn quét chuỗi. Nhưng cái nhanh là chỉ mục, không phải JSON.
Vậy lợi ích thật của log có cấu trúc là gì
Không phải tốc độ, và cũng không phải dung lượng. Nó là tính ổn định của hợp đồng.
Với log chữ, mọi công cụ đọc log của bạn chứa một biểu thức chính quy dựa trên thứ tự và định dạng của các trường. Đổi thứ tự, thêm một trường, đổi dấu phân cách — mọi biểu đồ và mọi cảnh báo im lặng hỏng.
Câu lệnh awk -F'ms=' ở trên là ví dụ hoàn hảo: nó hoạt động vì trường ms= tình cờ là trường cuối. Thêm một trường sau nó, và phép so $2+0 > 500 bắt đầu đọc nhầm.
Với JSON, .ms là .ms bất kể có bao nhiêu trường và chúng nằm ở đâu.
Ba việc chỉ làm được với log có cấu trúc:
- Gắn
trace_idvào mọi dòng và nối log với trace — phần sau của sê-ri sẽ đo việc này. - Đếm và vẽ biểu đồ trực tiếp từ log, không cần bộ phân tích riêng cho từng dịch vụ.
- Gộp log của nhiều dịch vụ viết bằng nhiều ngôn ngữ vào cùng một truy vấn.
Khi nào chọn cái nào
| Chọn log chữ khi | Chọn log JSON khi |
|---|---|
| Log chỉ đọc bằng mắt người | Có Loki / Elasticsearch / CloudWatch |
| Không có hệ thống thu thập | Cần lọc theo trường, không theo chuỗi |
Tìm bằng grep ngay trên máy chủ |
Cần gắn trace_id vào mọi dòng |
| Số dòng rất lớn và không nén | Nhiều dịch vụ, cần gộp log |
| Ngân sách thời gian cực chặt | Cần đếm và vẽ biểu đồ từ log |
Một lựa chọn thứ ba mà nhiều nơi dùng: logfmt — level=info req=42 path=/api ms=87.31. Nó có tên khoá như JSON nên grep được, và nhẹ hơn JSON. Tôi chưa đo nó; đó là một khoảng trống của bài này.
Chỗ phép đo này không nói tới
Tôi đo trên tệp thô, không đo trên hệ thống có chỉ mục. Với Loki hay Elasticsearch, con số truy vấn hoàn toàn khác và JSON thắng. Nhưng chi phí lúc nhận — phân tích, tách trường, dựng chỉ mục — chuyển sang phía hệ thống thu thập, và nó không biến mất.
Tôi dùng gzip -6. zstd nén nhanh hơn và tốt hơn, và tỷ lệ giữa hai định dạng có thể khác.
Dòng log của tôi có 7 trường. Log thật thường có nhiều hơn, và tỷ lệ 1,50× phụ thuộc vào độ dài tên khoá so với độ dài giá trị. Log có tên khoá dài và giá trị ngắn thì tỷ lệ này xấu hơn nhiều — và nén tốt hơn tương ứng.
Thử ba mươi giây
So chính log của bạn:
f=/var/log/dich-vu.log
echo "kich thuoc : $(wc -c < $f) byte, $(wc -l < $f) dong"
echo "byte moi dong : $(awk 'END{print int(s/NR)}' s=$(wc -c < $f) $f 2>/dev/null || echo '-')"
echo "--- nen duoc bao nhieu ---"
gzip -6 -c $f | wc -c | awk -v r=$(wc -c < $f) '{printf "sau gzip: %d byte (%.2fx nho hon)\n", $1, r/$1}'
echo "--- truy van thu ---"
time grep -c 'ERROR' $f
command -v jq >/dev/null && head -100000 $f | time jq -c 'select(.level=="error")' 2>/dev/null | wc -l
Nếu log của bạn đang là JSON mà không có hệ thống chỉ mục nào ở phía sau, bạn đang trả cả 50% dung lượng lẫn 19 lần thời gian truy vấn — mà chưa nhận được lợi ích nào của cấu trúc.
Phần sau: mức log và lấy mẫu — đo cách giảm khối lượng log mà không mất thông tin.