Case Study: Design Metrics Monitoring & Alerting System

“Hệ thống không có monitoring giống như lái xe ban đêm mà tắt đèn — mọi thứ ổn cho đến khi em đâm vào tường. Lúc đó thì đã quá muộn.”

Tags: system-design monitoring alerting metrics time-series case-study alex-xu-vol2 Student: Hieu Source: Alex Xu — System Design Interview Volume 2, Chapter 6 Prerequisite: Tuan-13-Monitoring-Observability · Tuan-08-Message-Queue · Tuan-06-Cache-Strategy Lien quan: Tuan-07-Database-Sharding-Replication · Tuan-02-Back-of-the-envelope · Tuan-01-Scale-From-Zero-To-Millions


1. Context & Why — Tại sao cần Metrics Monitoring & Alerting?

1.1 Analogy: He thong canh bao chay trong toa nha

Hieu, em hay tuong tuong he thong monitoring nhu he thong phong chay chua chay trong mot toa nha choc troi:

Thành phần phòng cháyThành phần monitoringVai trò
Sensor khói/nhiệt (mỗi phòng có 1 cái)Metrics collection agent (mỗi server có 1 agent)Thu thập tín hiệu liên tục từ nguồn
Dây dẫn tín hiệuIngestion pipeline (Kafka)Truyền tải dữ liệu từ sensor về trung tâm
Trung tâm điều khiểnTime Series DatabaseLưu trữ và xử lý mọi tín hiệu
Bảng hiển thị trạng tháiDashboard (Grafana)Hiển thị trạng thái real-time cho người quan sát
Quy tắc kích hoạt báo động (nhiệt > 80 độ C)Alert rules engineĐánh giá điều kiện để quyết định có báo động không
Còi báo động + đèn nhấp nháyNotification channels (PagerDuty, Slack, Email)Thông báo đến đúng người, đúng mức độ
Đội cứu hỏaOn-call engineerNgười thực sự xử lý sự cố
Hệ thống chữa cháy tự động (sprinkler)Auto-remediation (auto-scaling, restart)Phản ứng tự động trước khi người can thiệp

Aha Moment: Một hệ thống phòng cháy tốt không phải là hệ thống kêu nhiều — mà là hệ thống kêu đúng lúc, đúng mức, và không bao giờ bỏ sót đám cháy thật. Monitoring cũng vậy: alert fatigue (quá nhiều false alarm) nguy hiểm không kém việc không có alert.

1.2 Tại sao monitoring quan trọng trong production?

Hieu, khi em deploy hệ thống lên production, có 3 câu hỏi em phải trả lời được mọi lúc:

  1. Hệ thống có đang healthy không? (CPU, memory, disk, network)
  2. Business logic có đang hoạt động đúng không? (request rate, error rate, latency)
  3. Khi có vấn đề, ai biết trước — em hay customer? (alerting)

Không có monitoring, em chỉ biết hệ thống sập khi customer gọi điện than phiền. Đó là cách tệ nhất để phát hiện incident.

1.3 Scope của bài toán

Bài toán trong Alex Xu Vol 2 Chapter 6 focus vào infrastructure metrics monitoring — không phải application-level tracing hay logging. Cụ thể:

Thuộc tínhTrong scopeNgoài scope
Metrics (CPU, memory, request count, latency)Yes
Logs (structured/unstructured text)Dùng ELK/Loki
Traces (distributed request flow)Dùng Jaeger/Zipkin
Alerting (dựa trên metrics)Yes
Dashboard (visualization)Yes
Auto-remediationNâng cao, ngoài scope

Ghi nhớ: Trong thực tế, Metrics + Logs + Traces = Three Pillars of Observability (đã học ở Tuan-13-Monitoring-Observability). Bài này deep dive vào pillar đầu tiên — Metrics.


2. Deep Dive — Alex Xu 4-Step Framework

Step 1 — Understand the Problem & Establish Design Scope

2.1 Functional Requirements (Yêu cầu chức năng)

Trước khi vẽ diagram, em phải hỏi interviewer để xác định scope rõ ràng:

Câu hỏiCâu trả lời giả địnhTại sao hỏi
Hệ thống cần monitor bao nhiêu server?10,000 serversQuyết định ingestion throughput
Mỗi server thu thập bao nhiêu metrics?100 metrics/server (CPU, memory, disk, network, custom metrics)Quyết định số lượng time series
Tổng bao nhiêu time series?1,000,000 time series (10K servers x 100 metrics)Quyết định TSDB capacity
Ingestion rate?10,000,000 data points/sec (10M/s)Quyết định Kafka throughput, TSDB write capacity
Collection interval?Mỗi 10 giây cho infrastructure metrics, mỗi 1 phút cho business metricsQuyết định data volume
Cần alerting không?Có — rule-based alerting với multiple severity levelsCore feature
Cần dashboard không?Có — real-time dashboard với drill-downCore feature
Retention bao lâu?1 năm cho raw data, 3 năm cho downsampled dataQuyết định storage strategy
Multi-tenant không?Có — nhiều team dùng chung hệ thốngAccess control, isolation
Có anomaly detection không?Ngoài scope ban đầu, nhưng extensibleFuture feature

2.2 Non-functional Requirements (Yêu cầu phi chức năng)

RequirementTargetGhi chú
Scalability10K servers, 1M time series, 10M data points/secHệ thống quy mô lớn
Availability99.99% (four 9s) cho alerting pipelineAlert bị miss = sự cố không được phát hiện
Latency — Ingestion< 30 giây end-to-end (từ collection đến queryable)Near-real-time monitoring
Latency — Alerting< 1 phút từ threshold breached đến notification sentPhát hiện sự cố nhanh
Latency — Dashboard< 2 giây cho query thông thường, < 10 giây cho complex queryUX requirement
DurabilityKhông mất data points sau khi đã ingestedAt-least-once delivery
Query flexibilityHỗ trợ aggregation (sum, avg, percentile), filtering, groupingPromQL-like
ReliabilityAlert không bị duplicate gửi quá 2 lần, không bị missDedup + at-least-once

2.3 Phân biệt Metrics vs Events

Đặc điểmMetricEvent (Log)
Bản chấtGiá trị số (numeric) đo lường theo thời gianText mô tả một sự kiện xảy ra
Ví dụcpu_usage = 78.5%"User 123 logged in from IP 1.2.3.4"
CardinalityThấp-trung bình (metric name + tags)Rất cao (mỗi event unique)
StorageCompact, nén tốt (time series)Lớn, khó nén
Query patternAggregation (avg, sum, percentile over time)Search, filter, full-text
Database phù hợpTSDB (InfluxDB, Prometheus)Search engine (Elasticsearch)
Retention costRẻ (sau downsampling)Đắt (raw text)

Quan trọng: Bài toán này focus vào metrics — numeric time series data. Đây là lý do TSDB (Time Series Database) là core component.


Step 2 — Propose High-Level Design

2.4 Analogy cho High-Level Architecture

Hieu, tưởng tượng kiến trúc monitoring như hệ thống thần kinh của cơ thể:

  • Collection agents = Các đầu dây thần kinh (nerve endings) — cảm nhận nhiệt độ, áp suất, đau
  • Ingestion pipeline = Dây thần kinh (nerves) — truyền tín hiệu về trung tâm
  • Time series DB = Não — lưu trữ và xử lý thông tin
  • Query service = Vùng vỏ não nhận thức — trả lời câu hỏi “đang xảy ra cái gì?”
  • Alerting system = Phản xạ — tự động phản ứng khi nguy hiểm
  • Dashboard = Bảng chỉ số sức khỏe — bác sĩ nhìn vào để chẩn đoán

2.5 Architecture Overview

