MongoDB “im lặng” cho đến khi chết — vấn đề mình từng gặp
Production database bị chậm lúc 2 giờ sáng. Không có alert nào. Không có log cảnh báo nào hiện ra trước đó. Chỉ đến khi user phản ánh app không load được thì mình mới biết — và lúc đó replica set primary đã bị bầu lại 3 lần trong đêm.
Trước khi có monitoring đàng hoàng, quy trình xử lý sự cố của mình là: SSH vào từng server, chạy db.serverStatus() trong mongo shell, copy số ra notepad, so sánh bằng tay. Mất cả tiếng đồng hồ mà vẫn không chắc đang nhìn vào số gì. Bây giờ chỉ cần mở Grafana dashboard là thấy hết — connection pool, replication lag, slow queries — tất cả real-time trên một màn hình.
Bài này tập trung vào 3 thứ mà MongoDB hay “giấu” nhất: Replica Set health, Connection Pool exhaustion, và Slow Operations — những thứ thường là nguyên nhân thực sự đằng sau “app chậm không rõ lý do”.
Tại sao MongoDB khó giám sát hơn bạn nghĩ
MongoDB có sẵn db.serverStatus() và db.currentOp(), nhưng đây là point-in-time data. Bạn phải chủ động query, và không có gì lưu lịch sử để so sánh xu hướng theo thời gian.
Replica Set lại có thêm một lớp phức tạp nữa. Primary có thể failover bất kỳ lúc nào. Secondary lag tăng âm thầm — không ai hay cho đến khi read concern bị ảnh hưởng và user bắt đầu thấy data cũ.
Connection pool là thứ hay bị bỏ qua nhất. Mặc định MongoDB driver giữ pool size khá lớn. Nhưng khi ứng dụng leak connection hoặc có spike traffic, pool cạn trong vài giây. Error phía app lúc này thường chỉ là timeout chung chung — không hề nhắc đến MongoDB, nên dễ mất thêm 20-30 phút debug nhầm chỗ.
Slow queries thì MongoDB có Profiler, nhưng enable ở level cao sẽ ảnh hưởng performance. Level 1 — chỉ log slow ops trên 100ms — là mức hợp lý, nhưng vẫn cần tool để aggregate và visualize theo thời gian.
Cài đặt Prometheus MongoDB Exporter
Dùng bản của Percona — đầy đủ metrics nhất, support replica set và sharding:
# Download mongodb_exporter (Percona build)
wget https://github.com/percona/mongodb_exporter/releases/download/v0.40.0/mongodb_exporter-0.40.0.linux-amd64.tar.gz
tar xzf mongodb_exporter-0.40.0.linux-amd64.tar.gz
sudo mv mongodb_exporter /usr/local/bin/
# Tạo MongoDB user riêng cho exporter (trong mongo shell)
use admin
db.createUser({
user: "prometheus",
pwd: "strong_password_here",
roles: [
{ role: "clusterMonitor", db: "admin" },
{ role: "read", db: "local" }
]
})
Tạo systemd service để exporter chạy tự động khi khởi động:
# Nội dung file /etc/systemd/system/mongodb_exporter.service
[Unit]
Description=MongoDB Exporter
After=network.target
[Service]
User=prometheus
ExecStart=/usr/local/bin/mongodb_exporter \
--mongodb.uri="mongodb://prometheus:strong_password_here@localhost:27017/admin" \
--collect-all \
--discovering-mode
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
# Kích hoạt và kiểm tra
sudo systemctl daemon-reload
sudo systemctl enable --now mongodb_exporter
curl -s localhost:9216/metrics | grep mongodb_up
Flag --collect-all bật toàn bộ collector bao gồm replica set và indexing stats. --discovering-mode tự detect topology thay đổi — quan trọng khi xảy ra failover.
Thêm job scrape vào Prometheus:
# prometheus.yml
scrape_configs:
- job_name: 'mongodb'
static_configs:
- targets: ['localhost:9216']
labels:
instance: 'mongo-primary'
env: 'production'
scrape_interval: 15s
scrape_timeout: 10s
Theo dõi Replica Set Health
Replica Set health là thứ mình xem đầu tiên khi có incident — quan trọng hơn bất kỳ metric nào khác trong MongoDB production. Replication lag tăng nghĩa là secondary đang không bắt kịp primary. Đọc từ secondary lúc này trả data cũ — và nếu primary down đúng lúc lag đang cao, failover sẽ mất data nhiều hơn bình thường.
# Replication lag của từng secondary (giây)
mongodb_mongod_replset_member_replication_lag{state="SECONDARY"}
# Trạng thái replica set members (1 = healthy, 0 = down)
mongodb_mongod_replset_member_health
# Số members còn healthy — alert nếu mất majority
count(mongodb_mongod_replset_member_health == 1) by (set)
# Oplog window (giờ) — secondary cần catch up trong bao lâu
(mongodb_mongod_replset_oplog_head_timestamp - mongodb_mongod_replset_oplog_tail_timestamp) / 3600
Alert rule nên có ngay từ đầu, trước khi sự cố xảy ra:
- alert: MongoDBReplicationLagHigh
expr: mongodb_mongod_replset_member_replication_lag > 30
for: 2m
labels:
severity: warning
annotations:
summary: "MongoDB replication lag cao: {{ $value }}s"
description: "Secondary {{ $labels.instance }} lag {{ $value }}s so với primary"
- alert: MongoDBReplicaSetMemberDown
expr: count(mongodb_mongod_replset_member_health == 0) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "MongoDB Replica Set có member bị down"
Giám sát Connection Pool
Connection pool exhaustion hiếm khi báo lỗi trực tiếp từ MongoDB. Phía app, bạn chỉ thấy timeout — rồi mất thêm 20-30 phút debug nhầm chỗ mới nghĩ đến connection pool. Mấy metrics này nên có trên dashboard ngay từ đầu:
# Số connection hiện tại và còn trống
mongodb_ss_connections{conn_type="current"}
mongodb_ss_connections{conn_type="available"}
# Tỷ lệ sử dụng connection pool — alert khi > 80%
(
mongodb_ss_connections{conn_type="current"} /
(mongodb_ss_connections{conn_type="current"} + mongodb_ss_connections{conn_type="available"})
) * 100
# Rate tạo connection mới — tăng đột biến là dấu hiệu có vấn đề
rate(mongodb_ss_connections{conn_type="totalCreated"}[5m])
Một pattern mình hay thấy: connection count tăng đột biến rồi giảm ngay — dấu hiệu điển hình của connection leak trong code ứng dụng, không phải do MongoDB. Graph Grafana sẽ vẽ đường răng cưa rất rõ. Khác hẳn với load tăng thật sự: tăng từ từ theo traffic, giảm từ từ sau đó.
Phát hiện Slow Operations
Bật MongoDB Profiler ở level 1 để capture slow queries mà không ảnh hưởng performance:
# Trong mongo shell — enable profiler level 1 (log slow ops > 100ms)
use myDatabase
db.setProfilingLevel(1, { slowms: 100 })
# Kiểm tra cấu hình hiện tại
db.getProfilingStatus()
# Xem slow queries gần đây trực tiếp (debug nhanh)
db.system.profile.find().sort({ ts: -1 }).limit(10).pretty()
mongodb_exporter export scan ratio — chỉ số quan trọng để phát hiện query thiếu index:
# Documents scanned / documents returned
# Tỷ lệ cao (ví dụ 1000:1) = query full scan, thiếu index
mongodb_mongod_metrics_query_executor_total{state="scanned"} /
mongodb_mongod_metrics_query_executor_total{state="returned"}
# Operations per second theo loại
rate(mongodb_ss_opcounters{legacy_op_type="query"}[1m])
rate(mongodb_ss_opcounters{legacy_op_type="update"}[1m])
rate(mongodb_ss_opcounters{legacy_op_type="delete"}[1m])
# Page faults — dấu hiệu working set lớn hơn RAM
rate(mongodb_ss_extra_info{extra_info_type="page_faults"}[5m])
Import Dashboard Grafana
Thay vì build từ đầu, import dashboard sẵn từ Grafana marketplace:
- Dashboard ID 14997: MongoDB Overview by Percona — đầy đủ nhất, có Replica Set panel và WiredTiger stats
- Dashboard ID 2583: MongoDB Exporter — nhẹ hơn, phù hợp standalone instance
Vào Grafana → Dashboards → Import → nhập ID → Load → chọn Prometheus datasource.
Sau khi import, thêm panel Connection Pool utilization dạng Gauge với ngưỡng màu: xanh khi < 60%, vàng khi 60–80%, đỏ khi > 80%. Nhìn vào là biết ngay tình trạng mà không cần đọc số.
Phân tầng alert để không bị alarm fatigue
Cài alert quá nhiều thì thành ra bỏ qua hết. Mình chia làm 3 tầng:
- Critical (gọi điện / PagerDuty): Replica set mất majority votes, replication lag > 60 giây, available connections < 5%
- Warning (Telegram): Replication lag > 30 giây, connection usage > 80%, page faults tăng liên tục trong 10 phút
- Info (Slack): Slow query rate tăng > 50% so với baseline tuần trước
Một điều nhiều người bỏ qua: đặt alert dựa trên rate-of-change thay vì threshold tĩnh. Query latency 500ms có thể bình thường với hệ thống này nhưng là thảm họa với hệ thống kia. PromQL cho điều này:
# Alert khi query rate tăng 5x so với giờ trước
rate(mongodb_ss_opcounters{legacy_op_type="query"}[5m]) /
rate(mongodb_ss_opcounters{legacy_op_type="query"}[5m] offset 1h) > 5
mongodb_exporter nhẹ hơn bạn nghĩ — chỉ ~50MB RAM, scrape 15 giây một lần không ảnh hưởng gì đến MongoDB. Setup mất vài tiếng. Nhưng đổi lại, lần sau 2 giờ sáng có vấn đề, bạn mở Grafana là biết ngay cái gì đang xảy ra và từ lúc nào — chứ không phải mò mẫm SSH từng server khi mọi thứ đã cháy.

