Danh sách bài viết

Bài 49: Monitoring với Prometheus + Grafana

Hands-on Prometheus và Grafana cho AI app: cấu hình scrape /metrics, viết PromQL cho latency percentile và error rate, xây dashboard với LLM-specific panel (token cost, TTFT, cache hit rate), thiết lập alert rule cho Alertmanager, GPU monitoring với DCGM Exporter, và tránh pitfall cardinality bomb.

27/05/2026
0 lượt xem
1

Mục tiêu bài học

Sau bài này bạn sẽ:

  • ✅ Hiểu kiến trúc Prometheus scrape + Grafana visualize và vai trò từng component
  • ✅ Expose /metrics từ FastAPI bằng prometheus-fastapi-instrumentator
  • ✅ Viết PromQL cho rate, percentile, error rate
  • ✅ Xây Grafana dashboard với panel cho LLM metrics (token cost, TTFT, cache hit rate)
  • ✅ Thiết lập alert rule với Prometheus Alertmanager và route đến Slack
  • ✅ Monitor GPU workload với DCGM Exporter
  • ✅ Biết các pitfall về cardinality và retention
2

Kiến trúc tổng thể

Prometheus và Grafana là hai project riêng biệt nhưng thường dùng cùng nhau:

Prometheus

Time-series database kết hợp với scrape engine. Hoạt động theo mô hình pull: Prometheus chủ động gọi HTTP GET đến endpoint /metrics của app theo interval cấu hình (mặc định 15 giây). Dữ liệu lưu trong TSDB (time-series database) trên disk local. Có thêm Alertmanager để gửi alert khi rule thỏa mãn.

Grafana

Visualization layer. Không lưu data; chỉ query từ data source (Prometheus, Loki, Elasticsearch, InfluxDB, ...) và render thành dashboard. Hỗ trợ cả alert từ Grafana 9+, nhưng alert rule thường đặt ở Prometheus để không phụ thuộc Grafana UI.

Luồng dữ liệu

AI app  ──expose──►  /metrics endpoint  (text format)
                           │
                    Prometheus scrape (mỗi 15s)
                           │
                      TSDB on disk
                           │
                   ┌───────┴────────┐
                Grafana query    Alertmanager
                (PromQL)         (rule eval)
                   │                │
              Dashboard         Slack / PagerDuty / email

Node Exporter

Agent cài trên host để expose OS metrics: CPU, RAM, disk I/O, network. Là exporter riêng, không cần sửa code app. Chạy trên port 9100.

Prometheus text format

Mỗi metric là một dòng key-value có thể kèm labels:

# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="POST",endpoint="/inference",status="200"} 1423
http_requests_total{method="POST",endpoint="/inference",status="500"} 12

# HELP http_request_duration_seconds HTTP request duration
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1"} 234
http_request_duration_seconds_bucket{le="0.5"} 891
http_request_duration_seconds_bucket{le="1.0"} 1201
http_request_duration_seconds_bucket{le="+Inf"} 1423
http_request_duration_seconds_sum 2847.3
http_request_duration_seconds_count 1423

Có 4 loại metric type: Counter (chỉ tăng), Gauge (tăng giảm tự do), Histogram (phân phối vào bucket), Summary (quantile tính sẵn). Với latency nên dùng Histogram để Prometheus tự tính quantile qua PromQL.

3

App expose /metrics — FastAPI

Cài thư viện

pip install prometheus-fastapi-instrumentator==7.0.0
pip install prometheus-client==0.21.0

Expose /metrics tự động

prometheus-fastapi-instrumentator tự đo latency, request count, in-progress request cho mọi endpoint FastAPI:

from fastapi import FastAPI
from prometheus_fastapi_instrumentator import Instrumentator

app = FastAPI()

# Instrument tất cả route và tự tạo /metrics endpoint
Instrumentator().instrument(app).expose(app)

@app.post("/inference")
async def inference(payload: dict):
    # logic xử lý
    return {"result": "..."}

Truy cập http://localhost:8000/metrics sẽ thấy output text format Prometheus. Không cần viết thêm code.

Thêm custom metric cho LLM

Các metric HTTP có sẵn không đủ cho AI app. Cần thêm metric riêng cho token, cost, TTFT:

