# Model sai mà không báo lỗi: tự dựng hệ thống bắt data drift bằng Prometheus và Grafana

> Model vẫn trả HTTP 200 chỉ sau vài mili giây, kể cả khi dữ liệu khách hàng đã khác hẳn dữ liệu nó từng học. Hướng dẫn này giúp bạn dựng một hệ thống nhận ra chuyện đó trước khi khách hàng phát hiện.

Bản gốc: https://fdetimes.net/vi/bach-khoa/giam-sat-model-va-data-drift-voi-prometheus-grafana/

Một model chấm điểm gian lận có thể trả kết quả trong vài mili giây, trả HTTP 200 cho mọi request và không ghi dòng lỗi nào vào log. Dù vậy nó vẫn có thể đang sai. Khi dữ liệu production đã khác đáng kể so với dữ liệu huấn luyện, tức là data drift, model không biết để báo.

Chỉ một hệ thống được dựng riêng để đo phân bố dữ liệu mới nhận ra chuyện đó.

Với một FDE, chuyện này nằm ngay trong việc hằng ngày. Bạn deploy model lên hạ tầng của khách hàng, và vài tuần sau sẽ có người hỏi: "Sao dạo này model kém thế?" Nếu lúc đó bạn đã có sẵn một dashboard chỉ ra feature nào bắt đầu lệch từ ngày nào, bạn đang trả lời bằng dữ liệu. Nếu chưa có, bạn chỉ có thể đoán.

Bài này dựng đúng hệ thống đó với ba công cụ. Evidently thu thập và tính metric, Prometheus lưu metric, còn Grafana hiển thị và cảnh báo.

## Bạn sẽ dựng gì, và cần chuẩn bị gì?

Kết quả cuối cùng gồm hai tiến trình Python. Service phục vụ model cung cấp metric về số dự đoán và latency qua một endpoint HTTP. Một job drift chạy định kỳ cung cấp điểm drift qua endpoint thứ hai.

Prometheus định kỳ đến cả hai endpoint để kéo số liệu về, Grafana có một panel vẽ điểm drift của từng feature theo thời gian, và bạn nhận cảnh báo khi điểm vượt ngưỡng.

Bạn cần Python 3, một model đã huấn luyện (model phân loại scikit-learn nào cũng được), Prometheus và Grafana cài trên máy hoặc chạy bằng container. Bạn cũng cần một tập dữ liệu tham chiếu, thường là chính dữ liệu đã dùng để huấn luyện. Thiếu tập này thì không có gì để so.

Có một điểm kiến trúc cần nắm trước khi viết code. Prometheus thu thập time series theo mô hình pull qua HTTP: mỗi tiến trình chỉ cần cung cấp metric qua endpoint, còn Prometheus tự tìm đến để lấy. Vì thế bạn không phải viết đoạn code nào để đẩy metric đi đâu cả.

## Bước 1: chọn đúng kiểu metric cho từng câu hỏi

Prometheus cung cấp client library để instrument code ứng dụng, và service phục vụ model cũng chỉ là một ứng dụng. Muốn giám sát model, bạn cần trả lời ba câu hỏi, và mỗi câu hỏi hợp với một kiểu metric.

| Câu hỏi | Kiểu metric | Lý do |
|---|---|---|
| Model đã trả bao nhiêu dự đoán? | Counter | Chỉ tăng đơn điệu, hợp với việc đếm |
| Suy luận mất bao lâu? | Histogram | Đếm từng lần đo vào các bucket cấu hình được |
| Dữ liệu đang lệch bao nhiêu? | Gauge | Giá trị có thể tăng hoặc giảm tùy ý |

Hai câu hỏi đầu được trả lời ngay trong service phục vụ model. Dưới đây là phác thảo tối giản dùng client library Python. Tên hàm có thể khác nhau giữa các phiên bản, nên bạn hãy đối chiếu với docs của bản đang cài.

```python
# serve.py — phác thảo, kiểm tra lại API trong docs client library
from prometheus_client import Counter, Histogram, start_http_server

PREDICTIONS = Counter("predictions_total", "Số dự đoán đã trả về")
LATENCY = Histogram("inference_seconds", "Thời gian suy luận")

start_http_server(8000)

def predict(x):
with LATENCY.time():
y = model.predict(x)
PREDICTIONS.inc()
return y
```

**Kiểm tra:** gửi vài request vào hàm predict, rồi mở `localhost:8000/metrics` trên trình duyệt. Bạn phải thấy `predictions_total` tăng sau mỗi request và các dòng bucket của `inference_seconds`.

## Bước 2: tính drift theo lô, trong một tiến trình có endpoint riêng

Drift là đặc tính của một phân bố, mà một request đơn lẻ thì không có phân bố. Theo kiến trúc Evidently mô tả, Evidently đọc log của model, so dữ liệu gần đây với tập tham chiếu, rồi cung cấp một endpoint để Prometheus vào lấy.

