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.

Log chữ so với log JSON

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
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.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_id và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: logfmtlevel=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.