from prometheus_client import Counter, Histogram, Gauge

# Tổng token đã dùng (counter — chỉ tăng)
llm_tokens_total = Counter(
    "llm_tokens_total",
    "Total tokens consumed",
    labelnames=["model", "direction"],  # direction: "input" | "output"
)

# Chi phí LLM tính bằng USD
llm_cost_usd_total = Counter(
    "llm_cost_usd_total",
    "Total LLM cost in USD",
    labelnames=["model"],
)

# Time To First Token (histogram để tính percentile)
llm_ttft_seconds = Histogram(
    "llm_ttft_seconds",
    "Time to first token in seconds",
    labelnames=["model"],
    buckets=[0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0],
)

# Cache hit / miss
cache_requests_total = Counter(
    "cache_requests_total",
    "Total cache lookup requests",
    labelnames=["result"],  # result: "hit" | "miss"
)

Ghi metric trong code xử lý LLM

import time

async def call_llm(model: str, messages: list) -> dict:
    start = time.perf_counter()

    # Gọi LLM API (ví dụ OpenAI)
    response = await openai_client.chat.completions.create(
        model=model,
        messages=messages,
        stream=False,
    )

    elapsed = time.perf_counter() - start
    usage = response.usage

    # Ghi metric
    llm_tokens_total.labels(model=model, direction="input").inc(usage.prompt_tokens)
    llm_tokens_total.labels(model=model, direction="output").inc(usage.completion_tokens)
    llm_ttft_seconds.labels(model=model).observe(elapsed)

    # Tính cost ví dụ cho gpt-4o-mini ($0.15/1M input, $0.60/1M output)
    cost = (usage.prompt_tokens * 0.15 + usage.completion_tokens * 0.60) / 1_000_000
    llm_cost_usd_total.labels(model=model).inc(cost)

    return response

Ghi cache metric

def lookup_cache(key: str) -> str | None:
    value = redis_client.get(key)
    if value:
        cache_requests_total.labels(result="hit").inc()
        return value.decode()
    else:
        cache_requests_total.labels(result="miss").inc()
        return None
4

Cài Prometheus với Docker

Chạy Prometheus container

docker run -d --name prom \
  -p 9090:9090 \
  -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus:v2.54.0

prometheus.yml

File config khai báo scrape target và interval:

global:
  scrape_interval: 15s       # Gọi /metrics mỗi 15 giây
  evaluation_interval: 15s   # Evaluate alert rule mỗi 15 giây

scrape_configs:
  - job_name: 'ai-api'
    static_configs:
      - targets: ['host.docker.internal:8000']
    metrics_path: '/metrics'

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['host.docker.internal:9100']

host.docker.internal là hostname trỏ đến host machine từ trong container (macOS/Windows). Trên Linux dùng IP của host hoặc dùng network_mode: host. Khi dùng Docker Compose, thay bằng service name (xem mục 6).

Verify Prometheus hoạt động

Truy cập http://localhost:9090 → tab Status → Targets. Mỗi target phải có state UP. Nếu DOWN, kiểm tra port và hostname.

Chạy Node Exporter

docker run -d --name node-exporter \
  -p 9100:9100 \
  prom/node-exporter:v1.8.2
5

Cài Grafana với Docker

docker run -d --name grafana \
  -p 3000:3000 \
  -e GF_SECURITY_ADMIN_PASSWORD=admin \
  grafana/grafana:11.2.0

Truy cập http://localhost:3000, đăng nhập admin/admin. Grafana sẽ yêu cầu đổi mật khẩu lần đầu.

Add Prometheus data source

  1. Vào Connections → Data sources → Add new data source
  2. Chọn Prometheus
  3. URL: http://prom:9090 (nếu dùng Docker Compose, service name là prom)
  4. Click Save & test — phải thấy "Data source is working"

Nếu chạy Grafana và Prometheus standalone (không Compose), URL là http://host.docker.internal:9090.

6

Docker Compose — full stack

Dùng Docker Compose để khởi động toàn bộ stack với 1 lệnh docker compose up -d:

docker-compose.yml

services:
  api:
    build: .
    ports:
      - "8000:8000"
    networks:
      - monitoring

  prometheus:
    image: prom/prometheus:v2.54.0
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - ./alerts.yml:/etc/prometheus/alerts.yml
      - prom_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:11.2.0
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:v1.8.2
    ports:
      - "9100:9100"
    networks:
      - monitoring