Chi tiết "endpoint riêng" quan trọng hơn vẻ ngoài của nó. Gauge nằm trong bộ nhớ của tiến trình đã tạo ra nó. Một job chạy định kỳ ở tiến trình khác không thể ghi vào Gauge của service phục vụ model. Vì thế job drift phải tự tạo Gauge và tự cung cấp metric trên một cổng khác, ở đây là 8001.

Bạn cũng có thể chạy phép tính drift như một luồng nền bên trong chính service, nhưng tách riêng giúp phép tính nặng không làm chậm suy luận.

```python
# drift_job.py — giả mã. compute_drift() đại diện cho Report Data Drift của Evidently;
# API Evidently thay đổi theo phiên bản, xem docs hiện hành.
from prometheus_client import Gauge, start_http_server

DRIFT = Gauge("feature_drift_score", "Điểm drift theo feature", ["feature"])
start_http_server(8001)

while True:
current_window = load_recent_prediction_log()   # đọc log của service
for feature in FEATURES:
score = compute_drift(reference[feature], current_window[feature])
DRIFT.labels(feature=feature).set(score)
sleep_until_next_run()                          # ví dụ: mỗi giờ một lần
```

Thử hình dung một model gian lận có 20 feature. Mỗi feature là một giá trị nhãn của cùng một Gauge, nên bạn có 20 time series. Evidently cho biết ví dụ Data Drift này áp dụng được theo cùng cách cho các Report khác, nên sau này bạn có thể thêm metric về chất lượng dữ liệu mà không phải đổi kiến trúc.

**Kiểm tra:** sau lượt chạy đầu tiên, `localhost:8001/metrics` phải có 20 dòng `feature_drift_score{feature="..."}`, mỗi dòng mang một giá trị.

## Bước 3: cấu hình Prometheus để kéo metric về

Trong file cấu hình, `scrape_interval` quy định Prometheus kéo metric về thường xuyên đến mức nào. Vì có hai tiến trình, bạn cần khai báo hai target. File dưới đây đã được rút gọn để minh họa.

```yaml
# prometheus.yml (rút gọn)
global:
scrape_interval: 15s
scrape_configs:
- job_name: "fraud-model"
static_configs:
- targets: ["localhost:8000"]
- job_name: "drift-monitor"
static_configs:
- targets: ["localhost:8001"]
```

Tính thử một chút. Với chu kỳ 15 giây, mỗi time series nhận 240 mẫu mỗi giờ. Nhưng nếu job drift chỉ chạy mỗi giờ một lần, 239 mẫu trong số đó lặp lại đúng một giá trị. Điều này không sai, chỉ là bạn cần hiểu khi đọc biểu đồ: đường drift đi theo bậc thang theo chu kỳ của job, không theo chu kỳ scrape.

Khởi động Prometheus với file này (trang Getting Started có lệnh đúng cho bản bạn cài), mở giao diện web và gõ truy vấn PromQL đơn giản nhất là tên metric `feature_drift_score`.

**Kiểm tra:** trang targets phải báo cả `fraud-model` lẫn `drift-monitor` đang hoạt động. Nếu thiếu một job, thường là sai port hoặc tiến trình đó chưa chạy.

## Bước 4: dựng panel và cảnh báo trên Grafana

Trong Grafana, thêm Prometheus làm data source, tạo một panel time series và dùng truy vấn `feature_drift_score`. Mỗi feature sẽ thành một đường riêng. Đây là biểu đồ bạn sẽ mở ra khi khách hàng hỏi "model có vấn đề gì không".

Tiếp theo là cảnh báo. Grafana cho phép đặt cảnh báo qua email, Slack hoặc SMS theo ngưỡng tùy chỉnh. Tutorial của DataCamp dùng ngưỡng 0.026, nghĩa là hệ thống gửi cảnh báo khi điểm drift vượt mức đó. Để thử, bạn có thể viết điều kiện kiểu `feature_drift_score > 0.026`.

**Kiểm tra:** lấy dữ liệu test, nhân giá trị của một feature lên gấp đôi rồi cho vào cửa sổ dữ liệu hiện tại. Đường của feature đó phải vọt lên và cảnh báo phải đến kênh bạn đã cấu hình.

**Điểm mấu chốt:** Con số 0.026 là ví dụ để học cách dựng cảnh báo, không phải ngưỡng đúng cho dữ liệu của khách hàng.

## Ngưỡng đúng đến từ dữ liệu của chính khách hàng

Cách an toàn là cho hệ thống chạy ở chế độ chỉ quan sát vài tuần, trong lúc model đang chạy tốt, rồi đặt ngưỡng từ chính những con số đó. Thử hình dung job drift chạy mỗi giờ trong 4 tuần: mỗi feature có 4 × 7 × 24 = 672 điểm baseline.