flowchart TB
    subgraph "Metrics Sources (10K servers)"
        S1["Application Servers<br/>(CPU, Memory, Disk)"]
        S2["Database Servers<br/>(Query latency, Connections)"]
        S3["Message Queues<br/>(Queue depth, Consumer lag)"]
        S4["Load Balancers<br/>(Request rate, Error rate)"]
        S5["Custom Business Metrics<br/>(Orders/sec, Revenue)"]
    end

    subgraph "Collection Layer"
        CA["Collection Agents<br/>(Prometheus exporters /<br/>StatsD / Telegraf)"]
        SD["Service Discovery<br/>(Consul / K8s API /<br/>DNS-based)"]
    end

    subgraph "Ingestion Pipeline"
        KAFKA["Kafka Cluster<br/>(Buffer & Decouple)"]
        SP["Stream Processor<br/>(Flink / Kafka Streams)<br/>Pre-aggregation"]
    end

    subgraph "Storage Layer"
        TSDB[("Time Series Database<br/>(InfluxDB / Prometheus<br/>/ TimescaleDB)")]
        HOT[("Hot Storage<br/>(SSD, recent 7 days)")]
        WARM[("Warm Storage<br/>(HDD, 7 days - 3 months)")]
        COLD[("Cold Storage<br/>(S3/GCS, 3 months - 3 years)")]
    end

    subgraph "Query & Alerting"
        QS["Query Service<br/>(PromQL / InfluxQL)"]
        CACHE[("Query Cache<br/>(Redis)")]
        ARE["Alert Rules Engine<br/>(Evaluate rules<br/>every 15-60s)"]
    end

    subgraph "Notification & Visualization"
        AM["Alert Manager<br/>(Grouping, Dedup,<br/>Silencing, Routing)"]
        DASH["Dashboard<br/>(Grafana)"]
        PD["PagerDuty"]
        SLACK["Slack"]
        EMAIL["Email"]
        WEBHOOK["Webhook"]
    end

    S1 & S2 & S3 & S4 & S5 --> CA
    SD -->|"Discover targets"| CA
    CA -->|"Push metrics"| KAFKA
    KAFKA --> SP
    SP -->|"Write"| TSDB
    TSDB --- HOT
    TSDB --- WARM
    TSDB --- COLD

    TSDB --> QS
    QS --> CACHE
    QS --> DASH
    QS --> ARE
    ARE -->|"FIRING alerts"| AM
    AM --> PD & SLACK & EMAIL & WEBHOOK

    style KAFKA fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff
    style TSDB fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff
    style ARE fill:#f44336,stroke:#333,stroke-width:2px,color:#fff
    style AM fill:#e91e63,stroke:#333,stroke-width:2px,color:#fff
    style DASH fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff

2.6 Component Responsibilities

ComponentTrách nhiệmTechnology choices
Collection AgentThu thập metrics từ server/service, format thành time seriesPrometheus Node Exporter, Telegraf, StatsD, collectd
Service DiscoveryBiết danh sách targets cần thu thập (pull model)Consul, Kubernetes API, DNS SRV records, file-based
Kafka ClusterBuffer metrics data, decouple collection từ storage, handle burstApache Kafka (partitioned by metric name hash)
Stream ProcessorPre-aggregate raw metrics trước khi ghi vào TSDBApache Flink, Kafka Streams, Apache Spark Streaming
Time Series DBLưu trữ metrics data, index by metric name + tagsInfluxDB, Prometheus, TimescaleDB, OpenTSDB, VictoriaMetrics
Query ServiceParse query language, fetch data từ TSDB, trả về resultPromQL engine, InfluxQL, Flux
Query CacheCache kết quả hot queriesRedis, in-memory LRU cache
Alert Rules EngineEvaluate alert rules periodically, determine firing alertsPrometheus Alertmanager, Grafana Alerting
Alert ManagerGrouping, deduplication, silencing, routing alertsPrometheus Alertmanager
DashboardVisualization — graphs, tables, heatmapsGrafana, Kibana, custom UI

2.7 Data Flow — End to End

sequenceDiagram
    participant Server as Application Server
    participant Agent as Collection Agent
    participant SD as Service Discovery
    participant Kafka as Kafka Cluster
    participant SP as Stream Processor
    participant TSDB as Time Series DB
    participant QS as Query Service
    participant ARE as Alert Rules Engine
    participant AM as Alert Manager
    participant OC as On-Call Engineer

    Note over Server,Agent: Collection Phase (every 10s)
    Agent->>SD: Discover scrape targets
    SD-->>Agent: List of endpoints
    Agent->>Server: Scrape /metrics (pull model)
    Server-->>Agent: Metric data points
    Agent->>Kafka: Produce metric batch

    Note over Kafka,TSDB: Ingestion Phase
    Kafka->>SP: Consume raw metrics
    SP->>SP: Pre-aggregate (5s → 1min)
    SP->>TSDB: Write aggregated data points

    Note over TSDB,OC: Query & Alert Phase
    ARE->>TSDB: Evaluate rule: avg(cpu) > 90% for 5m
    TSDB-->>ARE: Result: true, value = 94.2%
    ARE->>AM: Fire alert: HIGH CPU on server-042
    AM->>AM: Check: grouped? silenced? duplicate?
    AM->>OC: Send PagerDuty notification
    OC-->>AM: Acknowledge alert

    Note over TSDB,QS: Dashboard Phase
    QS->>TSDB: Query: avg(cpu) by (cluster) last 1h
    TSDB-->>QS: Time series result
    QS-->>QS: Cache result (TTL 30s)

Step 3 — Design Deep Dive

2.8 Data Model — Time Series Data

Cấu trúc một data point

Mỗi metric data point gồm 4 thành phần:

FieldMô tảVí dụ
Metric nameTên metric, mô tả cái đang đohttp_request_total, cpu_usage_percent
Tags / LabelsCặp key-value để phân loại, filtering{host="server-042", region="us-east-1", service="payment"}
TimestampThời điểm ghi nhận (Unix epoch, millisecond precision)1679000000000
ValueGiá trị số (int64 hoặc float64)78.5
Ví dụ data representation
metric_name{label1="value1", label2="value2"} value timestamp
cpu_usage_percent{host="server-042", region="us-east-1", env="prod"} 78.5 1679000000
http_request_total{method="GET", path="/api/orders", status="200"} 150432 1679000000
memory_used_bytes{host="server-042"} 6442450944 1679000000
kafka_consumer_lag{topic="orders", consumer_group="payment-svc"} 2350 1679000000
Time Series = Unique Combination of Metric Name + Tags

Hieu, đây là concept cực kỳ quan trọng. Một time series được xác định bởi metric name + tập hợp tất cả label pairs:

cpu_usage{host="s1", region="us-east-1"}  → Time series #1
cpu_usage{host="s1", region="eu-west-1"}  → Time series #2  (khác region)
cpu_usage{host="s2", region="us-east-1"}  → Time series #3  (khác host)

Mỗi time series là một stream các (timestamp, value) pairs. TSDB optimize cho việc lưu trữ và query các stream này.

Đặc tính của Time Series Data
Đặc tínhGiải thíchẢnh hưởng design
Append-onlyData points chỉ được ghi thêm, không bao giờ update hay deleteWrite path đơn giản, LSM tree phù hợp
Write-heavyWrite >> Read (mỗi giây có hàng triệu data points mới)Optimize write path, batch writes
Recent data queried most90%+ queries là cho data trong 24h gần nhấtHot/warm/cold tiering, cache recent data
Time-orderedData luôn đến theo thứ tự thời gian (hoặc gần đúng)Sequential write, time-based partitioning
High cardinality tagsTag values có thể rất nhiều (e.g., pod_id trong Kubernetes)Cardinality explosion — killer issue
CompressibleGiá trị liên tiếp thường gần nhau (CPU 78.5 → 78.7 → 78.3)Delta encoding, XOR compression cực hiệu quả

Aha Moment: Time series data có pattern cực kỳ đặc biệt — append-only, write-heavy, time-ordered, compressible. Đó là lý do TSDB tồn tại — relational DB và general-purpose NoSQL không tận dụng được các đặc tính này.


2.9 Collection — Push vs Pull Model

Đây là một trong những quyết định kiến trúc quan trọng nhất. Hai mô hình chính:

Pull Model (Prometheus-style)
flowchart LR
    subgraph "Targets"
        T1["Server A<br/>/metrics endpoint"]
        T2["Server B<br/>/metrics endpoint"]
        T3["Server C<br/>/metrics endpoint"]
    end

    subgraph "Service Discovery"
        SD["Consul / K8s API"]
    end

    subgraph "Collector"
        PROM["Prometheus Server<br/>(Pull every 15s)"]
    end

    SD -->|"Target list"| PROM
    PROM -->|"HTTP GET /metrics"| T1
    PROM -->|"HTTP GET /metrics"| T2
    PROM -->|"HTTP GET /metrics"| T3

    style PROM fill:#E65100,stroke:#333,stroke-width:2px,color:#fff

Cách hoạt động: Collector chủ động gọi HTTP GET đến /metrics endpoint của mỗi target theo interval cố định.

Ưu điểmNhược điểm
Dễ debug: Collector biết chính xác target nào alive, target nào deadFirewall/NAT issues: Collector phải reach được target
Service discovery tự nhiên: Nếu pull fail → target downScaling collector: Một Prometheus instance có giới hạn targets
Không cần agent phức tạp: Target chỉ cần expose HTTP endpointShort-lived jobs: Job chạy xong trước khi bị scrape → miss data
Centralized control: Thay đổi interval, targets ở một chỗNetwork overhead: Mỗi scrape là một HTTP request
Push Model (Datadog/StatsD-style)
flowchart LR
    subgraph "Targets"
        T1["Server A<br/>+ Agent"]
        T2["Server B<br/>+ Agent"]
        T3["Server C<br/>+ Agent"]
    end

    subgraph "Gateway"
        GW["Push Gateway /<br/>Collector Endpoint"]
    end

    T1 -->|"Push metrics"| GW
    T2 -->|"Push metrics"| GW
    T3 -->|"Push metrics"| GW
    GW --> KAFKA["Kafka"]

    style GW fill:#1565C0,stroke:#333,stroke-width:2px,color:#fff

