Hãy nghĩ về kho lưu trữ chỉ số như một cái kho chứa đồ theo tháng. Trực giác đầu tiên ai cũng có: kho càng đầy thì tìm một món càng lâu. Nhưng cái kho này được thiết kế lạ — mỗi tháng đồ được đóng vào một thùng riêng có nhãn, và khi bạn cần món của tuần trước, bạn chỉ mở đúng cái thùng gần nhất, chẳng đụng tới hàng trăm thùng cũ. Thế nên chứa một năm hay mười năm, việc lấy đồ của tuần này tốn y hệt nhau. Cái tăng lên chỉ là tiền thuê kho. Kho chỉ số của Prometheus đúng là cái kho đó, và bài này tôi đo cho ra ba con số mà mọi đội cần để quyết định thuê bao nhiêu.

Phần trước đã cộng tổng chi phí của việc quan sát: 12,9% thông lượng và hàng chục gigabyte mỗi ngày. Câu hỏi kế tiếp là câu mà mọi đội đều phải trả lời và hầu như không đội nào trả lời bằng số: giữ dữ liệu đó bao lâu? Tôi đo hai vế của câu hỏi — chi phí lưu và chi phí truy vấn — rồi nói rõ vế nào tôi không đo được. (Đo bằng Prometheus 2.54.1 thật; phần dung lượng ngoại suy từ 2,16 byte/mẫu đã đo ở phần 19, không phải perf counter.)

Bảng số liệu

Prometheus 2.54.1 với 900 chuỗi. Phần truy vấn đo trực tiếp; phần dung lượng ngoại suy từ 2,16 byte mỗi mẫu — con số đo được ở phần 19 của sê-ri này.

Chi phí truy vấn theo độ dài khoảng xem (bước cố định 15 giây):

Khoảng xem Truy vấn gộp (5 đường) Truy vấn không gộp (900 đường)
30 giây 3 ms · 15 điểm · 0,7 KB 3 ms · 2 700 điểm · 189,0 KB
1 phút 3 ms · 25 điểm · 1,1 KB 4 ms · 4 500 điểm · 248,0 KB
2 phút 6 ms · 45 điểm · 1,7 KB 7 ms · 8 100 điểm · 366,2 KB
3 phút 7 ms · 65 điểm · 2,4 KB 8 ms · 11 700 điểm · 484,3 KB

Chi phí lưu trữ theo chu kỳ thu thập:

Chu kỳ Mỗi ngày
1 giây 160,2 MB
15 giây 10,7 MB
1 phút 2,7 MB

Và theo thời gian giữ (chu kỳ 15 giây):

Giữ Dung lượng
7 ngày 0,07 GB
30 ngày 0,31 GB
90 ngày 0,94 GB
1 năm 3,81 GB

Bốn điều đáng nhớ

Dung lượng tăng tuyến tính, chi phí truy vấn tăng dưới tuyến tính. Khoảng xem gấp 6 lần chỉ làm thời gian truy vấn gấp 2,4 lần và dữ liệu trả về gấp 2,6 lần, vì mỗi truy vấn có một khoản chi phí cố định không đổi. Hệ quả thực dụng, và là chỗ tôi thấy nhiều người hiểu sai: giữ dữ liệu lâu hơn không làm bảng điều khiển hằng ngày chậm đi. Truy vấn một giờ gần nhất tốn như nhau dù kho có bảy ngày hay một năm dữ liệu. Giữ lâu chỉ làm hoá đơn dài ra — đúng cái kho đóng thùng theo tháng.

Đòn bẩy lớn nhất không phải thời gian giữ — mà là chu kỳ thu thập. Từ 1 giây sang 15 giây giảm 15 lần dung lượng, và với phần lớn chỉ số thì độ phân giải 15 giây là quá đủ. So sánh: rút thời gian giữ từ 90 ngày xuống 30 ngày chỉ tiết kiệm 3 lần, và bạn mất khả năng so sánh với quý trước. Người ta hay vặn nhầm cái núm.

Đòn bẩy lớn thứ hai là gộp. Cùng một khoảng xem 3 phút, truy vấn gộp trả về 2,4 KB còn truy vấn không gộp trả về 484,3 KB — gấp 202 lần. Phần 18 đã đo rằng gộp không làm Prometheus nhẹ đi, nhưng nó cứu băng thông và trình duyệt đúng theo tỷ lệ này.

Vì sao thời gian giữ không đụng tới truy vấn ngắn

Prometheus lưu dữ liệu thành các khối theo khoảng thời gian, mỗi khối tự chứa chỉ mục riêng — đúng cái thùng có nhãn. Truy vấn một giờ gần nhất chỉ chạm khối gần nhất; các khối cũ hơn không được mở ra. Đó là lý do thời gian giữ không ảnh hưởng tới truy vấn ngắn — chúng ở những tệp khác nhau.

Chi phí cố định của mỗi truy vấn gồm phân tích biểu thức, tra chỉ mục để tìm chuỗi khớp, và dựng phản hồi JSON. Với khoảng xem ngắn, phần cố định đó chiếm phần lớn thời gian; đó là lý do đường cong dưới tuyến tính. Về dung lượng thì không có gì cứu được: mỗi mẫu là 2,16 byte, số mẫu bằng số chuỗi nhân số lần thu thập, và cả hai đều tuyến tính. Không có điểm gãy kỹ thuật nào để bám vào — không có ngưỡng mà sau đó việc giữ thêm trở nên đắt bất thường hay rẻ bất thường.

