Bỏ qua để đến nội dung
Knowledge Hub

Metric cardinality cao: một label có thể tạo hàng nghìn time series Trung cấp

Thời gian đọc ước tính: 6 phút

Sau một lần deploy, bộ nhớ Prometheus tăng, scrape chậm, dashboard query lâu hơn, trong khi traffic không đổi. Trong diff có một thay đổi tưởng vô hại: thêm label user_id vào counter http_requests_total để “dễ lọc theo khách hàng”.

Cardinality là số tổ hợp giá trị label khác nhau của một metric; mỗi tổ hợp là một time series riêng. Label có giá trị không giới hạn (user ID, email, request ID, URL chứa ID) biến metric thành kho tra cứu sự kiện, việc mà metric không được thiết kế để làm.

Ba route template, hai nhóm status, 100 user giả: 3 × 2 = 6 series; thêm user_id thành 3 × 2 × 100 = 600.

Đang tải diagram…

Con số là trần tổ hợp xuất hiện, không phải số byte RAM. Bảng dưới cho thấy chỗ nào bắt đầu đáng lo:

Số series Ví dụ Mức rủi ro
6 route × status Không đáng kể
600 thêm 100 user Vẫn nhỏ, nhưng là mô hình sai; user thật là hàng triệu
400.000 50 route × 5 method × 10 status × 8 region × 20 instance Phải có series budget và theo dõi
10 triệu thêm label không giới hạn Sự cố; không dựa vào quy tắc “bao nhiêu KB mỗi series” vì phụ thuộc phiên bản và workload

Histogram còn nhân thêm số bucket (cộng _count, _sum); label không giới hạn trên histogram càng đắt. instance do Prometheus gắn lúc scrape, cũng là một hệ số nhân theo số replica.

Chạy mô phỏng tổ hợp (không cần Prometheus):

Terminal window
npx --prefix examples ts-node examples/src/metric-cardinality/cardinality-demo.ts

Đổi 100 thành 10_000 để thấy số tổ hợp tăng. Chỉ tổ hợp thực sự được ghi mới tạo series; giá trị động liên tục thì tạo churn (series cũ ngừng, series mới xuất hiện), nên chi phí còn phụ thuộc tốc độ thay đổi.

Câu hỏi và công cụ tương ứng:

Muốn biết Dùng
Tổng active series và xu hướng prometheus_tsdb_head_series
Tốc độ tạo series mới (churn) rate(prometheus_tsdb_head_series_created_total[5m])
Metric nào nhiều series nhất topk(10, count by (__name__) ({__name__=~".+"})) (query nặng, chạy có kiểm soát)
Label nào nhiều giá trị nhất GET /api/v1/status/tsdb, đọc seriesCountByMetricName, labelValueCountByLabelName, memoryInBytesByLabelName, seriesCountByLabelValuePair
Phân tích sâu một block promtool tsdb analyze <db path>

Endpoint /api/v1/status/tsdb nhận tham số limit (mặc định 10, tối đa 10000) và là endpoint vận hành, không nên public. So sánh trước và sau deploy để tìm metric hoặc label mới; tổng memory một mình chưa đủ kết luận vì retention, ingest và query load đều ảnh hưởng.

Trong code, review mọi chỗ gọi .labels(...): tập giá trị tối đa là bao nhiêu, client có điều khiển được giá trị không? Kiểm tra cả middleware tự xuất metric, nó hay dùng URL thô nếu chưa cấu hình route normalization.

Guardrail: cho scrape fail thay vì để Prometheus OOM

Phần tiêu đề “Guardrail: cho scrape fail thay vì để Prometheus OOM”

Mặc định các giới hạn này là 0 (không giới hạn). Khi vượt sample_limit, label_limit, label_name_length_limit hoặc label_value_length_limit, cả lần scrape bị coi là thất bại, nên bạn thấy up == 0 thay vì mất bộ nhớ.

scrape_configs:
- job_name: orders-api
sample_limit: 20000 # số sample tối đa mỗi lần scrape (sau metric relabeling)
label_limit: 30 # số label tối đa trên một sample
label_value_length_limit: 200 # byte
target_limit: 50 # số target tối đa của job (tính năng experimental)

Chọn số theo quan sát thực tế của từng job, cộng margin. Giới hạn chỉ chặn ở ranh giới scrape; label từ input vẫn phải được chuẩn hóa trong code.

Series budget: ước tính tích số label × số replica cho từng metric family trước khi merge, và alert trên prometheus_tsdb_head_series cùng churn.

Review label bằng bảng:

Label Tối đa bao nhiêu giá trị Bị chặn trên chưa Owner
route số route template Có (dùng template, route lạ thành unmatched) Team API
status_class 5 Có Team API
tier 3 (free, paid, enterprise) Có Team Billing
user_id hàng triệu Không, cấm

Thay thế khi cần tra cá thể:

  • Exemplar (metric trỏ tới trace_id) và log hoặc trace có retention và phân quyền, xem logs, metrics, traces.
  • Chuẩn hóa label từ input thành tập hữu hạn; test rằng input lạ rơi vào nhóm fallback.
  • Native histogram giảm số series do bucket so với classic histogram.
  • Cần phân tích theo dimension cao: dùng event store hoặc hệ thống analytics, không dùng metric.
  1. Mỗi label mới có số giá trị tối đa, đã bị chặn trên và có owner (bảng review label ở trên).
  2. Không dùng user_id, request_id, email hay raw path chứa ID làm label; dùng route template, route lạ thành unmatched.
  3. Ước tính series budget (tích số label × số replica, nhân thêm bucket với histogram) trước khi merge.
  4. Đặt sample_limit và label_limit cho từng job theo quan sát thực tế cộng margin.
  5. Alert trên prometheus_tsdb_head_series và churn rate(prometheus_tsdb_head_series_created_total[5m]).
  6. Sau deploy, so /api/v1/status/tsdb trước và sau để tìm metric hoặc label mới.
  7. Khi Prometheus quá tải: chặn nguồn (rollback hoặc bỏ label) trước, metric_relabel_configs để giảm áp lực, không xóa TSDB bằng tay.

Tự kiểm tra: (1) Vì sao thêm 100 user ID biến 6 tổ hợp thành tối đa 600? (2) Khi vượt sample_limit, điều gì xảy ra với lần scrape? (3) Cần tra cứu một request cụ thể thì dùng metric, log hay trace?