Cách hoạt động: Agent trên mỗi server chủ động gửi metrics đến central collector/gateway.

Ưu điểmNhược điểm
Firewall-friendly: Agent push ra ngoài, không cần inbound accessKhó biết target dead: Không nhận data ≠ target down (có thể network issue)
Short-lived jobs OK: Job push metrics ngay khi cóThundering herd: Nhiều agent push cùng lúc → burst
Agent tự quản collection intervalConfig scattered: Mỗi agent config riêng
NAT/multi-DC friendly: Agent biết cách reach gatewayCần agent phức tạp hơn: Buffering, retry, batching
So sánh tổng hợp
Tiêu chíPull (Prometheus)Push (Datadog/StatsD)
Health detectionTự nhiên (pull fail = down)Cần heartbeat riêng
Service discoveryCần (Consul, K8s API)Không cần (agent tự push)
FirewallCần inbound accessChỉ cần outbound
Short-lived jobsKhó (dùng Pushgateway workaround)Dễ
ScalingHorizontal: federation, shardingHorizontal: gateway + Kafka
Ai dùng?Prometheus, Thanos, VictoriaMetricsDatadog, New Relic, StatsD, Telegraf

Thực tế: Hệ thống production quy mô lớn thường dùng hybrid — pull cho infrastructure metrics (server luôn sẵn sàng), push cho business metrics và short-lived jobs. Kafka ở giữa làm buffer cho cả hai.

Service Discovery cho Pull Model

Trong pull model, collector cần biết danh sách tất cả targets để scrape. Đây là lý do cần service discovery:

Phương phápMô tảKhi nào dùng
Static configHardcode list targets trong config fileMôi trường nhỏ, ít thay đổi
File-basedĐọc target list từ file, file được update bởi automationCM tool (Ansible/Puppet) quản lý
ConsulQuery Consul catalog để lấy list healthy servicesConsul đã có trong stack
Kubernetes APIWatch K8s API cho pods với annotation prometheus.io/scrape: "true"Kubernetes environment
DNS SRVResolve SRV records để lấy host:portDNS-based service discovery
EC2/GCE APIQuery cloud provider API cho list instancesCloud-native environment

2.10 Ingestion Pipeline — Kafka as Buffer

Tại sao cần Kafka giữa Collection và TSDB?

Hieu, đây là câu hỏi hay. Sao không ghi thẳng từ agent vào TSDB?

Vấn đề khi ghi thẳngKafka giải quyết thế nào
Burst traffic — deploy 1000 servers cùng lúc, restart agentsKafka absorb burst, TSDB consume ổn định
TSDB downtime — upgrade, compaction heavyKafka retain data, TSDB catch up sau
Fan-out — cùng data cần đi nhiều nơi (TSDB, alerting, real-time dashboard)Kafka multiple consumer groups
Backpressure — TSDB quá tảiKafka giữ data, consumer tự điều chỉnh pace
Data transformation — cần pre-aggregate trước khi ghiStream processor consume từ Kafka, transform, rồi ghi
Ingestion Pipeline Architecture
flowchart LR
    subgraph "Collection Agents"
        A1["Agent 1"]
        A2["Agent 2"]
        AN["Agent N"]
    end

    subgraph "Kafka Cluster"
        direction TB
        T1["Topic: raw-metrics<br/>Partitions: 64<br/>Replication: 3"]
        T2["Topic: aggregated-metrics<br/>Partitions: 32<br/>Replication: 3"]
    end

    subgraph "Stream Processing"
        SP1["Flink Job 1<br/>Counter aggregation<br/>(sum per minute)"]
        SP2["Flink Job 2<br/>Histogram aggregation<br/>(percentile computation)"]
        SP3["Flink Job 3<br/>Rule evaluation<br/>(real-time alerting)"]
    end

    subgraph "Storage"
        TSDB["Time Series DB"]
        ARE["Alert Rules Engine"]
    end

    A1 & A2 & AN -->|"Produce"| T1
    T1 --> SP1 & SP2 & SP3
    SP1 & SP2 -->|"Write aggregated"| T2
    T2 --> TSDB
    SP3 -->|"Trigger alerts"| ARE

    style T1 fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff
    style T2 fill:#FFC107,stroke:#333,stroke-width:2px,color:#000
Kafka Partitioning Strategy

Cách partition metrics trong Kafka ảnh hưởng lớn đến hiệu suất:

StrategyCách làmƯuNhược
By metric namepartition_key = hash(metric_name)Cùng metric trên cùng partition → dễ aggregateHotspot nếu 1 metric có data nhiều hơn
By hostpartition_key = hash(host_id)Tất cả metrics của 1 host trên 1 partition → ordered per hostHotspot nếu 1 host có nhiều metrics
By metric + hostpartition_key = hash(metric_name + host_id)Phân bổ đều, 1 time series trên 1 partitionNhiều partitions cần thiết
Round-robinKhông có key, Kafka tự phân bổPhân bổ đều nhấtMất ordering — khó aggregate

Khuyến nghị: Dùng hash(metric_name) cho hệ thống general. Nếu có stream processing cần aggregate per host, dùng hash(metric_name + host_id).

Stream Processor — Pre-aggregation

Pre-aggregation là bước cực kỳ quan trọng để giảm tải cho TSDB:

OperationInputOutputVí dụ
Counter sum1000 data points/sec cho http_request_total1 data point/min (sum of 60s window)150432 requests in last minute
Gauge average1000 data points/sec cho cpu_usage1 data point/min (avg of 60s window)avg CPU = 78.3% last minute
Histogram merge1000 latency values/sec1 histogram per minute (p50, p90, p99)p99 latency = 245ms last minute
Rate computationCounter values over timeRate per second2,507 req/s

Lợi ích: Giảm write volume từ 10M data points/sec xuống còn khoảng 170K data points/sec (giảm ~60x) mà vẫn giữ được thông tin đủ cho 1-minute granularity.


2.11 Time Series Database — Core of the System

Tại sao Relational DB không phù hợp?

Hieu, đây là câu hỏi interviewer hay hỏi. Em phải giải thích được rõ ràng:

Vấn đềRelational DB (PostgreSQL)TSDB (InfluxDB, Prometheus)
Write throughput10K-50K inserts/sec (B-tree overhead)500K-2M+ inserts/sec (LSM tree, batch write)
CompressionTệ cho time series (generic compression)Tuyệt vời (gorilla, delta-of-delta, XOR) — 10x-50x
Time-based queriesFull table scan hoặc cần complex indexingNative time-based partitioning, O(1) time range lookup
DownsamplingPhải viết custom logic, expensiveBuilt-in continuous queries
RetentionManual DELETE, vacuum overheadBuilt-in retention policies, automatic TTL
Storage efficiency~16 bytes/data point (row overhead)~1-2 bytes/data point (after compression)
Cardinality handlingOK (B-tree index)Chuyên optimize cho label-based queries

Kết luận: Relational DB được thiết kế cho general-purpose CRUD. TSDB được thiết kế chuyên biệt cho append-only, time-ordered, numeric data. Sự khác biệt là 10-100x ở write throughput và storage efficiency.

TSDB Options — So sánh
TSDBArchitectureQuery LanguageScalingAi dùng
PrometheusSingle-node, local storagePromQLFederation / ThanosKubernetes ecosystem
InfluxDBCluster (enterprise), single-node (OSS)InfluxQL, FluxEnterprise clusteringIoT, DevOps
TimescaleDBPostgreSQL extensionSQL + custom functionsPostgreSQL native replicationTeams muốn SQL compatibility
OpenTSDBOn top of HBaseHTTP APIHBase scalingHadoop ecosystem
VictoriaMetricsSingle-node + clusterPromQL compatibleBuilt-in clusteringPrometheus alternative at scale
ClickHouseColumn-oriented OLAPSQLBuilt-in shardingAnalytics-heavy use cases
Apache DruidReal-time + batchSQL-likeBuilt-in clusteringReal-time analytics
LSM Tree — Tại sao TSDB dùng LSM thay vì B-tree?
Đặc tínhB-tree (PostgreSQL, MySQL)LSM Tree (RocksDB, LevelDB)
Write patternRandom I/O (update page in place)Sequential I/O (append to WAL + memtable)
Write amplificationCao (page split, rebalance)Thấp hơn (merge sort)
Read performanceTốt (O(log n) lookup)Tệ hơn (check memtable + multiple levels)
Space amplificationThấp (in-place update)Cao hơn (multiple copies during compaction)
Phù hợp choRead-heavy, update-heavyWrite-heavy, append-only

Kết luận: Time series data là write-heavy, append-only → LSM tree là lựa chọn tự nhiên. Đó là lý do Prometheus, InfluxDB, VictoriaMetrics đều dùng LSM-based storage engine.

Compression — Chìa khóa của Storage Efficiency