Điều đó có nghĩa là quyết định "giữ bao lâu" hoàn toàn là quyết định về giá trị, không phải về kỹ thuật. Kỹ thuật chỉ cho bạn giá; nó không cho bạn câu trả lời. Và tôi thấy đó là điều thành thật nhất một bài đo có thể nói: có những câu hỏi mà số liệu chỉ định khung, còn quyết định là của con người.

Nghĩa là gì trong công việc hằng ngày

  • Sửa chu kỳ thu thập trước khi sửa thời gian giữ. Đây là cách tiết kiệm rẻ nhất về mặt thông tin mất đi. Với chỉ số có đột biến ngắn thì hãy cẩn thận — phần 33 đã đo rằng chu kỳ thưa giấu mất đỉnh.
  • Chọn thời gian giữ theo chu kỳ ra quyết định của đội. Nếu bạn xem lại chỉ số theo quý thì cần 90 ngày; nếu chỉ dùng để gỡ sự cố thì 7–14 ngày là đủ và rẻ hơn mười lần.
  • Đừng giữ mọi thứ cùng một khoảng. Chỉ số hạ tầng thô có thể giữ 7 ngày; chỉ số cấp SLO nên giữ hàng năm vì chúng là cái bạn báo cáo. Prometheus không hỗ trợ điều đó trực tiếp, nhưng các hệ lưu trữ dài hạn thì có.
  • Ước lượng trước khi bật. 900 chuỗi × chu kỳ 15 giây = 10,7 MB mỗi ngày. Nhân với số chuỗi thật của bạn — phần 11 đã đo rằng một Histogram gắn nhãn sai có thể nhân số chuỗi lên hàng nghìn lần, và đó mới là thứ làm hoá đơn nổ chứ không phải thời gian giữ.

Cùng một cấu trúc chi phí "lưu tuyến tính, đọc dưới tuyến tính", đổi ngăn xếp

Chuyện "lưu trữ dạng khối theo thời gian khiến dữ liệu cũ không làm chậm truy vấn gần đây, và cái đắt là dung lượng chứ không phải tốc độ" không riêng gì Prometheus — nó là kiến trúc chung của mọi hệ time-series và log.

Trong thế giới Java, Elasticsearch (nền của nhiều hệ log/APM trong giới Java) dùng đúng mô hình này với index theo thời gian và ILM (Index Lifecycle Management): index của tuần này nóng và nhanh, index cũ chuyển sang tầng warm/cold rẻ hơn, và truy vấn "7 ngày gần nhất" chỉ chạm index nóng. Quyết định giữ bao lâu ở đó cũng là chính sách ILM, và cũng là quyết định về giá trị chứ không phải kỹ thuật — y hệt kết luận bài này.

Trong Go — ngôn ngữ viết Prometheus, VictoriaMetrics, Loki — cùng triết lý khối-theo-thời-gian là nền của khả năng "giữ lâu mà không phạt truy vấn gần". Loki (log) lưu chunk theo nhãn+thời gian y như Prometheus lưu chunk chỉ số, nên bài học "vặn chu kỳ/cardinality trước khi vặn retention" chuyển nguyên vẹn sang cả log.

Còn ở cơ sở dữ liệu, đây chính là lý do TimescaleDB và các time-series DB dùng hypertable chia thành chunk theo khoảng thời gian: một truy vấn WHERE time > now() - 1h chỉ quét chunk mới nhất nhờ "chunk exclusion", nên bảng có một năm dữ liệu không làm truy vấn giờ gần nhất chậm đi. PostgreSQL thường cũng đạt điều tương tự bằng partition theo range thời gian — cùng ý tưởng thùng-có-nhãn.

Mẫu số chung: khi dữ liệu được chia thành khối theo thời gian và mỗi khối tự đánh chỉ mục, chi phí lưu tăng tuyến tính theo lượng giữ nhưng chi phí đọc gần đây gần như phẳng — nên "giữ bao lâu" tách hẳn thành một bài toán ngân sách, không phải bài toán hiệu năng. Đòn bẩy tiết kiệm thật nằm ở độ phân giải (chu kỳ) và số chuỗi (cardinality), không ở thời gian giữ, vì hai cái đầu nhân vào mọi ngày còn cái sau chỉ cộng thêm ngày. Nên câu hỏi đúng khi hoá đơn lưu trữ phình không phải "tôi có giữ quá lâu không" mà là "tôi có đang thu quá dày, hay sinh quá nhiều chuỗi không" — đó gần như luôn mới là biến sai.

Muốn tự tính hoá đơn của chính Prometheus mình, chạy:

# Ngan sach luu tru cua chinh Prometheus cua ban
P=http://localhost:9090

curl -s -G "$P/api/v1/query" --data-urlencode \
  'query=prometheus_tsdb_head_series' \
  | python3 -c 'import sys,json;r=json.load(sys.stdin)["data"]["result"];print("so chuoi        :", int(float(r[0]["value"][1])) if r else "?")'

curl -s -G "$P/api/v1/query" --data-urlencode \
  'query=rate(prometheus_tsdb_head_samples_appended_total[5m])' \
  | python3 -c '
import sys,json
r=json.load(sys.stdin)["data"]["result"]
if r:
    mps=float(r[0]["value"][1])
    ngay = mps*86400*2.16/1073741824
    print("mau moi giay    : %.0f" % mps)
    print("uoc %.2f GB moi ngay  ->  %.1f GB cho 30 ngay, %.1f GB cho 1 nam" % (ngay, ngay*30, ngay*365))
'

Ba dòng đó là toàn bộ hoá đơn của bạn. Nếu con số một năm làm bạn giật mình, hãy nhìn lại số chuỗi trước khi nhìn lại thời gian giữ — nó thường là biến sai.