volumes:
  prom_data:
  grafana_data:

networks:
  monitoring:
    driver: bridge

prometheus.yml khi dùng Compose

Trong Compose, các service liên lạc qua tên service (thay host.docker.internal):

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "alerts.yml"

scrape_configs:
  - job_name: 'ai-api'
    static_configs:
      - targets: ['api:8000']
    metrics_path: '/metrics'

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

Volumes prom_datagrafana_data quan trọng: không khai báo thì mất toàn bộ metric và dashboard khi restart container.

7

PromQL — query language

PromQL là ngôn ngữ query của Prometheus. Mọi Grafana panel dùng PromQL để lấy data.

Rate — request/sec từ counter

Counter chỉ tăng, không thể dùng trực tiếp để biết RPS. Dùng rate() với time range:

rate(http_requests_total[5m])

Nghĩa: tốc độ tăng trung bình trong 5 phút gần nhất (đơn vị: req/sec). Window [5m] cần ít nhất 2 data point, nên scrape_interval phải nhỏ hơn window này.

Sum by label

sum by (endpoint) (rate(http_requests_total[5m]))

Gộp tất cả label khác, chỉ giữ nhóm theo endpoint. Kết quả: RPS từng endpoint.

Latency percentile từ histogram

histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

P95 latency toàn app. Muốn chia theo endpoint:

histogram_quantile(
  0.95,
  sum by (endpoint, le) (rate(http_request_duration_seconds_bucket[5m]))
)

Lưu ý: phải sum by (..., le) — giữ label le (less than equal) vì đó là bucket boundary của histogram.

Error rate %

100 * sum(rate(http_requests_total{status=~"5.."}[5m]))
      / sum(rate(http_requests_total[5m]))

status=~"5.." là regex match mọi 5xx status code.

LLM cost trong 1 giờ

sum(increase(llm_cost_usd_total[1h]))

increase() là lượng tăng tuyệt đối trong window. Khác rate() — rate là tốc độ (per second), increase là tổng.

Token per minute

sum(rate(llm_tokens_total[1m])) * 60

Cache hit rate %

100 * sum(rate(cache_requests_total{result="hit"}[5m]))
      / sum(rate(cache_requests_total[5m]))

TTFT P95 theo model

histogram_quantile(
  0.95,
  sum by (model, le) (rate(llm_ttft_seconds_bucket[5m]))
)
8

Grafana dashboard — pattern AI app

Tổ chức dashboard theo row. Grafana 11 hỗ trợ collapsible row để nhóm panel liên quan.

Row 1: Top-level health

4 Stat panel (single number) đặt cạnh nhau để nhìn tổng quan ngay lập tức:

Panel PromQL Unit
RPS sum(rate(http_requests_total[1m])) req/s
P95 Latency histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) * 1000 ms
Error Rate 100 * sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) %
Cache Hit Rate 100 * sum(rate(cache_requests_total{result="hit"}[5m])) / sum(rate(cache_requests_total[5m])) %

Dùng threshold để tô màu: Error Rate > 5% → đỏ, 1-5% → vàng, <1% → xanh.

Row 2: Latency detail

  • Time series: P50, P95, P99 theo endpoint (dùng 3 query với quantile 0.5/0.95/0.99).
  • Heatmap: dùng http_request_duration_seconds_bucket trực tiếp để xem phân phối latency theo thời gian. Panel type: Heatmap.

Row 3: LLM metrics

  • Stat — LLM Cost Today: sum(increase(llm_cost_usd_total[24h])) — unit USD.
  • Time series — Tokens/min: sum by (model) (rate(llm_tokens_total[1m])) * 60 — phân tách theo model.
  • Time series — TTFT P95: histogram_quantile(0.95, sum by (model, le) (rate(llm_ttft_seconds_bucket[5m]))).
  • Stat — Cache Hit Rate: query cache hit rate.

Nếu có custom metric hallucination rate từ evaluator:

100 * sum(rate(llm_eval_hallucination_total[1h]))
      / sum(rate(llm_eval_requests_total[1h]))