Compression là yếu tố sống còn cho TSDB. Không có compression, storage cost sẽ unmanageable.

Gorilla Encoding (Facebook, 2015) — kỹ thuật nền tảng cho hầu hết TSDB hiện đại:

Kỹ thuậtÁp dụng choCách hoạt độngTỷ lệ nén
Delta-of-deltaTimestampsThay vì lưu timestamp tuyệt đối, lưu delta của delta. Nếu interval đều (mỗi 10s), delta-of-delta = 0 → nén cực tốt~1-2 bits/timestamp (thay vì 64 bits)
XOR encodingFloat valuesXOR giá trị hiện tại với giá trị trước. Nếu giá trị gần nhau, XOR result có nhiều leading/trailing zeros → nén tốt~1-4 bytes/value (thay vì 8 bytes)
Dictionary encodingTag valuesMap string → integer ID. "us-east-1"3Giảm 5-10x cho tags
Run-length encodingRepeated valuesLưu (value, count) thay vì repeatTốt cho constant metrics

Ví dụ trực quan Delta-of-delta:

Timestamps:     1679000000  1679000010  1679000020  1679000030  1679000040
Deltas:                     10          10          10          10
Delta-of-delta:             0           0           0           0

Thay vì lưu 5 giá trị 64-bit (40 bytes), chỉ cần lưu 1 base timestamp + 4 bits (gần 0 bytes cho delta-of-delta = 0).

Ví dụ trực quan XOR encoding:

Values (float64):    78.50    78.52    78.48    78.55
XOR with previous:   —        0x...0040  0x...00C0  0x...0070

Vì values gần nhau, XOR result có rất nhiều leading zeros → chỉ cần lưu vài bits cho phần khác biệt.

Aha Moment: Nhờ Gorilla encoding, TSDB đạt compression ratio 10x-50x. Một data point raw cần ~16 bytes, sau compression chỉ còn 1-2 bytes. Đây là lý do monitoring system có thể lưu trữ hàng tỷ data points mà storage cost vẫn hợp lý.

Downsampling — Trade Precision for Storage

Downsampling là quá trình giảm resolution của data theo thời gian:

TierResolutionRetentionUse case
Raw10 giây7 ngàyDebug real-time issues
1 phút1 phút30 ngàyRecent investigation
5 phút5 phút90 ngàyWeekly review
1 giờ1 giờ1 nămMonthly reporting
1 ngày1 ngày3 nămAnnual trends

Cách downsampling hoạt động: Continuous query (hoặc background job) aggregate raw data:

Raw (10s): 78.5, 78.3, 78.7, 78.4, 78.6, 78.2  (6 points in 1 minute)
      ↓ downsample to 1-minute
1-min: avg=78.45, min=78.2, max=78.7, count=6     (1 aggregated point)

Lưu ý: Khi downsample, luôn lưu min, max, avg, sum, count — không chỉ avg. Vì nếu chỉ có avg, em mất thông tin về spikes (max) và dips (min).

Storage savings từ downsampling:

TierData points/series/dayGiảm so với raw
Raw (10s)8,6401x
1 phút1,4406x
5 phút28830x
1 giờ24360x
1 ngày18,640x

Trade-off: Khi nhìn graph CPU 6 tháng trước ở resolution 1 giờ, em sẽ không thấy spike 2 phút. Nhưng em sẽ thấy min/max trong mỗi giờ → đủ để phát hiện pattern bất thường.


2.12 Query Service — PromQL-like Language

Query Patterns phổ biến
PatternMô tảVí dụ PromQL
Instant queryGiá trị hiện tại của metriccpu_usage{host="s1"}
Range queryGiá trị trong khoảng thời giancpu_usage{host="s1"}[5m]
AggregationAggregate across labelsavg(cpu_usage) by (cluster)
RateTốc độ tăng của counterrate(http_requests_total[5m])
Top-KTop servers theo metrictopk(10, cpu_usage)
PercentileTính percentile từ histogramhistogram_quantile(0.99, rate(http_duration_bucket[5m]))
MathPhép tính giữa metricsmemory_used / memory_total * 100
Alert conditionĐiều kiện kết hợpavg(cpu_usage) by (host) > 90 for 5m
Query Optimization
Kỹ thuậtMô tảTác dụng
Pre-aggregationTính sẵn aggregated values khi ingest (recording rules)Query avg(cpu) by cluster trở thành simple lookup thay vì scan millions of series
Query result cachingCache kết quả query trong Redis (TTL = collection interval)Dashboard refresh mỗi 30s nhưng cùng query → cache hit
Time-based partitioningData partition theo time block (e.g., 2-hour blocks trong Prometheus)Query last 1 hour chỉ cần đọc 1-2 blocks, không scan toàn bộ
Inverted indexIndex label values → series IDsFilter {region="us-east-1"} nhanh, không cần scan
Query parallelismSplit query across time ranges, parallel fetch24h query = 24 parallel 1h queries, merge results
Subquery optimizationRewrite nested queries thành efficient formTránh N+1 query problem
Recording Rules (Pre-computed Queries)

Recording rules là concept quan trọng để optimize dashboard performance:

# Recording rule definition
name: cluster:cpu_usage:avg5m
expression: avg(rate(cpu_usage_percent[5m])) by (cluster)
interval: 1m

Thay vì mỗi lần refresh dashboard phải aggregate raw data từ 10K servers, recording rule tính sẵn mỗi phút và lưu kết quả như một metric mới. Dashboard query metric đã pre-computed → response time giảm từ vài giây xuống milliseconds.


2.13 Alerting System — Brain of Monitoring

Alert Rules Engine

Alert rules engine là component quan trọng nhất — nó quyết định khi nào cần gọi người.

Cấu trúc một alert rule:

FieldMô tảVí dụ
NameTên alertHighCPUUsage
ExpressionPromQL conditionavg(cpu_usage{env="prod"}) by (host) > 90
DurationPhải true bao lâu trước khi firefor 5m (tránh false alarm từ spike ngắn)
SeverityMức độ nghiêm trọngcritical, warning, info
LabelsMetadata cho routingteam="platform", service="payment"
AnnotationsThông tin cho on-callsummary="Host {{ $labels.host }} CPU > 90% for 5m"
Alert States — State Machine
stateDiagram-v2
    [*] --> INACTIVE: Rule created
    INACTIVE --> PENDING: Condition becomes true
    PENDING --> FIRING: Condition true for >= duration
    PENDING --> INACTIVE: Condition becomes false<br/>before duration
    FIRING --> RESOLVED: Condition becomes false
    RESOLVED --> INACTIVE: After cooldown period
    RESOLVED --> PENDING: Condition true again
    FIRING --> FIRING: Condition still true<br/>(re-evaluate every interval)

    note right of PENDING
        Alert chưa fire
        Đang chờ đủ duration
        VD: CPU > 90% mới 2 phút
        (cần 5 phút mới fire)
    end note

    note right of FIRING
        Alert đã fire
        Notification đã gửi
        Tiếp tục gửi reminder
        cho đến khi RESOLVED
    end note

    note right of RESOLVED
        Condition đã về bình thường
        Gửi "resolved" notification
        Cooldown trước khi reset
    end note

Giải thích chi tiết:

StateÝ nghĩaAction
INACTIVEĐiều kiện chưa xảy ra hoặc đã resetKhông làm gì
PENDINGĐiều kiện đã true nhưng chưa đủ durationChờ. Nếu condition false trước duration → quay lại INACTIVE
FIRINGĐiều kiện true >= durationGửi notification, tiếp tục evaluate
RESOLVEDĐiều kiện đã false sau khi đã FIRINGGửi “resolved” notification

Tại sao cần PENDING state? Để tránh flapping alerts. CPU spike lên 95% trong 30 giây rồi xuống 80% — không đáng gửi alert lúc 3 giờ sáng. Chỉ khi CPU > 90% liên tục 5 phút mới fire → giảm false alarms đáng kể.

Alert Manager — Routing & Processing

Alert Manager nhận alerts từ Rules Engine và xử lý trước khi gửi notification:

flowchart TB
    ARE["Alert Rules Engine<br/>Firing alerts"] --> AM["Alert Manager"]

    subgraph AM_Internal["Alert Manager Processing"]
        direction TB
        GROUP["Grouping<br/>Gom alerts cùng nhóm"]
        DEDUP["Deduplication<br/>Loại alert trùng"]
        SILENCE["Silencing<br/>Tắt alert đang maintenance"]
        INHIBIT["Inhibition<br/>Alert A suppress Alert B"]
        THROTTLE["Throttling<br/>Rate limit notifications"]
        ROUTE["Routing<br/>Gửi đến đúng team"]
    end

    AM --> GROUP --> DEDUP --> SILENCE --> INHIBIT --> THROTTLE --> ROUTE

    ROUTE -->|"critical"| PD["PagerDuty<br/>(wake up on-call)"]
    ROUTE -->|"warning"| SLACK["Slack Channel<br/>(#alerts-platform)"]
    ROUTE -->|"info"| EMAIL["Email Digest<br/>(daily summary)"]

    style AM fill:#e91e63,stroke:#333,stroke-width:2px,color:#fff
    style PD fill:#f44336,stroke:#333,stroke-width:2px,color:#fff
    style SLACK fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff

Chi tiết từng bước xử lý:

BướcMô tảVí dụ
GroupingGom alerts có cùng label set thành 1 notification50 servers cùng cluster CPU cao → 1 notification: “Cluster X: 50 hosts high CPU”
DeduplicationKhông gửi lại alert đã gửi (trong repeat interval)Alert “High CPU server-042” đã gửi 5 phút trước → skip (repeat interval = 15m)
SilencingTắt alert cho resources đang maintenanceServer-042 đang patching → silence tất cả alerts cho server-042 từ 2am-4am
InhibitionAlert cấp cao suppress alert cấp thấpAlert “Node down” suppress “High CPU” và “Disk full” cho cùng node (vì node down thì các metrics khác không có ý nghĩa)
ThrottlingRate limit notifications per channelTối đa 10 PagerDuty alerts/hour per team → tránh alert storm
RoutingRoute đến đúng team/channel dựa trên labelsteam="payment" + severity="critical" → PagerDuty payment on-call
Escalation Policy
LevelThời gian chờAction
L10 phútPagerDuty alert to primary on-call
L215 phút không ACKEscalate to secondary on-call
L330 phút không ACKEscalate to team lead + Slack broadcast
L460 phút không ACKEscalate to engineering manager + incident channel

Aha Moment: Alert system tốt không phải gửi nhiều alert — mà là gửi đúng alert, đúng người, đúng lúc, đúng mức. Grouping, dedup, silencing, inhibition — tất cả đều nhằm giảm noise, tăng signal.


2.14 Visualization — Grafana-like Dashboard

Dashboard Architecture
ComponentVai tròChi tiết
Dashboard definitionJSON/YAML config mô tả panels, queries, layoutVersion controlled trong Git
Panel typesGraph (time series), Singlestat, Table, Heatmap, GaugeMỗi panel = 1 hoặc nhiều queries
Variable/templateDynamic filters (dropdown chọn cluster, host, service)Query TSDB label values để populate dropdown
Auto-refreshDashboard tự refresh mỗi 10-30 giâyWebSocket hoặc polling
Time range selectorChọn thời gian: last 15m, 1h, 6h, 24h, 7d, customẢnh hưởng query range
Drill-downClick vào graph → zoom in, click vào host → xem detailLink giữa các dashboard
AnnotationsĐánh dấu events trên timeline (deploy, incident, config change)Correlate metrics với events
Real-time Streaming vs Polling
ApproachMô tảKhi nào dùng
PollingFrontend gọi API mỗi X giây để lấy data mớiĐơn giản, phù hợp refresh interval 10-30s
WebSocketServer push data mới khi cóReal-time dashboard (< 5s latency)
Server-Sent Events (SSE)Unidirectional stream từ serverSimpler than WebSocket, one-way data

Thực tế: Grafana dùng polling (mặc định 10s) vì monitoring data không cần sub-second latency. Polling đơn giản hơn WebSocket nhiều về infrastructure.

Dashboard Best Practices
PracticeMô tảLý do
USE MethodUtilization, Saturation, Errors per resourceBrendan Gregg’s framework — systematic resource analysis
RED MethodRate, Errors, Duration per serviceTom Wilkie’s framework — service-level monitoring
Four Golden SignalsLatency, Traffic, Errors, SaturationGoogle SRE book — most important metrics
Layered dashboardsOverview → Cluster → Host → ProcessDrill-down from high-level to detail
Alerts on dashboardShow active alerts inline with metricsContext for investigating

2.15 Storage — Hot/Warm/Cold Tiering

Storage Tiering Architecture
flowchart TB
    subgraph "Hot Tier (Recent 7 days)"
        HOT_SSD["NVMe SSD<br/>Raw data (10s resolution)<br/>In-memory index<br/>Fastest query"]
    end

    subgraph "Warm Tier (7 days - 3 months)"
        WARM_HDD["HDD / Cheap SSD<br/>Downsampled to 1-min<br/>Compressed<br/>Moderate query speed"]
    end

    subgraph "Cold Tier (3 months - 3 years)"
        COLD_S3["Object Storage (S3/GCS)<br/>Downsampled to 1-hour<br/>Heavily compressed<br/>Slow but cheapest"]
    end

    HOT_SSD -->|"After 7 days:<br/>downsample + migrate"| WARM_HDD
    WARM_HDD -->|"After 3 months:<br/>further downsample + migrate"| COLD_S3
    COLD_S3 -->|"After retention:<br/>delete"| DEL["Delete"]

    QS["Query Service"] -->|"Last 7d queries"| HOT_SSD
    QS -->|"7d-3mo queries"| WARM_HDD
    QS -->|"3mo+ queries"| COLD_S3

    style HOT_SSD fill:#f44336,stroke:#333,stroke-width:2px,color:#fff
    style WARM_HDD fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff
    style COLD_S3 fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff
Storage Cost Comparison
TierMediaCost (estimate/GB/month)Read latencyUse case
HotNVMe SSD$0.20-0.40< 1msReal-time dashboards, alerting
WarmHDD / SATA SSD$0.05-0.105-50msInvestigation, weekly reports
ColdS3/GCS$0.01-0.02100ms-1sCompliance, annual reports
Compaction

Compaction là background process merge nhiều small files thành ít large files:

Vấn đề khi không compactCompaction giải quyết
Hàng ngàn small SSTable filesMerge thành ít large files
Read phải check nhiều files (read amplification)Read chỉ cần check ít files
Deleted data vẫn chiếm space (tombstones)Thực sự xóa data đã tombstoned
Overlapping time ranges giữa filesNon-overlapping time ranges sau compact

Compaction strategies:

StrategyMô tảTrade-off
Size-tieredMerge files có similar sizeWrite-optimized, space amplification cao
LeveledMerge theo levels, mỗi level lớn hơn 10xRead-optimized, write amplification cao
Time-windowMerge files cùng time windowPhù hợp TSDB (data naturally partitioned by time)
Retention Policies
PolicyMô tảConfiguration
Time-basedXóa data older than X daysretention_period = 365d
Size-basedXóa data cũ nhất khi total size > thresholdmax_storage = 10TB
Resolution-basedDownsample data older than X, delete rawraw_retention = 7d, 1m_retention = 90d

3. Capacity Estimation (Uoc luong dung luong)

3.1 Assumptions

Thông sốGiá trịGiải thích
Servers monitored10,000Medium-large infrastructure
Metrics per server100CPU, memory, disk, network, custom
Total time series1,000,00010K x 100
Collection interval10 secondsStandard for infrastructure
Raw data point size (uncompressed)16 bytes8 bytes timestamp + 8 bytes float64 value
Compression ratio12xGorilla encoding average
Tags overhead per series200 bytesAverage tag set size
Retention: raw7 daysHot tier
Retention: 1-minute90 daysWarm tier
Retention: 1-hour365 daysCold tier
Query QPS (dashboard)50050 dashboards x 10 panels x refresh every second
Alert rules5,000Evaluated every 15-60 seconds

3.2 Ingestion Rate

Lưu ý: Con số 10M/s trong requirement bao gồm cả high-cardinality metrics (e.g., per-request latency histograms) và redundancy (push + pull overlap). Với pre-aggregation tại agent level:

Giải thích: Pre-aggregation ở agent level gom 60 data points (10s interval x 6 points/minute) thành 1 aggregated point/minute cho một số metrics, giảm đáng kể volume.

3.3 Storage Estimation

Raw data (Hot tier — 7 ngày)

1-minute downsampled (Warm tier — 90 ngày)

Downsample ratio: 10s → 1min = 6x giảm data points, nhưng lưu 5 aggregates (min, max, avg, sum, count) → net reduction ~1.2x:

1-hour downsampled (Cold tier — 365 ngày)

Total storage

So sánh nếu không compress: Raw storage cho 1 năm = . Với compression + downsampling → 1 TB. Giảm ~50x.

3.4 Index Storage

Inverted index cho labels:

Index nhỏ đủ để fit hoàn toàn trong memory → fast label-based filtering.

3.5 Kafka Throughput

Khuyến nghị: 64 partitions cho headroom và parallelism. Kafka cluster 3-5 brokers, mỗi broker handle ~100 MB/s.

3.6 Query QPS

Nhận xét: Query load khá nhẹ so với write load. Monitoring system là extremely write-heavy — write:read ratio ~ 250:1 (100K writes/s vs 400 reads/s).

3.7 Tóm tắt Estimation