Sắp xếp 672 điểm đó và lấy percentile thứ 99. Vì 1% của 672 là 6,72, giá trị này nằm quanh điểm cao thứ bảy. Giả sử nó là 0.04, nghĩa là 99% số giờ bình thường có điểm drift không vượt 0.04.

Bạn có thể đặt ngưỡng cao hơn một khoảng, chẳng hạn 0.05, để chỉ những lần lệch thật sự bất thường mới gây cảnh báo. Các con số ở đây là giả định, còn cách tính thì áp dụng được cho dữ liệu thật.

Nên tính riêng cho từng feature quan trọng, vì có feature tự nhiên dao động mạnh hơn feature khác. Với PromQL, bạn có thể dùng `quantile_over_time` trên khoảng thời gian baseline. Hãy kiểm tra cú pháp trong docs Prometheus trước khi dùng.

## Những lỗi làm hệ thống giám sát vô dụng

Lỗi đầu tiên là dùng Counter cho điểm drift. Counter chỉ tăng, nên khi drift giảm thì metric không phản ánh được, và biểu đồ của bạn sẽ báo sai. Mọi giá trị biểu thị "trạng thái hiện tại", như điểm drift hay độ chính xác, đều phải dùng Gauge.

Lỗi thứ hai là chép nguyên ngưỡng từ tutorial. Ngưỡng quá thấp làm Slack của khách hàng ngập cảnh báo, và sau một tuần không ai đọc nữa. Phép tính percentile ở phần trên tốn vài dòng code nhưng giúp bạn tránh được lỗi này.

Lỗi thứ ba là gắn nhãn theo những thứ có rất nhiều giá trị, chẳng hạn mã khách hàng. Như ở bước 2, mỗi giá trị nhãn cho ra một time series riêng: 20 feature là 20 đường dễ đọc, còn nhãn theo từng user sẽ làm số time series phình ra theo số user.

Nên giữ nhãn ở những chiều có ít giá trị và biết trước, rồi kiểm tra kỹ docs của Prometheus trước khi thêm nhãn mới.

Lỗi cuối cùng ít người để ý: tập tham chiếu không còn khớp với model đang chạy, vì model đã được huấn luyện lại mà tập tham chiếu vẫn để nguyên.

## Kỹ năng này xuất hiện ở đâu khi làm cho khách hàng?

Khi làm cho khách hàng, phần khó nhất thường không nằm ở code. Bạn phải ngồi với người làm nghiệp vụ để thống nhất ba điều: feature nào quan trọng đến mức cần cảnh báo riêng, ai nhận cảnh báo, và khi cảnh báo bật thì làm gì tiếp. Nếu không có bước xử lý sau cảnh báo, dashboard chỉ để trang trí.

Vì thế, buổi đầu tiên ở khách hàng, bạn nên hỏi họ đang dùng Prometheus, Grafana hay hệ thống giám sát nào khác. Gắn metric của model vào hạ tầng họ đã có thường dễ được chấp nhận hơn nhiều so với đề xuất một stack mới.

Khi đọc job description FDE hoặc MLOps, hãy để ý những cụm như "model monitoring", "observability" hay "drift detection". Trong CV, câu "dựng Prometheus và Grafana" không nói lên nhiều. Câu "phát hiện drift trên 20 feature, cảnh báo qua Slack, ngưỡng lấy từ percentile 99 của dữ liệu baseline" cho người tuyển biết bạn hiểu vấn đề.

Model nào được deploy rồi cũng sẽ có lúc chạy sai. Khác biệt là bạn phát hiện ra nhờ một cảnh báo lúc dữ liệu vừa bắt đầu lệch, hay phải đợi đến khi khách hàng gọi điện báo.

**Thử ngay tuần này:**

- Lấy một model scikit-learn có sẵn, bọc vào service có endpoint /metrics với Counter đếm dự đoán và Histogram đo latency, rồi viết thêm một job drift riêng có endpoint thứ hai chứa Gauge điểm drift theo từng feature.
- Lấy dữ liệu test, cố ý dịch phân bố một feature (ví dụ nhân số tiền lên gấp đôi), rồi xem panel Grafana và cảnh báo có bật lên không.
- Viết một đoạn README ngắn giải thích bạn lấy ngưỡng cảnh báo từ percentile nào của dữ liệu baseline, và đưa link repo vào CV ở mục dự án MLOps.

## Nguồn

- [Prometheus Overview](https://prometheus.io/docs/introduction/overview/)

- [Getting Started with Prometheus](https://prometheus.io/docs/tutorials/getting_started/)

- [Metric types | Prometheus](https://prometheus.io/docs/concepts/metric_types/)

- [Evidently and Grafana: ML monitoring live dashboards](https://evidentlyai.com/blog/evidently-and-grafana-ml-monitoring-live-dashboards)

- [Grafana Tutorial: Monitoring Machine Learning Models (DataCamp)](https://www.datacamp.com/tutorial/grafana-tutorial-monitoring-machine-learning-models)
