Bốn phần trước đo trace từ phía ứng dụng: cái nó chỉ ra, cái nó tốn, cái gãy khi mất ngữ cảnh, và cách lấy mẫu. Phần này đo phía sau: hai hệ lưu trữ trace thật, cùng một tập 20 000 trace, đo dung lượng và tốc độ truy vấn. Kết quả so sánh thì rõ ràng — nhưng thứ tôi học được nhiều nhất lại là bốn lần cả hai hệ nói dối tôi theo bốn kiểu khác nhau.

Jaeger và Tempo trên cùng 20 000 trace

Bảng số liệu

Jaeger 1.62 với lưu trữ Badger, Tempo 2.6.1 với lưu trữ local. Cùng một bộ sinh dữ liệu nạp 20 000 trace, 80 000 span vào từng hệ qua OTLP/HTTP.

Nạp vào:

Jaeger 42 254 span/giây
Tempo 42 816 span/giây

Gần như bằng nhau.

Dung lượng — và đây là chỗ phải cẩn thận:

Cách đo Jaeger
du -sb trên cả thư mục 2 193 MB
Số block thực sự chiếm 68,50 MB
Chỉ dữ liệu đã ổn định (.sst) 16,48 MB

Ba con số cho cùng một câu hỏi, chênh nhau 133 lần. Tempo, đo tương ứng: 5,05 MB (parquet 4,95 MB + bloom filter 0,10 MB).

So dữ liệu đã ổn định với nhau: 16,48 so với 5,05 MB — Tempo nhỏ hơn 3,3 lần.

Truy vấn (trung vị 3 lần, mỗi lần 5 lượt):

Câu hỏi Jaeger Tempo Kết quả
Lấy một trace theo trace_id 0 ms 5 ms 4 span / 1 879 byte
Tìm theo dịch vụ, giới hạn 20 24 ms 3 ms 20 trace cả hai
Tìm theo thuộc tính status_code=500 2 ms 3 ms 20 trace cả hai

Điều đáng nhớ

Đánh đổi rất rõ: Tempo nhỏ hơn 3,3 lần và tìm theo dịch vụ nhanh hơn 8 lần; Jaeger lấy theo trace_id nhanh hơn. Điều đó khớp với thiết kế — Jaeger dùng kho khoá-giá trị nên tra theo khoá là thao tác rẻ nhất của nó; Tempo lưu cột trong parquet nên quét theo thuộc tính là thứ nó làm tốt.

Nhưng con số đáng nhớ nhất không nằm trong bảng so sánh. du -sb trên thư mục Jaeger cho 2 193 MB. Con số đó sai không phải một chút mà sai 133 lần, vì Badger cấp phát trước một file value log khai báo 2 GB — và file đó là file thưa, thực sự chiếm đúng 4 KB trên đĩa. Nếu tôi tin con số đầu tiên mình lấy được, bài này sẽ kết luận Jaeger tốn 432 lần Tempo.

Đây là lần thứ ba trong sê-ri cùng một cái bẫy. Loki với thư mục WAL, Prometheus với chunk chưa kịp ghi, và giờ Badger với file cấp phát trước. Ba hệ khác nhau, ba cơ chế khác nhau, cùng một kết luận: du trên thư mục dữ liệu không phải là phép đo dung lượng.

Vì sao

Badger là kho khoá-giá trị kiểu LSM. Nó tách dữ liệu thành hai phần: khoá và chỉ mục nằm trong file .sst, còn giá trị nằm trong file .vlog. File value log được tạo sẵn ở kích thước tối đa ngay lúc khởi động để tránh phải mở rộng file khi ghi. Hệ thống tệp hiện đại xử lý chuyện đó bằng file thưa: file khai báo 2 GB nhưng chỉ cấp block khi có dữ liệu thật.

du -sb đọc kích thước khai báo. du không có cờ đó đếm số block thực cấp. Với file thưa, hai con số chênh nhau tuỳ ý.

Còn 52 MB memtable trong con số 68,50 MB: đó là bộ đệm ghi trong bộ nhớ được ánh xạ ra file, và nó sẽ được nén xuống .sst khi đầy. Nó là chi phí tức thời, không phải chi phí lưu trữ dài hạn — cùng bản chất với WAL của Loki.

Về phía truy vấn, Tempo lưu mỗi block thành một file parquet có bloom filter đi kèm. Tìm theo thuộc tính nghĩa là đọc đúng cột cần và bỏ qua phần còn lại, nên 3 ms cho 20 kết quả. Jaeger phải tra chỉ mục rồi lấy từng trace theo khoá, và với truy vấn "20 trace gần nhất của dịch vụ" thì đó là 20 lần tra — 24 ms.

Bốn lần "không có dữ liệu", bốn nguyên nhân khác nhau

Đây là phần tôi bỏ nhiều thời gian nhất, và là phần hữu ích nhất cho ai sắp dựng Tempo. Bốn lần liên tiếp Tempo trả về rỗng, mỗi lần một nguyên nhân khác, và cả bốn đều biểu hiện y hệt nhau.

