MongoDBが「沈黙したまま」死んでいく — 自分が経験した問題
午前2時、本番データベースが急に重くなった。アラートは何もなかった。事前に警告ログも出ていなかった。ユーザーからアプリが開けないと報告が来て初めて気づいた — そのとき、replica setのprimaryはその夜に3回も再選出されていた。
まともな監視ツールがなかった頃、障害対応の手順はこうだった:各サーバーにSSHして、mongo shellでdb.serverStatus()を実行、数値をメモ帳にコピーして、手作業で比較する。1時間かけても、何の数値を見ているのかさえ確信が持てなかった。今はGrafanaダッシュボードを開くだけで全部わかる — コネクションプール、レプリケーションラグ、スロークエリ — すべてがリアルタイムで1画面に表示される。
この記事では、MongoDBが最もよく「隠す」3つの問題に絞って説明する:Replica Setの健全性、コネクションプールの枯渇、スローオペレーション — 「なんかアプリが遅い、理由不明」の本当の原因になりやすいものだ。
なぜMongoDBの監視は思ったより難しいのか
MongoDBにはdb.serverStatus()とdb.currentOp()が標準で用意されているが、これらはその瞬間のデータに過ぎない。能動的にクエリを実行しなければならないし、時系列でのトレンド比較に使えるような履歴も保存されない。
Replica Setはさらに複雑さが増す。Primaryはいつでもフェイルオーバーし得る。Secondaryのラグは静かに増えていく — read concernに影響が出てユーザーが古いデータを見るようになるまで、誰も気づかない。
コネクションプールは最も見落とされがちな要素だ。デフォルトでMongoDBドライバーはかなり大きなプールサイズを保持する。しかしアプリがコネクションをリークしたり、トラフィックスパイクが発生したりすると、プールは数秒で枯渇してしまう。アプリ側のエラーは大抵、タイムアウトというざっくりしたメッセージで、MongoDBには一切触れられない。そのため、間違った場所を20〜30分デバッグするはめになりやすい。
スロークエリについては、MongoDBにProfilerがあるが、高いレベルで有効にするとパフォーマンスに影響が出る。100ms以上のスローオペレーションだけをログに残すLevel 1が妥当な設定だが、それでも時系列での集計と可視化にはツールが必要だ。
Prometheus MongoDB Exporterのインストール
Percona版を使う — メトリクスが最も充実しており、replica setとshardingをサポートしている:
# 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/
# exporter用のMongoDBユーザーを作成(mongo shell内で実行)
use admin
db.createUser({
user: "prometheus",
pwd: "strong_password_here",
roles: [
{ role: "clusterMonitor", db: "admin" },
{ role: "read", db: "local" }
]
})
exporterが起動時に自動で実行されるよう、systemdサービスを作成する:
# /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
# 有効化して動作確認
sudo systemctl daemon-reload
sudo systemctl enable --now mongodb_exporter
curl -s localhost:9216/metrics | grep mongodb_up
--collect-allフラグはreplica setとindexingのstatsを含む全コレクターを有効にする。--discovering-modeはフェイルオーバー発生時に重要で、トポロジーの変化を自動で検出する。
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
Replica Setの健全性を監視する
Replica Setの健全性は、インシデント発生時に最初に確認するもの — MongoDB本番環境では他のどのメトリクスよりも重要だ。レプリケーションラグが増加しているということは、secondaryがprimaryに追いつけていないことを意味する。このとき、secondaryからの読み取りは古いデータを返す — しかもラグが大きい状態でprimaryがダウンすると、通常より多くのデータが失われるフェイルオーバーになってしまう。
# 各secondaryのレプリケーションラグ(秒)
mongodb_mongod_replset_member_replication_lag{state="SECONDARY"}
# replica setメンバーの状態(1 = 正常、0 = ダウン)
mongodb_mongod_replset_member_health
# 正常なメンバー数 — majorityを失った場合にアラート
count(mongodb_mongod_replset_member_health == 1) by (set)
# Oplogウィンドウ(時間)— secondaryがキャッチアップに要する時間
(mongodb_mongod_replset_oplog_head_timestamp - mongodb_mongod_replset_oplog_tail_timestamp) / 3600
アラートルールは障害が起きる前から設定しておくべきだ:
- alert: MongoDBReplicationLagHigh
expr: mongodb_mongod_replset_member_replication_lag > 30
for: 2m
labels:
severity: warning
annotations:
summary: "MongoDBレプリケーションラグが高い: {{ $value }}s"
description: "Secondary {{ $labels.instance }}のラグがprimaryより{{ $value }}s遅れています"
- alert: MongoDBReplicaSetMemberDown
expr: count(mongodb_mongod_replset_member_health == 0) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "MongoDB Replica Setのメンバーがダウンしています"
コネクションプールを監視する
コネクションプールの枯渇は、MongoDBから直接エラーとして報告されることがほとんどない。アプリ側ではタイムアウトとしか見えず、20〜30分間違った場所をデバッグしてやっとコネクションプールに思い当たる、というパターンが多い。以下のメトリクスはダッシュボードに最初から入れておくべきだ:
# 現在のコネクション数と空きコネクション数
mongodb_ss_connections{conn_type="current"}
mongodb_ss_connections{conn_type="available"}
# コネクションプールの使用率 — 80%超でアラート
(
mongodb_ss_connections{conn_type="current"} /
(mongodb_ss_connections{conn_type="current"} + mongodb_ss_connections{conn_type="available"})
) * 100
# 新規コネクション作成レート — 急増は問題のサイン
rate(mongodb_ss_connections{conn_type="totalCreated"}[5m])
よく見かけるパターン:コネクション数が急増してすぐに急落する — これはMongoDBの問題ではなく、アプリコードのコネクションリークによる典型的なサインだ。Grafanaのグラフでは、ギザギザの鋸歯状の波形が鮮明に現れる。実際のトラフィック増加とは明確に異なる:トラフィックに伴う場合はゆっくり増えてゆっくり減る。
スローオペレーションを検出する
パフォーマンスに影響を与えずにスロークエリをキャプチャするため、MongoDBのProfilerをlevel 1で有効にする:
# mongo shell内で実行 — level 1のProfilerを有効化(100ms超のスローオペレーションを記録)
use myDatabase
db.setProfilingLevel(1, { slowms: 100 })
# 現在の設定を確認
db.getProfilingStatus()
# 最近のスロークエリを直接表示(デバッグ時に便利)
db.system.profile.find().sort({ ts: -1 }).limit(10).pretty()
mongodb_exporterはスキャン比率をエクスポートする — インデックスが欠けているクエリを検出するための重要な指標だ:
# スキャンドキュメント数 / 返却ドキュメント数
# 比率が高い(例:1000:1)= フルスキャン、インデックスなし
mongodb_mongod_metrics_query_executor_total{state="scanned"} /
mongodb_mongod_metrics_query_executor_total{state="returned"}
# 操作タイプ別の1秒あたりオペレーション数
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])
# ページフォルト — ワーキングセットがRAMを超えているサイン
rate(mongodb_ss_extra_info{extra_info_type="page_faults"}[5m])
GrafanaダッシュボードをImportする
ゼロから構築する代わりに、Grafana Marketplaceのダッシュボードをインポートしよう:
- Dashboard ID 14997:MongoDB Overview by Percona — 最も充実しており、Replica SetパネルとWiredTiger statsを含む
- Dashboard ID 2583:MongoDB Exporter — 軽量版、スタンドアロンインスタンスに向いている
Grafana → Dashboards → Import → IDを入力 → Load → Prometheusデータソースを選択。
インポート後、コネクションプール使用率をGaugeパネルで追加し、色の閾値を設定する:60%未満は緑、60〜80%は黄、80%超は赤。数値を読まなくても一目で状態がわかる。
アラート疲れを防ぐ3段階のアラート設計
アラートを設定しすぎると、結局全部無視するようになってしまう。3段階に分けるのがおすすめだ:
- Critical(電話 / PagerDuty):Replica setがmajority votesを失った、レプリケーションラグ > 60秒、空きコネクション < 5%
- Warning(Telegram):レプリケーションラグ > 30秒、コネクション使用率 > 80%、10分間連続でページフォルト増加
- Info(Slack):スロークエリレートが先週のベースラインより50%以上増加
多くの人が見落とす点:静的な閾値ではなく変化率(rate-of-change)でアラートを設定すること。クエリレイテンシ500msが、あるシステムでは正常でも、別のシステムでは致命的な問題になる。PromQLではこう書ける:
# 1時間前と比べてクエリレートが5倍を超えた場合にアラート
rate(mongodb_ss_opcounters{legacy_op_type="query"}[5m]) /
rate(mongodb_ss_opcounters{legacy_op_type="query"}[5m] offset 1h) > 5
mongodb_exporterは思ったよりずっと軽量だ — RAMはわずか約50MB、15秒ごとのスクレイプもMongoDBに影響を与えない。セットアップに数時間かかるが、その見返りとして、次に午前2時に問題が起きたとき、Grafanaを開けば何が起きているのか、いつから起きているのかが即座にわかる — 全てが炎上した後でサーバーをSSHで飛び回る必要はなくなる。