MetricValue
Ingestion rate (raw)10M data points/sec
Ingestion rate (after pre-agg)~167K data points/sec
Kafka throughput~240 MB/s
Hot storage (7d, raw, compressed)~81 GB
Warm storage (90d, 1-min, compressed)~864 GB
Cold storage (365d, 1-hour, compressed)~59 GB
Total storage (1 year)~1 TB
Uncompressed equivalent~50.5 TB
Compression + downsampling savings~50x
Total time series1M
Index size~200 MB
Query QPS (peak)~1,200
Alert rules5,000
Write:Read ratio~250:1

4. Security

4.1 Access Control — Multi-tenant Isolation

Trong tổ chức lớn, nhiều team dùng chung monitoring platform. Security là critical:

ConcernSolutionChi tiết
Team A xem metrics của Team BLabel-based access control (LBAC)Mỗi tenant có label team="X", query engine tự động inject filter {team="X"} dựa trên user identity
Unauthorized alert config changesRBAC (Role-Based Access Control)Roles: viewer (xem dashboard), editor (tạo dashboard/alert), admin (manage users/data sources)
Cross-tenant data leaksNamespace isolationMỗi tenant có separate TSDB namespace hoặc separate database instance
API accessAPI key + OAuth2Mỗi service cần API key để push metrics, mỗi user cần OAuth2 token để query
Multi-tenancy Architecture Options
ApproachMô tảƯuNhược
Shared cluster + label filterTất cả tenants trên 1 TSDB, phân biệt bằng __tenant_id__ labelĐơn giản, resource efficientNoisy neighbor, security boundary yếu
Namespace per tenantMỗi tenant có separate namespace/database trong cùng clusterBalance giữa isolation và efficiencyPhức tạp quản lý
Cluster per tenantMỗi tenant có riêng TSDB clusterIsolation hoàn toànTốn resource, operational overhead

Khuyến nghị: Shared cluster + label filter cho teams nhỏ. Namespace per tenant cho production grade. Cluster per tenant chỉ khi có compliance requirements (e.g., PCI-DSS, HIPAA).

4.2 Preventing Alert Spam

ThreatMitigation
Malicious service push fake metrics → trigger false alertsAuthenticate metric sources, validate metric names against allow-list
Alert rule misconfiguration → alert stormAlert rule review process (PR-based), dry-run mode, max alerts per rule limit
Notification channel abuseRate limit per channel: max 10 PagerDuty/hour, max 100 Slack/hour
Recursive alerting”Monitoring the monitoring” alert không được route qua same monitoring system

4.3 Securing Notification Channels

ChannelSecurity measure
PagerDutyAPI key rotation every 90 days, IP whitelist
SlackWebhook URL stored in secret manager (Vault), not in config files
EmailSPF/DKIM/DMARC to prevent spoofing alert emails
WebhookmTLS between alert manager and webhook endpoint, HMAC signature verification

4.4 Audit Logging

Mọi thay đổi liên quan đến alerting phải được audit:

EventWhat to logRetention
Alert rule created/modified/deletedWho, when, old config, new config2 years
Silence created/expiredWho, what alerts silenced, duration1 year
Notification channel config changedWho, what changed2 years
Dashboard permission changedWho, what granted/revoked1 year
Alert acknowledged/resolvedWho, when, how long to ACK1 year

Tại sao audit logging quan trọng? Post-incident review: “Ai đã silence alert cho server-042 lúc 2am mà không ai biết?” — audit log trả lời câu hỏi này.


5. DevOps — Monitoring the Monitoring System

5.1 Meta-monitoring — The Chicken-and-Egg Problem

Hieu, đây là câu hỏi kinh điển: “Ai monitor cái monitoring system?”

Nếu monitoring system sập mà không ai biết → tất cả alerts đều mất → production sập mà không ai hay.

StrategyMô tả
Separate monitoring systemDùng hệ thống monitoring thứ hai (đơn giản hơn) để monitor hệ thống chính. VD: dùng Pingdom/UptimeRobot external check
Self-monitoringHệ thống tự monitor chính mình (Prometheus monitor Prometheus). Nhưng nếu nó sập → self-monitoring cũng sập
Dead man’s switchGửi heartbeat mỗi phút đến external service. Nếu heartbeat miss → external service alert. Đây là approach được khuyến nghị nhất
Cross-region monitoringMonitoring system ở region A monitor system ở region B và ngược lại

Best practice: Kết hợp self-monitoring + dead man’s switch (Cronitor, Healthchecks.io, hoặc simple external cron check).

5.2 Key Operational Metrics to Monitor

MetricÝ nghĩaThreshold
Ingestion lagKhoảng cách giữa data point timestamp và thời điểm queryable< 30s normal, > 60s warning, > 5m critical
Kafka consumer lagSố messages chưa được consume< 10K normal, > 100K warning, > 1M critical
TSDB write latency P99Thời gian ghi 1 batch vào TSDB< 100ms normal, > 500ms warning
TSDB compaction durationThời gian chạy compaction< 10 min normal, > 30 min warning
TSDB disk usagePhần trăm disk sử dụng< 70% normal, > 80% warning, > 90% critical
Query latency P99Thời gian xử lý query< 2s normal, > 5s warning, > 10s critical
Alert evaluation durationThời gian evaluate tất cả alert rules< evaluation_interval (15s)
Notification delivery latencyThời gian từ alert fire đến notification sent< 30s normal, > 60s warning
Active time seriesSố time series đang active (written to recently)Monitor for cardinality explosion
Dropped data pointsData points bị drop do overload0 is ideal, > 0.1% is concerning

5.3 TSDB Compaction Monitoring

Compaction là background task critical. Nếu compaction tụt lại:

SymptomRoot causeImpact
Compaction queue growingWrite rate > compaction rateQuery slower (more files to scan)
Disk space increasing faster than expectedCompaction không kịp merge/deleteDisk full risk
Query latency tăngQuá nhiều small files chưa compactRead amplification

Actions:

  • Monitor compaction_pending_bytescompaction_duration
  • Set alert nếu compaction lag > 2x average
  • Tune compaction concurrency (number of compaction goroutines/threads)
  • Tăng disk IOPS nếu bottleneck là disk

5.4 Capacity Planning

SignalĐo lườngAction khi trend xấu
Time series growth rateActive series count over timeAdd TSDB nodes hoặc reduce cardinality
Ingestion rate growthData points/sec trendScale Kafka + stream processors
Storage growth rateTB used over timeAdd storage, adjust retention, increase downsampling
Query latency trendP99 latency over weeksAdd query cache, optimize recording rules, add query nodes

5.5 Runbook Essentials

Mỗi alert phải có runbook link. Runbook bao gồm:

SectionNội dung
WhatAlert này nghĩa là gì (plain language)
ImpactẢnh hưởng business nếu không fix
DiagnosisCác bước kiểm tra (dashboard links, queries to run)
RemediationCác bước fix (restart service, scale up, failover)
EscalationKhi nào escalate, escalate cho ai
Post-incidentTạo incident ticket, schedule post-mortem

6. Mermaid Diagrams — Architecture Views

6.1 Overall System Architecture (Chi tiết)

flowchart TB
    subgraph Sources["Metrics Sources"]
        direction LR
        APP["Application<br/>Servers (5K)"]
        DB_SRC["Database<br/>Servers (1K)"]
        MQ_SRC["Message<br/>Queues (500)"]
        LB_SRC["Load<br/>Balancers (200)"]
        K8S["Kubernetes<br/>Pods (3K)"]
        CUSTOM["Custom Business<br/>Metrics"]
    end

    subgraph Collection["Collection Layer"]
        direction LR
        EXPORTER["Prometheus<br/>Exporters"]
        STATSD["StatsD<br/>Agents"]
        TELEGRAF["Telegraf<br/>Agents"]
        SD_COL["Service<br/>Discovery"]
    end

    subgraph Ingestion["Ingestion Pipeline"]
        direction LR
        KAFKA_IN["Kafka Cluster<br/>(5 brokers, 64 partitions)<br/>240 MB/s throughput"]
        FLINK["Apache Flink<br/>(Stream Processing)<br/>Pre-aggregation<br/>Rate computation"]
    end

    subgraph Storage["Storage Layer"]
        direction TB
        TSDB_MAIN["Primary TSDB Cluster<br/>(InfluxDB / VictoriaMetrics)<br/>Write: 167K points/sec"]

        subgraph Tiers["Storage Tiers"]
            direction LR
            HOT_T["Hot (SSD)<br/>7 days<br/>81 GB"]
            WARM_T["Warm (HDD)<br/>90 days<br/>864 GB"]
            COLD_T["Cold (S3)<br/>365 days<br/>59 GB"]
        end
    end

    subgraph QueryAlert["Query & Alerting"]
        direction TB
        QS_MAIN["Query Service<br/>(PromQL Engine)"]
        CACHE_Q["Query Cache<br/>(Redis)"]
        RECORDING["Recording Rules<br/>(Pre-computed)"]
        ALERT_ENGINE["Alert Rules Engine<br/>(5K rules, eval every 15s)"]
        ALERT_MGR["Alert Manager<br/>(Group, Dedup,<br/>Silence, Route)"]
    end

    subgraph Output["Output"]
        direction LR
        GRAFANA["Grafana<br/>Dashboards"]
        PD_OUT["PagerDuty"]
        SLACK_OUT["Slack"]
        EMAIL_OUT["Email"]
        API_OUT["Metrics API<br/>(for automation)"]
    end

    Sources --> Collection
    Collection --> Ingestion
    Ingestion --> Storage
    TSDB_MAIN --- Tiers
    Storage --> QueryAlert
    QS_MAIN --> CACHE_Q
    QS_MAIN --> RECORDING
    QS_MAIN --> GRAFANA
    QS_MAIN --> API_OUT
    ALERT_ENGINE --> ALERT_MGR
    ALERT_MGR --> PD_OUT & SLACK_OUT & EMAIL_OUT
    SD_COL --> Collection

    style KAFKA_IN fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff
    style TSDB_MAIN fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff
    style ALERT_ENGINE fill:#f44336,stroke:#333,stroke-width:2px,color:#fff
    style GRAFANA fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff

6.2 Ingestion Pipeline Detail

flowchart LR
    subgraph Agents["Collection Agents"]
        A1["Agent<br/>server-001"]
        A2["Agent<br/>server-002"]
        AN["Agent<br/>server-N"]
    end

    subgraph Buffer["Kafka Buffer"]
        direction TB
        TOPIC_RAW["Topic: raw-metrics<br/>64 partitions<br/>retention: 24h<br/>replication: 3"]
    end

    subgraph Processing["Stream Processing (Flink)"]
        direction TB
        WINDOW["Tumbling Window<br/>(1 minute)"]
        AGG_SUM["Sum Aggregator<br/>(counters)"]
        AGG_AVG["Average Aggregator<br/>(gauges)"]
        AGG_HIST["Histogram Merger<br/>(distributions)"]
        AGG_RATE["Rate Computer<br/>(counters → rate/sec)"]
    end

    subgraph Output_Kafka["Kafka Output"]
        TOPIC_AGG["Topic: aggregated-metrics<br/>32 partitions"]
    end

    subgraph Write["TSDB Write Path"]
        WAL["Write-Ahead Log<br/>(durability)"]
        MEMTABLE["Memtable<br/>(in-memory, sorted)"]
        FLUSH["Flush to disk<br/>(when memtable full)"]
        SSTABLE["SSTable on disk<br/>(compressed, immutable)"]
    end

    Agents -->|"Batch push<br/>every 10s"| Buffer
    Buffer --> WINDOW
    WINDOW --> AGG_SUM & AGG_AVG & AGG_HIST & AGG_RATE
    AGG_SUM & AGG_AVG & AGG_HIST & AGG_RATE --> Output_Kafka
    Output_Kafka --> WAL
    WAL --> MEMTABLE
    MEMTABLE -->|"Memtable full<br/>(128MB)"| FLUSH
    FLUSH --> SSTABLE

    style TOPIC_RAW fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff
    style MEMTABLE fill:#FFEB3B,stroke:#333,stroke-width:2px,color:#000
    style SSTABLE fill:#90A4AE,stroke:#333,stroke-width:2px,color:#000

6.3 Alerting Flow Detail

flowchart TB
    subgraph RuleEval["Alert Rules Evaluation (every 15s)"]
        RULES["5,000 Alert Rules<br/>(stored in ConfigDB)"]
        EVAL["Rule Evaluator<br/>(parallel evaluation)"]
        TSDB_Q["Query TSDB"]
    end

    subgraph StateM["State Machine per Rule"]
        direction LR
        INACTIVE["INACTIVE"]
        PENDING["PENDING<br/>(waiting for duration)"]
        FIRING["FIRING"]
        RESOLVED["RESOLVED"]
    end

    subgraph AlertMgr["Alert Manager"]
        direction TB
        RECV["Receive firing alerts"]
        GROUP_AM["Group by<br/>(cluster, alertname)"]
        DEDUP_AM["Deduplicate<br/>(same alert within<br/>repeat interval)"]
        SILENCE_AM["Check silences<br/>(maintenance windows)"]
        INHIBIT_AM["Check inhibition<br/>(parent alert<br/>suppresses child)"]
        ROUTE_AM["Route by<br/>severity + team labels"]
    end

    subgraph Notify["Notification"]
        CRIT["CRITICAL<br/>→ PagerDuty<br/>(wake up on-call)"]
        WARN["WARNING<br/>→ Slack #alerts<br/>(business hours)"]
        INFO["INFO<br/>→ Email digest<br/>(daily batch)"]
    end

    subgraph Escalation["Escalation"]
        L1["L1: Primary on-call<br/>(0 min)"]
        L2["L2: Secondary on-call<br/>(+15 min)"]
        L3["L3: Team lead<br/>(+30 min)"]
        L4["L4: Eng Manager<br/>(+60 min)"]
    end

    RULES --> EVAL
    EVAL -->|"PromQL query"| TSDB_Q
    TSDB_Q -->|"Result"| EVAL
    EVAL --> StateM
    FIRING -->|"Fire event"| AlertMgr
    RESOLVED -->|"Resolve event"| AlertMgr
    RECV --> GROUP_AM --> DEDUP_AM --> SILENCE_AM --> INHIBIT_AM --> ROUTE_AM
    ROUTE_AM --> CRIT & WARN & INFO
    CRIT --> L1
    L1 -->|"No ACK in 15m"| L2
    L2 -->|"No ACK in 15m"| L3
    L3 -->|"No ACK in 30m"| L4

    style FIRING fill:#f44336,stroke:#333,stroke-width:2px,color:#fff
    style CRIT fill:#d32f2f,stroke:#333,stroke-width:2px,color:#fff
    style WARN fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff
    style INFO fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff

6.4 Storage Tiering Lifecycle

flowchart LR
    subgraph Ingest["Data Ingestion"]
        NEW["New data point<br/>t = now"]
    end

    subgraph Hot["Hot Tier"]
        HOT_STORE["NVMe SSD<br/>Resolution: 10s<br/>Age: 0 - 7 days<br/>Size: 81 GB"]
    end

    subgraph DS1["Downsampler #1"]
        DOWN1["Aggregate 10s → 1min<br/>(min, max, avg, sum, count)<br/>Runs daily"]
    end

    subgraph Warm["Warm Tier"]
        WARM_STORE["HDD / SATA SSD<br/>Resolution: 1 min<br/>Age: 7 - 90 days<br/>Size: 864 GB"]
    end

    subgraph DS2["Downsampler #2"]
        DOWN2["Aggregate 1min → 1hour<br/>Runs weekly"]
    end

    subgraph Cold["Cold Tier"]
        COLD_STORE["S3 / GCS<br/>Resolution: 1 hour<br/>Age: 90 - 365 days<br/>Size: 59 GB"]
    end

    subgraph Delete["Retention"]
        DEL_PROC["Delete data<br/>older than retention<br/>Runs daily"]
    end

    NEW --> HOT_STORE
    HOT_STORE -->|"Age > 7d"| DS1
    DS1 --> WARM_STORE
    HOT_STORE -->|"Delete raw<br/>after 7d"| DEL_PROC
    WARM_STORE -->|"Age > 90d"| DS2
    DS2 --> COLD_STORE
    WARM_STORE -->|"Delete 1min<br/>after 90d"| DEL_PROC
    COLD_STORE -->|"Age > 365d"| DEL_PROC

    style HOT_STORE fill:#f44336,stroke:#333,stroke-width:2px,color:#fff
    style WARM_STORE fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff
    style COLD_STORE fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff
    style DEL_PROC fill:#9E9E9E,stroke:#333,stroke-width:2px,color:#fff

7. Aha Moments & Pitfalls

7.1 Aha Moments — Bai hoc quan trong

Aha #1: Cardinality Explosion Kills TSDB

Vấn đề: Mỗi unique combination of metric name + label values = 1 time series. Nếu em thêm label request_id (unique per request) vào metric → vô số time series.

Ví dụ:

http_request_duration{method="GET", path="/api/orders", status="200"}
→ 1 time series (OK)

http_request_duration{method="GET", path="/api/orders", status="200", request_id="abc123"}
→ MỖI REQUEST = 1 time series (DISASTER)

Nếu có 10K req/s → 10K new time series/s → 864M time series/day. TSDB sẽ:

  • OOM (out of memory) vì inverted index phình to
  • Query timeout vì phải scan quá nhiều series
  • Compaction không kịp vì quá nhiều files

Rule of thumb: Giữ total active time series < 10M. Nếu label value có cardinality > 1000, không nên là label — nên là log hoặc trace attribute.

Cách phát hiện: Monitor metric active_time_series_count. Alert nếu tăng > 20% trong 1 giờ.

Aha #2: Compression Ratio 10x-50x