1. max_traces_per_user mặc định là 10 000. Tôi nạp 20 000 trace, Tempo im lặng từ chối khoảng một nửa và chỉ ghi LIVE_TRACES_EXCEEDED vào log. Bên gửi nhận về HTTP 200. Cách phát hiện: dung lượng Tempo là 2,61 MB — bằng đúng một nửa con số 5,05 MB sau khi sửa.

2. blocklist_poll mặc định là 5 phút. Block đã ghi xuống đĩa — tôi kiểm được bằng tempo_ingester_blocks_flushed_total = 1 — nhưng bộ truy vấn chỉ quét lại danh sách block mỗi 5 phút. Trong khoảng đó, mọi truy vấn đều trả rỗng dù dữ liệu nằm ngay trên đĩa.

3. query_backend_after mặc định là 15 phút. Tempo chỉ tìm trong kho lưu trữ với dữ liệu cũ hơn 15 phút; mới hơn thì nó tìm trong ingester. Tôi vừa khởi động lại Tempo nên ingester rỗng, còn kho thì "chưa đủ tuổi" — dữ liệu rơi vào đúng khoảng trống giữa hai nơi.

4. Và một lỗi của chính tôi: tôi đặt hai tham số trên vào querier.search thay vì query_frontend.search. Tempo từ chối khởi động với thông báo field query_backend_after not found in type querier.SearchConfig, chỉ đúng dòng sai.

Lỗi thứ tư là lỗi duy nhất ồn ào, và vì thế là lỗi duy nhất vô hại — tôi sửa nó trong hai phút. Ba lỗi đầu, mỗi lỗi tốn một lượt đo đầy đủ để loại trừ.

Nghĩa là gì trong thực tế

  • Đo dung lượng bằng du không có cờ -b, và luôn liệt kê từng file trước khi tin con số tổng. Ba lần trong sê-ri này, con số tổng đã sai theo ba cách khác nhau.
  • Chọn theo cách bạn hay tra cứu. Nếu quy trình của bạn là "copy trace_id từ log rồi dán vào ô tìm kiếm", Jaeger nhanh hơn. Nếu là "tìm mọi trace chậm của dịch vụ X trong giờ qua", Tempo nhanh hơn tám lần và tốn ít đĩa hơn ba lần.
  • Khi dựng Tempo và không thấy dữ liệu, hãy kiểm theo đúng thứ tự trên. Ba tham số mặc định đó là ba nguyên nhân phổ biến nhất, và cả ba đều không sinh ra lỗi nào.
  • Đọc log của hệ lưu trữ sau khi nạp lần đầu. LIVE_TRACES_EXCEEDED nằm ở đó suốt, tôi chỉ không nhìn.

Chỗ tôi không kết luận được

Cả hai đều chạy ở cấu hình một tiến trình đơn. Jaeger all-in-one và Tempo single-binary là chế độ để thử, không phải chế độ chạy thật. Cụm thật tách distributor, ingester, querier và compactor, dùng lưu trữ đối tượng thay cho đĩa cục bộ — và cả dung lượng lẫn độ trễ đều sẽ khác.

20 000 trace là quá nhỏ. Toàn bộ dữ liệu của cả hai nằm trong bộ đệm trang; không có lần đọc đĩa thật nào. Con số 24 ms so với 3 ms là khoảng cách giữa hai cách tổ chức dữ liệu trong bộ nhớ, không phải trên đĩa.

Dữ liệu của tôi rất đều: bốn span mỗi trace, năm điểm cuối, cùng bộ thuộc tính. Trace thật có độ sâu khác nhau, thuộc tính khác nhau, và điều đó ảnh hưởng trực tiếp tới tỷ lệ nén của parquet — nhiều khả năng theo hướng bất lợi cho Tempo.

Tôi không đo phần nén của Jaeger sau khi memtable được xả. Con số 16,48 MB là .sst tại thời điểm đo; nếu chờ thêm để 52 MB memtable được nén xuống, tổng dữ liệu ổn định sẽ lớn hơn. Nghĩa là tỷ lệ 3,3 lần là cận dưới cho khoảng cách dung lượng, không phải con số cuối cùng.

Thử ba mươi giây

# Ba cach do cung mot thu muc - chay tren bat ky kho du lieu nao cua ban
D=/var/lib/docker            # doi thanh thu muc du lieu can do

echo "khai bao (du -sb)   : $(du -sb  $D 2>/dev/null | cut -f1 | numfmt --to=iec)"
echo "thuc chiem (du -s)  : $(du -s   $D 2>/dev/null | cut -f1 | numfmt --from-unit=1024 --to=iec)"
echo
echo "nam file lon nhat, khai bao so voi thuc chiem:"
find $D -type f -printf '%s %b %p\n' 2>/dev/null | sort -rn | head -5 | \
  while read khai block ten; do
    printf "  %10s  %10s  %s\n" \
      "$(numfmt --to=iec $khai)" "$(numfmt --from-unit=512 --to=iec $block)" "$(basename $ten)"
  done

Nếu hai cột đầu của một dòng nào đó chênh nhau nhiều lần, bạn vừa tìm thấy một file thưa — và mọi con số dung lượng bạn từng báo cáo về thư mục này đều cần tính lại.