Row 4: System resources

  • CPU%: 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
  • RAM%: 100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
  • Disk I/O: rate(node_disk_read_bytes_total[5m]) + write
  • Network: rate(node_network_receive_bytes_total[5m])
9

Import dashboard có sẵn

grafana.com/grafana/dashboards có thư viện dashboard công cộng. Mỗi dashboard có ID dạng số, import nhanh qua UI:

  1. Vào Dashboards → Import
  2. Nhập ID, ví dụ 1860 cho "Node Exporter Full"
  3. Chọn Prometheus data source
  4. Click Import

Một số dashboard hữu ích:

ID Tên Dùng cho
1860 Node Exporter Full OS metrics: CPU, RAM, disk, network
7587 FastAPI Observability Tương thích prometheus-fastapi-instrumentator
15661 NVIDIA DCGM Exporter GPU metrics

Export JSON để version control

Grafana cho phép export dashboard thành JSON: Dashboard settings → JSON Model → Copy to clipboard. Lưu file dashboards/ai-app.json vào git repo để track thay đổi dashboard như code.

Có thể auto-provision dashboard khi khởi động Grafana qua volume mount:

# docker-compose.yml — thêm vào service grafana
volumes:
  - grafana_data:/var/lib/grafana
  - ./grafana/provisioning:/etc/grafana/provisioning
  - ./grafana/dashboards:/var/lib/grafana/dashboards
10

Alert rules

Alert rule viết trong file YAML, Prometheus evaluate theo evaluation_interval. Rule chỉ fire nếu thỏa mãn liên tục trong khoảng for: — tránh alert mỗi spike ngắn.

alerts.yml

groups:
  - name: ai-api
    rules:
      - alert: HighErrorRate
        expr: |
          100 * sum(rate(http_requests_total{status=~"5.."}[5m]))
          / sum(rate(http_requests_total[5m])) > 1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Error rate > 1% for 5 minutes"
          description: "Current error rate: {{ $value | printf \"%.2f\" }}%"

      - alert: HighLatency
        expr: |
          histogram_quantile(
            0.95,
            rate(http_request_duration_seconds_bucket[5m])
          ) > 2
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "P95 latency > 2s for 10 minutes"

      - alert: HighLLMCostRate
        expr: |
          sum(rate(llm_cost_usd_total[1h])) * 3600 > 5
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "LLM cost > $5/hour"

      - alert: ServiceDown
        expr: up{job="ai-api"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "AI API service is down"

Khai báo file rule trong prometheus.yml:

rule_files:
  - "alerts.yml"

Kiểm tra rule đã load: http://localhost:9090/rules. Kiểm tra alert đang active: http://localhost:9090/alerts.

11

Alertmanager — route alert ra Slack

Alertmanager là component riêng của Prometheus ecosystem. Nhận alert từ Prometheus, dedup, group, route đến receiver.

alertmanager.yml

global:
  resolve_timeout: 5m

route:
  group_by: ['alertname', 'severity']
  group_wait: 30s       # Chờ 30s để gom alert cùng nhóm
  group_interval: 5m    # Gửi lại mỗi 5 phút nếu vẫn firing
  repeat_interval: 4h   # Gửi nhắc lại sau 4h
  receiver: 'slack-warning'
  routes:
    - match:
        severity: critical
      receiver: 'slack-critical'

receivers:
  - name: 'slack-warning'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/T0/B0/XXXX'
        channel: '#alerts-warning'
        text: '{{ .CommonAnnotations.summary }}'

  - name: 'slack-critical'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/T0/B0/XXXX'
        channel: '#alerts-critical'
        text: '🔴 {{ .CommonAnnotations.summary }}'

Thêm Alertmanager vào Compose

  alertmanager:
    image: prom/alertmanager:v0.27.0
    ports:
      - "9093:9093"
    volumes:
      - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
    networks:
      - monitoring

Cập nhật prometheus.yml để biết địa chỉ Alertmanager:

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']

Ngoài Slack, Alertmanager hỗ trợ: PagerDuty, email, OpsGenie, webhook. Cấu hình tương tự, chỉ thay block trong receivers.

12

GPU monitoring — DCGM Exporter

NVIDIA DCGM Exporter (Data Center GPU Manager) expose GPU metrics cho Prometheus. Quan trọng với inference server hoặc training job.

Thêm vào docker-compose.yml

  dcgm-exporter:
    image: nvcr.io/nvidia/k8s/dcgm-exporter:3.3.5-3.4.1-ubuntu22.04
    runtime: nvidia
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
    ports:
      - "9400:9400"
    networks:
      - monitoring

Yêu cầu: NVIDIA Container Toolkit cài sẵn (nvidia-container-toolkit). Không có GPU thì skip service này.

Scrape config cho GPU

  - job_name: 'dcgm-exporter'
    static_configs:
      - targets: ['dcgm-exporter:9400']

Metrics DCGM Exporter expose

Metric Ý nghĩa
DCGM_FI_DEV_GPU_UTIL GPU utilization (%)
DCGM_FI_DEV_MEM_COPY_UTIL Memory bandwidth utilization (%)
DCGM_FI_DEV_FB_USED GPU memory used (MB)
DCGM_FI_DEV_FB_FREE GPU memory free (MB)
DCGM_FI_DEV_GPU_TEMP GPU temperature (°C)
DCGM_FI_DEV_POWER_USAGE Power consumption (W)

Ví dụ PromQL cho GPU memory usage %:

100 * DCGM_FI_DEV_FB_USED
      / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE)