Gorilla encoding + delta-of-delta + XOR compression biến time series data từ 16 bytes/point thành 1-2 bytes/point. Đây là lý do:

  • TSDB có thể lưu hàng tỷ data points trên hardware bình thường
  • Relational DB không cạnh tranh được (generic compression chỉ đạt 2-3x)
  • Monitoring ở scale lớn là khả thi về mặt cost

Điều kiện để compression tốt:

  • Data points đến đều đặn (delta-of-delta timestamps ~ 0)
  • Values thay đổi ít giữa consecutive points (XOR ~ 0)
  • Cùng metric name + tags (cùng time series)

Khi compression kém:

  • Irregular timestamps (random arrival) → delta-of-delta lớn
  • Highly volatile values (random fluctuation) → XOR lớn
  • Quá nhiều unique time series (mỗi series quá ngắn để compress hiệu quả)

Aha #3: Downsampling — Trade Precision for Storage

Downsampling không phải “mất data” — mà là chuyển đổi từ raw precision sang statistical summary:

Raw (10s)Downsampled (1h)
78.5, 78.3, 78.7, 78.4, 78.6, 78.2, … (360 points)avg=78.45, min=75.2, max=82.1, count=360

Em mất khả năng biết chính xác CPU lúc 14:32:40 là bao nhiêu. Nhưng em vẫn biết:

  • Trung bình giờ đó CPU bao nhiêu (avg)
  • Spike cao nhất là bao nhiêu (max)
  • Dip thấp nhất là bao nhiêu (min)

Với 3-month old data, đó là đủ thông tin cho hầu hết use cases.

Aha #4: Pull Model Simplifies Health Detection

Trong pull model (Prometheus-style), khi collector không thể scrape target → target down. Đây là “miễn phí” — không cần heartbeat mechanism riêng.

Trong push model, khi collector không nhận data → không biết target down hay network issue hay agent crash. Cần thêm heartbeat/alive check → complexity tăng.

Đó là lý do Prometheus thắng trong Kubernetes ecosystem — K8s service discovery + pull model = elegant monitoring solution.

Aha #5: Alert on Symptoms, Not Causes

Bad alert (cause)Good alert (symptom)
“CPU > 90%""API latency P99 > 500ms"
"Memory > 80%""Error rate > 1%"
"Disk > 70%""User-facing 5xx > 0.5%”

Cause-based alerts (CPU, memory) tạo noise — CPU 90% có thể hoàn toàn bình thường nếu service vẫn fast. Symptom-based alerts (latency, error rate) cho biết user experience đang bị ảnh hưởng.

Google SRE principle: “Alert on symptoms that affect users, debug causes with dashboards.”

7.2 Pitfalls — Bay can tranh

Pitfall #1: Alert Fatigue

Vấn đề: Quá nhiều alerts → on-call engineer bắt đầu ignore alerts → miss critical alert → production down.

Dấu hiệu:

  • 50% alerts là false positive hoặc not actionable

  • On-call engineer ACK alert mà không investigate
  • “Mute all” culture

Giải pháp:

  • Mỗi alert phải actionable — nếu on-call không cần làm gì → đó là log, không phải alert
  • Review alert effectiveness hàng tháng: bao nhiêu % true positive?
  • Tune thresholds dựa trên historical data
  • Dùng for duration để loại transient spikes

Pitfall #2: Monitoring Blind Spots

Vấn đề: Monitor mọi thứ trong hệ thống nhưng quên monitor từ góc nhìn user.

Internal monitoring (cần có)External monitoring (hay quên)
Server CPU, memorySynthetic monitoring (fake user requests from outside)
Database connectionsReal User Monitoring (RUM) — actual user experience
Queue depthEndpoint availability from external checks
Error logsSSL certificate expiry

Giải pháp: Kết hợp internal monitoring + external synthetic checks (Pingdom, Datadog Synthetics).

Pitfall #3: TSDB Không Scale — Single Point of Failure

Vấn đề: Single Prometheus instance có giới hạn:

  • ~1M active time series (beyond this → OOM risk)
  • Single-node storage (disk failure = data loss)
  • No horizontal scaling

Giải pháp tầng:

Scale levelSolution
< 1M seriesSingle Prometheus instance
1M - 10M seriesPrometheus federation (hierarchical)
10M - 100M seriesThanos / Cortex / VictoriaMetrics cluster
> 100M seriesDedicated TSDB cluster (InfluxDB Enterprise, M3DB)

Pitfall #4: Không có Retention Policy → Disk Full

Vấn đề: Quên set retention → data tích lũy vô hạn → disk full → TSDB crash → monitoring system down → không ai biết production đang sập.

Giải pháp:

  • Set retention policy từ ngày 1
  • Monitor disk usage của TSDB node
  • Auto-delete + downsampling pipeline
  • Alert khi disk > 80%

Pitfall #5: Dashboard Overload

Vấn đề: 200 dashboards mà không ai dùng, 50 panels trên 1 dashboard mà không ai hiểu.

Giải pháp:

  • Mỗi service có 1 dashboard chính với Four Golden Signals (latency, traffic, errors, saturation)
  • Layered approach: Overview → Cluster → Service → Instance
  • Dashboard ownership: mỗi dashboard có owner (team/person)
  • Deprecate dashboards không ai xem trong 30 ngày

Pitfall #6: Ignoring Label Naming Conventions

Vấn đề: Team A dùng host, Team B dùng hostname, Team C dùng server. Cùng concept, khác label → không thể aggregate cross-team.

Giải pháp:

  • Establish label naming convention sớm
  • Enforce qua metric validation tại ingestion
  • Common labels: host, region, env, service, team, cluster

8. Tong ket — Key Design Decisions

DecisionOptionsRecommendationLý do
Collection modelPush vs PullHybrid (Pull + Push gateway)Pull cho infrastructure, Push cho ephemeral jobs
BufferDirect write vs KafkaKafkaDecouple, burst handling, fan-out
TSDBRelational vs TSDBTSDB (InfluxDB/VictoriaMetrics/Prometheus)10-100x better for time series workload
CompressionGeneric vs GorillaGorilla encoding10-50x compression ratio
DownsamplingKeep all raw vs DownsampleMulti-resolution downsamplingBalance precision vs cost
StorageSingle tier vs Multi-tierHot/Warm/Cold tieringCost optimization
AlertingSimple threshold vs State machineState machine (PENDING → FIRING → RESOLVED)Reduce false alarms
Alert processingDirect send vs Alert ManagerAlert Manager (group, dedup, silence)Reduce noise, route correctly
QuerySQL vs PromQLPromQL-likePurpose-built for time series
DashboardCustom vs GrafanaGrafanaIndustry standard, rich ecosystem

9. Interview Tips

Hieu, nếu em gặp bài này trong interview, đây là flow em nên follow:

BướcThời gianNói gì
Clarify requirements5 phútBao nhiêu servers? Bao nhiêu metrics? Retention bao lâu? Cần alerting không?
High-level design10 phútVẽ 5 components: Collection → Kafka → TSDB → Query/Alerting → Dashboard
Deep dive — Data model5 phútGiải thích time series structure, tại sao TSDB không dùng relational DB
Deep dive — Ingestion5 phútPush vs pull, Kafka as buffer, pre-aggregation
Deep dive — TSDB5 phútLSM tree, Gorilla compression, downsampling
Deep dive — Alerting5 phútState machine, Alert Manager (group/dedup/silence)
Estimation5 phútWrite throughput, storage with compression, query QPS
Trade-offs5 phútCardinality explosion, monitoring the monitoring, alert fatigue

Pro tip: Interviewer đặc biệt ấn tượng khi em mention: (1) Gorilla compression, (2) cardinality explosion, (3) monitoring the monitoring system — đây là những topic cho thấy em có production experience, không chỉ đọc sách.


TopicLinkLiên quan thế nào
Monitoring & Observability foundationsTuan-13-Monitoring-ObservabilityNền tảng lý thuyết, Three Pillars, SLI/SLO/SLA
Message Queue (Kafka)Tuan-08-Message-QueueKafka là core component trong ingestion pipeline
Cache StrategyTuan-06-Cache-StrategyQuery cache, in-memory index, memtable
Database Sharding & ReplicationTuan-07-Database-Sharding-ReplicationTSDB clustering, time-based partitioning
Back-of-the-envelope EstimationTuan-02-Back-of-the-envelopeCapacity estimation framework
Scaling fundamentalsTuan-01-Scale-From-Zero-To-MillionsHorizontal scaling for TSDB cluster
Notification SystemTuan-19-Design-Notification-SystemAlert notification delivery (PagerDuty, Slack, Email)

“Monitoring tốt không phải là biết mọi thứ — mà là biết đúng thứ, đúng lúc, và không bao giờ miss thứ quan trọng.”

Next step: Hieu, sau bài này em nên thực hành setup Prometheus + Grafana + Alertmanager trên local Docker Compose. Hands-on experience sẽ solidify tất cả concepts trên.