Giám sát metrics
Hiểu đề
Yêu cầu chức năng
- Server và ứng dụng gửi lên (hoặc cho phép thu thập) metrics dạng counter, gauge, histogram kèm label
- Người dùng truy vấn và vẽ dashboard theo khoảng thời gian, gộp và lọc theo label
- Người dùng tạo alert rule (ngưỡng, kéo dài bao lâu) và nhận thông báo qua Slack, PagerDuty, email
- Dữ liệu cũ được giữ ở độ phân giải thấp hơn để xem xu hướng nhiều tháng
Yêu cầu phi chức năng
- Ở quy mô lớn ghi được 50 triệu sample/s, không mất dữ liệu khi một node chết
- Dữ liệu mới hiện trên dashboard trong < 30 s
- Alert phát hiện sự cố trong < 1 phút sau khi metric vượt ngưỡng
- Truy vấn 1 giờ gần nhất p95 < 1 s, 30 ngày p95 < 5 s
- Sẵn sàng 99,9% và vẫn chạy khi hệ thống đang được giám sát sập
Ngoài phạm vi: Logs và distributed tracing; Phát hiện bất thường bằng machine learning; Tính tiền theo lượng dùng cho khách SaaS; Giám sát người dùng thật trên trình duyệt (RUM).
Thực thể và API
| Method | Endpoint | Ghi chú |
|---|---|---|
| GET | /metrics | Endpoint trên mỗi service ở kiểu pull, trả text format các metric hiện tại. |
| POST | /v1/write | Agent đẩy batch sample (protobuf + snappy). Trả 429 khi vượt hạn mức series. |
| GET | /v1/query_range?q=&start=&end=&step= | Truy vấn cho dashboard, step tự chọn độ phân giải phù hợp. |
| POST | /v1/alert-rules | Tạo/sửa rule. Lưu ở DB cấu hình, evaluator tải lại định kỳ. |
| POST | /v1/silences | Tạm tắt thông báo theo bộ label trong lúc bảo trì. |
Thiết kế tổng thể
Đi lần lượt từng yêu cầu chức năng. Mục tiêu ở đây là chạy đúng, việc tối ưu để dành cho phần đào sâu.
1) Thu thập metrics từ server và ứng dụng
Ứng dụng giữ bộ đếm trong RAM và chỉ lộ ra con số hiện tại, rất rẻ. Phía thu thập gom lại theo chu kỳ 10-15 s, ghi theo batch chứ không ghi từng điểm.
- 1Ứng dụng dùng thư viện (prom-client) tăng counter, ghi histogram trong RAM
- 2Scraper/agent đọc /metrics mỗi 15 s, gắn label host, service, env
- 3Ingest kiểm tra tenant và hạn mức series, đẩy batch vào hàng đợi (quy mô lớn)
- 4Ingester ghi WAL rồi thêm sample vào head chunk trong RAM, mỗi 2 h đóng block nén xuống đĩa/S3
Nguồn: Grokking Modern System Design · ch.13 Distributed Monitoring ↗ · bài "Prerequisites of a Monitoring System", mục "Populate the metrics" (push hay pull) và "Application metrics" ; Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Data collector" và "Service discoverer" ; Redis in Action ↗ · ch.5 · §5.2.1 Storing counters in Redis (tr. 94–98) ; Chưa có nguồn trong thư viện: cấu trúc chi tiết của TSDB (head block, WAL, nén delta-of-delta/XOR, compaction, downsample) không có trong sách của thư viện; chỉ nêu khái niệm cơ bản, không đưa số liệu sản phẩm
2) Truy vấn và vẽ dashboard
Dữ liệu vài giờ gần nhất nằm trong RAM của ingester, dữ liệu cũ nằm trong block trên object storage. Lớp truy vấn hỏi cả hai rồi ghép lại.
- 1Grafana gửi PromQL kèm khoảng thời gian và step
- 2Query frontend chia truy vấn dài theo ngày, trả phần đã có trong cache
- 3Querier tra inverted index để tìm series khớp label, đọc phần mới từ ingester và phần cũ từ store gateway
- 4Gộp, bỏ bản trùng từ các replica, tính hàm (rate, sum by) rồi trả về
Nguồn: Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Querying service" và "Dashboard" ; Grokking Modern System Design · ch.14 ↗ · bài "Visualize Data in a Monitoring System", mục "Using heat maps to troubleshoot" ; Chưa có nguồn trong thư viện: cấu trúc chi tiết của TSDB (head block, WAL, nén delta-of-delta/XOR, compaction, downsample) không có trong sách của thư viện; chỉ nêu khái niệm cơ bản, không đưa số liệu sản phẩm
3) Tạo alert rule và nhận thông báo
Alert là một truy vấn chạy lặp lại. Phần đánh giá và phần gửi thông báo được tách riêng để gom nhóm, chống trùng và tắt tạm khi bảo trì.
- 1Rule evaluator chạy mỗi rule theo chu kỳ 30-60 s trên dữ liệu mới
- 2Vượt ngưỡng thì chuyển pending, giữ đủ thời gian 'for' (ví dụ 5 phút) thì firing
- 3Notifier gom alert theo label (cùng cluster, cùng service), bỏ trùng, áp silence
- 4Gửi Slack/PagerDuty, gửi thêm 'resolved' khi hết
Nguồn: Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Alert Manager" (gửi alert khi vi phạm rule trong rules database) ; Grokking Modern System Design · ch.13 Distributed Monitoring ↗ · bài "Prerequisites of a Monitoring System", mục "Alerting"
4) Giữ dữ liệu cũ ở độ phân giải thấp
Không ai cần xem CPU từng 15 s của năm ngoái. Compactor gộp block nhỏ thành lớn và tạo bản tóm tắt 5 phút, 1 giờ để truy vấn dài vừa nhanh vừa rẻ.
- 1Compactor gộp các block 2 h thành block lớn hơn, dọn series đã xóa
- 2Tạo bản downsample 5 phút và 1 giờ, mỗi điểm lưu min, max, sum, count
- 3Xóa dữ liệu raw quá 15 ngày, bản 5 phút quá 90 ngày, bản 1 giờ quá 13 tháng
- 4Querier tự chọn độ phân giải theo step của truy vấn
Nguồn: Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Storage" (TSDB lưu cục bộ, dữ liệu dư đẩy sang blob storage) ; Chưa có nguồn trong thư viện: cấu trúc chi tiết của TSDB (head block, WAL, nén delta-of-delta/XOR, compaction, downsample) không có trong sách của thư viện; chỉ nêu khái niệm cơ bản, không đưa số liệu sản phẩm
Hạ tầng theo quy mô
Cùng một sản phẩm, ba mức người dùng. Chọn từng mức để xem sơ đồ, phép tính và cấu hình server thay đổi thế nào.
Ở quy mô này người dùng là số target được giám sát, không phải DAU. Một cặp Prometheus cùng scrape, Grafana và Alertmanager là đủ, toàn bộ dữ liệu nằm trên đĩa local.
Ước lượng
- Series: 200 target × 500 series = 100.000 series đang hoạt động
- Ghi: 100.000 / 15 s (chu kỳ scrape) ≈ 6.667 sample/s
- Đĩa: 6.667 × 86.400 ≈ 576 triệu sample/ngày × 1,5 B (nén) ≈ 864 MB/ngày × 15 ngày ≈ 13 GB
- RAM head: 100.000 series × ~4 KB ≈ 400 MB, máy 16 GB còn dư cho truy vấn
- Alert: 200 rule / 30 s ≈ 7 lần đánh giá/s
Nguồn con số: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · bài "Examples of Resource Estimation", mục "Number of servers required", "Storage requirements" ; Quy tắc ước lượng của khoá học · /hoc/chon-server (500 req/s mỗi vCPU, chạy 60% CPU; số series mỗi target, chu kỳ scrape, byte mỗi sample sau nén và RAM mỗi series là giả định của khoá học, cần đo thực tế)
Bảng hạ tầng
| Thành phần | Chọn | Số lượng | Cấu hình | Vì sao |
|---|---|---|---|---|
| Prometheus | Prometheus (pull), cặp HA cùng scrape | 2 | 4 vCPU · 16 GB · 100 GB SSD | 6.667 sample/s và 13 GB là rất nhỏ; chạy 2 bản giống nhau để một cái chết vẫn còn dữ liệu và alert. |
| Exporter | node_exporter + prom-client trong app | 1 mỗi host | Vài chục MB RAM | Lộ /metrics cho CPU, RAM, đĩa và số đo của ứng dụng. |
| Alertmanager | Alertmanager cluster (gossip) | 2 | 1 vCPU · 1 GB | Hai Prometheus cùng bắn alert, Alertmanager bỏ trùng và gom nhóm. |
| Dashboard | Grafana | 1 | 1 vCPU · 2 GB | Vài chục người xem; mất Grafana không ảnh hưởng thu thập hay alert. |
| Thông báo | Slack / PagerDuty / email | 1 | Dịch vụ bên ngoài | On-call nhận cảnh báo ngoài hệ thống của mình. |
- Điểm xuất phát: Prometheus pull, lưu local 15 ngày
- HA bằng cách chạy 2 bản scrape giống nhau, không cần đồng bộ
- Alertmanager tách khỏi Prometheus để bỏ trùng và gom nhóm
Khi số series lên hàng triệu, RAM của một Prometheus không đủ, và đội muốn xem xu hướng nhiều tháng trong khi đĩa local chỉ giữ 15 ngày.
Cấu hình và chi phí là ước lượng để học và trình bày trong phỏng vấn. Xem cách tính ở bài Chọn server.
So sánh nhanh ba mức
| Thành phần | Mức 1 | Mức 2 | Mức 3 |
|---|---|---|---|
| Người dùng | 1 công ty nhỏ · 200 server/container · 100.000 series | Công ty 1 triệu DAU · 2.000 target · 2 triệu series | SaaS kiểu Datadog · 1 triệu host · 500 triệu series |
| Chi phí | ≈ 100–300 USD/tháng (ước lượng) | ≈ 2.000–5.000 USD/tháng (ước lượng) | ≈ 150.000–400.000 USD/tháng (ước lượng, phần lớn là RAM ingester và băng thông giữa AZ) |
| Prometheus | Prometheus (pull), cặp HA cùng scrape 2 × 4 vCPU · 16 GB · 100 GB SSD | Prometheus + Thanos sidecar 2 shard × 2 replica = 4 × 4 vCPU · 16 GB · 200 GB SSD | Chưa cần |
| Exporter | node_exporter + prom-client trong app 1 mỗi host × Vài chục MB RAM | Chưa cần | Chưa cần |
| Alertmanager | Alertmanager cluster (gossip) 2 × 1 vCPU · 1 GB | Alertmanager cluster 3 × 1 vCPU · 2 GB | Chưa cần |
| Dashboard | Grafana 1 × 1 vCPU · 2 GB | Grafana + Postgres lưu cấu hình 2 × 2 vCPU · 4 GB | Chưa cần |
| Thông báo | Slack / PagerDuty / email 1 × Dịch vụ bên ngoài | Chưa cần | Chưa cần |
| Object storage | Chưa cần | S3 / GCS 1 × ≈ 2,2 TB (518 GB raw + 1,7 TB downsample) | S3 (block 2 h) 1 × ≈ 239 TB |
| Truy vấn | Chưa cần | Thanos Query + Query Frontend 2 × 4 vCPU · 8 GB | Query frontend + querier + Memcached 30–60 querier (HPA) × 8 vCPU · 16 GB mỗi pod |
| Store gateway | Chưa cần | Thanos Store 2 × 4 vCPU · 16 GB | Đọc block trên S3, shard theo block 24 pod × 8 vCPU · 32 GB mỗi pod |
| Compactor | Chưa cần | Thanos Compactor 1 (chỉ được chạy 1 bản) × 4 vCPU · 16 GB · 500 GB đĩa tạm | Compactor + downsampler chia theo tenant 16 pod × 8 vCPU · 32 GB · 1 TB đĩa tạm |
| Ingest gateway | Chưa cần | Chưa cần | Go service trên Kubernetes, 3 AZ 167 pod (HPA) × 2 vCPU · 4 GB mỗi pod |
| Kafka | Chưa cần | Chưa cần | MSK / Confluent, 3 AZ 20 broker × Topic samples 54 partition, RF 3, giữ 6 h |
| Ingester (TSDB) | Chưa cần | Chưa cần | TSDB kiểu Mimir / M3 81 node (27 mỗi AZ) × 16 vCPU · 128 GB · 1 TB NVMe |
| Rule evaluator | Chưa cần | Chưa cần | Evaluator shard theo tenant, có lease 84 pod × 4 vCPU · 8 GB mỗi pod |
| Notifier | Chưa cần | Chưa cần | Cụm kiểu Alertmanager, đa tenant 6 pod × 2 vCPU · 4 GB mỗi pod |
Đào sâu
Mỗi câu hỏi đi từ cách đơn giản đến cách người phỏng vấn mong đợi ở cấp senior.
1) Thu thập metrics nên theo kiểu pull hay push?
Mỗi lần đo là một lần ghi qua mạng. Hàng nghìn service ghi cùng lúc làm DB quá tải, và DB chậm thì kéo chậm luôn ứng dụng.
Đánh đổi: Dễ code nhất nhưng giám sát lại trở thành nguyên nhân gây sự cố.
Nguồn: Grokking Modern System Design · ch.13 Distributed Monitoring ↗ · bài "Prerequisites of a Monitoring System", mục "Populate the metrics" (mỗi lần đẩy dữ liệu là một request tới hệ giám sát; push dễ làm ngập hạ tầng, xem ch.14 mục "Improving our design")
Ứng dụng chỉ giữ số trong RAM, hệ thống giám sát tự quyết định lúc nào lấy nên không bị target gửi quá nhiều làm ngập (Grokking ch.13). Scrape lỗi thì biết ngay target chết (up = 0).
Đánh đổi: Phải đi được vào mạng của target (khó qua firewall, NAT, nhiều cloud); job chạy ngắn có thể kết thúc trước lần scrape; một scraper có giới hạn số target.
Nguồn: Grokking Modern System Design · ch.13 Distributed Monitoring ↗ · bài "Prerequisites of a Monitoring System", mục "Populate the metrics" (pull: server chỉ giữ metric, hệ giám sát lấy theo lịch của mình ; push có lợi khi có firewall) ; Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Data collector" (chọn pull để tránh nghẽn mạng)
Agent (OpenTelemetry Collector, Prometheus agent, Datadog agent) scrape các target trong cùng máy hoặc cluster, gom batch, nén, đệm trên đĩa rồi đẩy ra qua HTTPS nên đi qua firewall dễ dàng. Gateway kiểm tra hạn mức rồi ghi vào Kafka để hấp thụ đột biến. Mỗi agent gửi heartbeat để phân biệt 'không có dữ liệu' với 'host chết'.
Đánh đổi: Phải vận hành agent trên mọi máy và phát hiện host chết dựa vào alert khi thiếu heartbeat thay vì scrape lỗi.
Nguồn: Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Improving our design" (kết hợp pull trong data center với push từ secondary lên primary, hệ phân cấp để scale) ; Chưa có nguồn trong thư viện: agent cụ thể (OpenTelemetry Collector, Datadog agent), đệm đĩa và heartbeat không có trong sách
2) Lưu hàng trăm triệu series thế nào cho rẻ, truy vấn nhanh, và giữ được nhiều tháng?
Mỗi dòng kèm index tốn 50-100 B. 50 triệu sample/s là hơn 4 nghìn tỷ dòng mỗi ngày, không DB quan hệ nào ghi hay lưu nổi.
Đánh đổi: Chỉ ổn cho vài nghìn series trong một dự án nhỏ.
Nguồn: Grokking Modern System Design · ch.13 Distributed Monitoring ↗ · bài "Prerequisites of a Monitoring System", mục "Persist the data" (cần time-series database cho lượng dữ liệu lớn) ; Grokking Modern System Design · ch.9 Databases ↗ · bài "Types of Databases", mục "Relational databases" › "Drawback"
Sample của một series nằm liền nhau. Timestamp nén bằng delta-of-delta, giá trị nén bằng XOR với giá trị trước (kiểu Gorilla) nên còn khoảng 1,3-2 B/sample. Inverted index label → series cho phép lọc nhanh.
Đánh đổi: Giới hạn ở RAM và đĩa của một máy; không có lịch sử dài; máy chết thì mất khoảng trống dữ liệu.
Nguồn: Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Storage" (TSDB lưu cục bộ trên server giám sát) ; Chưa có nguồn trong thư viện: cấu trúc chi tiết của TSDB (head block, WAL, nén delta-of-delta/XOR, compaction, downsample) không có trong sách của thư viện; chỉ nêu khái niệm cơ bản, không đưa số liệu sản phẩm
Ingester giữ 2 h gần nhất trong RAM (có WAL, nhân bản 3 AZ) rồi đẩy block nén lên S3 một lần. Store gateway đọc block cũ theo yêu cầu, chỉ cache index. Compactor tạo bản 5 phút và 1 giờ, lưu min/max/sum/count nên avg và max vẫn đúng. Percentile không gộp được từ p99 nên phải lưu histogram bucket. Giữ raw 15 ngày, 5 phút 90 ngày, 1 giờ 13 tháng.
Đánh đổi: Nhiều thành phần phải vận hành; bản downsample làm mất đỉnh ngắn hơn step, trừ khi xem max.
Nguồn: Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Storage" và "Improving our design" (blob storage cho dữ liệu dư, hệ phân cấp) ; Chưa có nguồn trong thư viện: cấu trúc chi tiết của TSDB (head block, WAL, nén delta-of-delta/XOR, compaction, downsample) không có trong sách của thư viện; chỉ nêu khái niệm cơ bản, không đưa số liệu sản phẩm
3) Đánh giá hàng triệu alert rule mỗi phút mà không bỏ sót và không spam?
Một vòng chạy hết rule lâu hơn chu kỳ thì rule cuối luôn trễ. Máy cron chết là không còn alert nào, mà không ai biết.
Đánh đổi: Chỉ đủ cho vài trăm rule.
Nguồn: Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Alert Manager" ; Chưa có nguồn trong thư viện: giới hạn của một cron tuần tự là suy luận của khoá, không có trong sách
Mỗi evaluator lo một phần rule, giữ trạng thái pending/firing. Alertmanager phía sau bỏ trùng và gom nhóm.
Đánh đổi: Mỗi rule là một truy vấn nên tải đọc tăng theo số rule; một evaluator chết thì phần rule của nó im lặng tới khi chia lại, đồng hồ 'for' bị reset.
Nguồn: Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Alert Manager" ; Grokking Modern System Design · ch.9 Databases ↗ · bài "Data Partitioning", mục "Hash-based sharding"
Rule chia theo tenant/rule bằng consistent hashing kèm lease, trạng thái pending lưu ra ngoài nên failover không reset 'for'. Evaluator chỉ đọc vài phút gần nhất từ ingester. Biểu thức nặng dùng chung được tính trước bằng recording rule. Notifier gom nhóm, chặn alert con khi alert cha đã bắn (cluster sập thì không báo từng host), áp silence. Một alert 'watchdog' luôn bắn tới dịch vụ bên ngoài, ngừng nhận là biết hệ giám sát đã chết.
Đánh đổi: Phức tạp hơn nhiều; cần định nghĩa rõ thứ bậc alert để gom nhóm không che mất sự cố thật.
Nguồn: Grokking Modern System Design · ch.9 Databases ↗ · bài "Data Partitioning", mục "Consistent hashing" ; Grokking Modern System Design · ch.14 Monitor Server-side Errors ↗ · bài "Detailed Design of a Monitoring System", mục "Alert Manager" ; Building Microservices ↗ (.zip) · ch.8 Monitoring · mục "Synthetic Monitoring" (tr. 161) ; Chưa có nguồn trong thư viện: lease, recording rule, inhibition và alert watchdog không có trong sách
4) Một dev thêm label user_id vào metric và cả hệ thống chậm hẳn. Kiểm soát high-cardinality thế nào?
Số series là tích số giá trị của các label: 1 triệu user × 20 endpoint × 5 status = 100 triệu series mới. Mỗi series tốn vài KB RAM trong ingester nên ingester hết RAM và sập.
Đánh đổi: Một commit làm sập giám sát của mọi đội.
⚠ Chưa có nguồn trong thư viện: khái niệm cardinality của label metric không có trong sách của thư viện; phép nhân số giá trị các label là phép tính thuần túy, RAM mỗi series là giả định của khoá
Vượt hạn mức thì trả 429 hoặc bỏ sample, có dashboard cho từng đội thấy mình đang dùng bao nhiêu series. Danh sách label cấm (user_id, request_id, email) chặn từ cửa.
Đánh đổi: Dữ liệu bị bỏ âm thầm nếu không báo rõ, dev khó hiểu vì sao biểu đồ trống.
Nguồn: Microservices Patterns ↗ (.zip) · ch.11 · §11.3.4 Applying the Application metrics pattern ; Grokking Modern System Design · ch.13 Distributed Monitoring ↗ · bài "Prerequisites of a Monitoring System", mục "Metrics" ; Grokking Modern System Design · ch.19 Rate Limiter ↗ · bài "System Design: The Rate Limiter", mục "Why do we need a rate limiter?" (hạn mức, trả 429)
Gateway ước lượng số series mới theo metric bằng HyperLogLog và cảnh báo trước khi chạm hạn mức. Agent áp relabel để gộp bớt label (bỏ pod_id sau khi sum). Cần đi từ một điểm trên biểu đồ tới request cụ thể thì gắn exemplar trỏ tới trace_id thay vì thêm label. Câu hỏi theo từng user đưa sang kho cột như ClickHouse.
Đánh đổi: Phải dạy đội dev cách đặt label và duy trì công cụ xem cardinality; một số câu hỏi chi tiết phải trả lời ở hệ khác.
Nguồn: Microservices Patterns ↗ (.zip) · ch.11 · §11.3.2 Applying the Log aggregation pattern và §11.3.3 Using the Distributed tracing pattern (chi tiết theo request nằm ở logs/traces) ; Grokking Modern System Design · ch.22 Distributed Logging ↗ · bài "Introduction to Distributed Logging", mục "Use sampling" ; Chưa có nguồn trong thư viện: HyperLogLog, exemplar, ClickHouse không có trong sách
Trong phỏng vấn
- “Tôi tính tải theo số series và chu kỳ thu thập: sample/s = series / chu kỳ, RAM ingester tỉ lệ với số series.”
- “Pull hợp với hạ tầng nội bộ; ở quy mô SaaS tôi dùng agent pull tại chỗ rồi push batch nén ra ngoài.”
- “Dữ liệu chia theo tuổi: 2 h trong RAM, block nén trên S3, downsample lưu min/max/sum/count cho dữ liệu cũ.”
- “Hệ giám sát cũng phải được giám sát: có alert watchdog luôn bắn ra dịch vụ bên ngoài.”
- Lưu mỗi sample một dòng trong DB quan hệ.
- Không giới hạn cardinality, để label user_id hay request_id lọt vào metric.
- Downsample bằng cách lấy trung bình rồi bỏ min/max, hoặc gộp percentile như số thường.
- Đặt hệ giám sát chung hạ tầng với hệ bị giám sát, sập cùng nhau.
Đọc thêm
Sách trong thư viện của bạn. Số chương có thể lệch nhẹ so với bản PDF.