Dùng dashboard Grafana ID 15661 để có panel GPU đầy đủ ngay mà không cần tự build.

13

Loki — log aggregation

Loki là log aggregation system của Grafana Labs, cùng codebase với Prometheus về thiết kế label. Khác Elasticsearch: Loki không index full-text, chỉ index labels. Điều này giúp storage nhỏ hơn nhiều nhưng query nội dung log chậm hơn.

LGTM stack

Grafana Labs đặt tên stack của họ là LGTM:

  • Loki — log aggregation, query bằng LogQL
  • Grafana — visualization
  • Tempo — distributed tracing
  • Mimir (hoặc Prometheus) — metrics

Trong Grafana, bạn có thể correlate: click vào log line của Loki → jump sang trace tương ứng trong Tempo → thấy span nào chậm. Đây là observability đầy đủ (metrics + logs + traces).

Thêm Loki vào stack (tùy chọn)

  loki:
    image: grafana/loki:3.2.0
    ports:
      - "3100:3100"
    networks:
      - monitoring

  promtail:
    image: grafana/promtail:3.2.0
    volumes:
      - /var/log:/var/log
      - ./promtail.yml:/etc/promtail/config.yml
    networks:
      - monitoring

Promtail là agent đọc log file và gửi lên Loki. Sau đó add Loki làm data source trong Grafana, query bằng LogQL.

Bài này tập trung vào Prometheus + Grafana cho metrics. Loki và Tempo là chủ đề riêng nếu cần đi sâu.

14

Retention và long-term storage

Retention mặc định

Prometheus mặc định giữ dữ liệu 15 ngày. Sau đó block cũ bị xóa tự động. Cấu hình trong docker-compose.yml:

command:
  - '--config.file=/etc/prometheus/prometheus.yml'
  - '--storage.tsdb.retention.time=30d'
  - '--storage.tsdb.retention.size=10GB'

Cả hai flag có thể dùng cùng nhau — block bị xóa khi vi phạm bất kỳ điều kiện nào.

Long-term storage

Prometheus local TSDB không phù hợp cho retention nhiều tháng hay multi-instance. Các giải pháp phổ biến:

Tool Đặc điểm
Thanos Open source, object storage (S3/GCS), HA Prometheus, downsampling
Mimir Grafana Labs, fully managed hay self-hosted, horizontal scale
VictoriaMetrics Open source, drop-in replacement TSDB, nhanh hơn Prometheus về write
Cortex CNCF project, tiền thân của Mimir

Với hầu hết AI app startup, 30 ngày retention local là đủ. Chỉ cần long-term storage khi cần trend analysis qua nhiều tháng hoặc compliance.

15

Cloud managed

Nếu không muốn tự maintain stack, có các lựa chọn managed:

Grafana Cloud

Grafana Labs cung cấp managed Prometheus + Grafana + Loki + Tempo trên cloud. Free tier: 10,000 metrics, 50GB logs, 50GB traces. App gửi metric về Grafana Cloud qua remote_write:

# prometheus.yml — thêm remote_write
remote_write:
  - url: https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push
    basic_auth:
      username: '12345'
      password: '${GRAFANA_CLOUD_TOKEN}'

AWS Managed Service for Prometheus (AMP) + Grafana (AMG)

Tích hợp với IAM, phù hợp nếu app chạy trên EKS/ECS. App gửi metric qua remote_write đến AMP endpoint. AMG kết nối AMP như data source. Không cần tự quản lý storage.

Datadog và New Relic

Proprietary APM với khả năng cao hơn (tracing, profiling, RUM, SLO tracking) nhưng chi phí cao hơn đáng kể so với stack open source. Phù hợp khi team không có capacity tự vận hành Prometheus.

16

Common pitfalls

Cardinality bomb

Đây là vấn đề phổ biến nhất. Cardinality = số lượng time series duy nhất. Số này bằng tích của tất cả giá trị có thể của label combinations.

Ví dụ nguy hiểm:

# ĐỪNG làm — user_id có thể là hàng triệu giá trị
requests_by_user = Counter(
    "http_requests_total",
    labelnames=["endpoint", "user_id"],  # ← BAD
)

100 endpoint × 1 triệu user_id = 100 triệu time series. Prometheus sẽ OOM. Labels an toàn là những giá trị có bounded cardinality: endpoint name, HTTP method, status code, model name.

Scrape interval quá ngắn

Interval 1 giây tạo load không cần thiết trên app và Prometheus. 15s là default phù hợp cho production. Chỉ giảm xuống 5s khi cần độ chính xác cao cho alert.

Quên for: trong alert rule

Nếu không có for:, alert fire ngay khi expression thỏa mãn lần đầu, kể cả spike ngắn. Kết quả: alert spam. Luôn đặt for: ít nhất bằng 2-3 lần scrape_interval.

Dashboard query nặng

Range 30 ngày kết hợp với sum() nhiều label → query có thể mất vài chục giây. Giải pháp: dùng recording rule để pre-compute query nặng, lưu kết quả như metric mới:

# prometheus.yml — recording rule
groups:
  - name: recordings
    interval: 1m
    rules:
      - record: job:http_request_duration_seconds:p95
        expr: |
          histogram_quantile(
            0.95,
            sum by (endpoint, le) (rate(http_request_duration_seconds_bucket[5m]))
          )

Dashboard dùng job:http_request_duration_seconds:p95 thay vì query full.

Không mount volume Prometheus

Chạy Prometheus container không có volume → restart container là mất toàn bộ metric. Luôn mount prom_data:/prometheus trong Compose.

Alert không có receiver

Prometheus có thể evaluate rule và gửi alert đến Alertmanager, nhưng nếu không cấu hình Alertmanager hoặc receiver không hợp lệ, alert chỉ hiển thị trong UI mà không notify. Test receiver bằng amtool hoặc Alertmanager web UI.

Histogram bucket không cover range thực tế

Nếu LLM latency thường là 2-10 giây nhưng bucket chỉ đến 1.0, hầu hết observation rơi vào bucket +Inf. Percentile tính ra sẽ sai. Định nghĩa bucket phù hợp với SLO thực tế của app:

llm_ttft_seconds = Histogram(
    "llm_ttft_seconds",
    "Time to first token",
    buckets=[0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0, 30.0],  # Cover đến 30s
)
17

Tóm tắt

✅ Prometheus scrape /metrics mỗi 15s, lưu time-series trong TSDB local

prometheus-fastapi-instrumentator expose HTTP metrics tự động; thêm Counter/Histogram riêng cho LLM token, cost, TTFT

✅ PromQL: rate() cho counter, histogram_quantile() cho percentile, increase() cho total trong window

✅ Grafana dashboard tổ chức theo row: top-level health → latency → LLM metrics → system

✅ Alert rule cần for: để tránh noise; Alertmanager route đến Slack/PagerDuty

✅ DCGM Exporter expose GPU utilization, memory, temperature cho inference workload GPU

✅ Tránh cardinality bomb — không dùng user_id hoặc trace_id làm label Prometheus

✅ Mount volume prom_datagrafana_data để dữ liệu tồn tại qua restart