Aurora PostgreSQL深夜2時の接続切れで学んだ、本番安定化の3つの現実

本番環境で3度のDB障害を経験。接続プール枯渇・バキューム遅延・デフォルト設定の罠を乗り越えた3年の実装知見を、実際のトラブルシューティングコードと共に公開します。

Aurora PostgreSQL深夜障害で学んだ、2026年版RDS運用の本当のコツ

深夜2時。Slackの通知音で目が覚めた。いつものやつだ——「Database connection limit exceeded」。

うちのチームがAurora PostgreSQLで本番を止めたのはこれで3度目。前回は接続プール枯渇、その前はバキューム遅延。毎回同じパターンで怒られて、「デフォルト設定を信じればなんとかなる」という幻想が完全に吹き飛んだ。

正直、PostgreSQLの設定ドキュメントは地獄だ。パラメータは数百個あるし、本番環境での実測値と推奨値が全然違う。でも3年間の試行錯誤を通じて、「実際に本番で動く設定」がようやく見えてきた。

RDS運用で最初に学ぶべき「接続管理」の現実

最初の失敗は単純だった。接続数の上限を計算してなかったんだ。

デフォルトだとmax_connectionsは自動で設定されるんだけど、うちは急成長してアプリケーションサーバーを8台に増やした。各サーバーから10個の接続が来るから、80個の接続。余裕があるなって思ってた。

でも実際には、こんなふうに確認してみたら:

-- 接続の内訳を確認
SELECT datname, usename, state, count(*) 
FROM pg_stat_activity 
GROUP BY datname, usename, state;

アイドル接続が150個以上残ってた。キープアライブがOFFになってたせいで、閉じられずに溜まってたんだ。

接続プール(PgBouncer)の導入が救いだった。正直、2026年時点で本番PostgreSQLを直接つなぐのはもう時代遅れだと言い切れる。

# pgbouncer.ini の基本設定
[databases]
mydb = host=aurora-endpoint.rds.amazonaws.com port=5432 user=postgres password=xxxxx

[pgbouncer]
pool_mode = transaction  # 重要: トランザクション終了後に接続を返す
max_client_conn = 1000
default_pool_size = 25   # 実験的に設定、監視必須
reserve_pool_size = 5
reserve_pool_timeout = 3

transactionモードが大事。session モードだと接続を再利用できず、コネクション枯渇のリスクが高まる。うちはデフォルトの session モードで3ヶ月ハマった。

本番環境でのコネクション数は、実際に負荷テストをやらないと分からない。ドキュメントに書いてある「ベストプラクティス」は、たいていあなたの環境には合ってないんだ。

バキューム地獄に5ヶ月ハマった話

接続プール問題を解決してから2ヶ月後、今度は「テーブルがデッドロック状態」という別の悪夢が襲ってきた。

updateやdeleteで大量のデータを扱うテーブルがあるんだけど、バキューム処理が追いつかなくなってたんだ。結果、テーブルが膨張し続けて、クエリのスキャンコストが爆上がり。状況を把握するために、こんなクエリを走らせてみたら:

-- テーブルサイズを確認
SELECT 
  schemaname,
  tablename,
  pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size,
  ROUND(100 * (pg_total_relation_size(schemaname||'.'||tablename) - pg_relation_size(schemaname||'.'||tablename)) / pg_total_relation_size(schemaname||'.'||tablename)) AS index_ratio
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

ある1つのテーブルが450GBに……。本来なら150GBで済むはずだったんだ。デッドタプルが溜まりまくってた。

デフォルトのオートバキューム設定じゃ、全然追いつかないんだよ。試行錯誤の末、本番で動いた設定がこれ:

-- 大型テーブル向けバキューム設定
ALTER TABLE large_table SET (
  autovacuum_vacuum_scale_factor = 0.01,    -- デフォルト0.2を10分の1に
  autovacuum_vacuum_cost_delay = 2,         -- ディスクI/O負荷を下げる
  autovacuum_vacuum_cost_limit = 200        -- ディスク負荷と実行頻度のバランス調整
);

でも正直、これでも完璧じゃない。実装してから1週間後、深夜にまたバキューム遅延でアラートが。パラメータをさらに調整したり、不要なインデックスを削除したり、試行錯誤の連続だった。

2026年の結論としては、「バキューム」は設定だけじゃなく、スキーマ設計の段階から意識しないと地獄を見るってことだ。チームメンバーも「DELETE/UPDATEしまくる方法は避けよう」という文化を作るのに3ヶ月かかったんだからね。

CloudWatch メトリクス監視で本番を守る実装

3度目の大障害を経験してから、チーム全体で監視を本気で整備することにした。こんなスクリプトで定期的にメトリクスを取得してる:

#!/bin/bash
# CloudWatch メトリクス取得スクリプト
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnections \
  --dimensions Name=DBInstanceIdentifier,Value=aurora-prod \
  --start-time 2026-08-09T00:00:00Z \
  --end-time 2026-08-10T00:00:00Z \
  --period 300 \
  --statistics Average,Maximum

これを定期的に見てると、パターンが見えてくる。うちの環境では、こんなふうに動いてる:

時間帯接続数理由
朝8時100→150ユーザーログイン時間
昼12時150(安定)ピーク時間帯
夜12時150→30夜間バッチ完了
深夜2-4時30→120(スパイク)データウェアハウス同期

このパターンを理解してから、バッチ処理のタイミングを変えたり、接続プールサイズを動的に調整したりできるようになった。

AWS RDS Enhanced Monitoring も導入した。これでPostgreSQLプロセスレベルの詳細が見えるんだ:

{
  "cpuUtilization": {
    "guest": 0,
    "irq": 0.1,
    "user": 25.3,
    "system": 8.2,
    "wait": 12.1
  },
  "diskIO": [
    {
      "readLatency": 2.1,
      "writeLatency": 4.3,
      "readThroughput": 50.2,
      "writeThroughput": 120.5
    }
  ]
}

この data から、「I/O待機が多い時間帯は接続数を制限しよう」とか「バッチ処理の順序を変えよう」とかいう判断ができる。これが本当に役立つんだ。

2026年版・本番PostgreSQL構成図

graph TB
  subgraph "Application Layer"
    App1["App Server 1"]
    App2["App Server 2"]
    App3["App Server 3"]
  end

  subgraph "Connection Pool Layer"
    PGB1["PgBouncer (transaction mode)"]
    PGB2["PgBouncer (Replica)"]
  end

  subgraph "AWS RDS"
    subgraph "Primary"
      AuroraPrimary["Aurora PostgreSQL 17<br/>instance: db.r7g.2xlarge"]
    end
    
    subgraph "Read Replicas"
      AuroraRead1["Read Replica 1<br/>db.r7g.xlarge"]
      AuroraRead2["Read Replica 2<br/>db.r7g.xlarge"]
    end
  end

  subgraph "Monitoring & Backup"
    CloudWatch["CloudWatch<br/>Enhanced Monitoring"]
    EventBridge["EventBridge<br/>Alert Rules"]
    BackupVault["Automated Backup<br/>35-day retention"]
  end

  App1 -->|Connection| PGB1
  App2 -->|Connection| PGB1
  App3 -->|Connection| PGB1
  
  PGB1 -->|Pooled Connections| AuroraPrimary
  PGB2 -->|Read Pool| AuroraRead1
  PGB2 -->|Read Pool| AuroraRead2
  
  AuroraPrimary -->|Replication| AuroraRead1
  AuroraPrimary -->|Replication| AuroraRead2
  
  AuroraPrimary -->|Metrics| CloudWatch
  CloudWatch -->|Alert| EventBridge
  AuroraPrimary -->|Backup| BackupVault

見ての通り、もはやPostgreSQLをダイレクトにつなぐことはしない。すべてコネクションプールを経由する。そして監視は複層。これでようやく「深夜2時の電話」が減ったんだ。

パラメータチューニング:実測で決めるの重要性

postgresql.conf の重要パラメータは、RDS のパラメータグループで管理する。2026年時点で、うちが本番で使ってる設定がこれ:

shared_buffers = 16GB           # インスタンス RAM の 25%
effective_cache_size = 48GB     # RAM の 75%
work_mem = 256MB                # shared_buffers / max_connections
maintenance_work_mem = 4GB      
wal_buffers = 16MB              
default_statistics_target = 100 # クエリプランニング精度向上
random_page_cost = 1.1          # SSD での推奨値
effective_io_concurrency = 200  # SSD での推奨値(NVMe対応)

でも正直なところ、これらの値は「テンプレート」に過ぎない。本当には、アプリケーションのワークロードを見て調整する必要がある。

実装直後1ヶ月は、毎日 CloudWatch を見て、パラメータを微調整した。特に work_mem は大きすぎるとメモリリークを招くし、小さすぎるとディスク I/O が増える。適切な値は、本番データ量と同時実行数で決まるんだ。

バージョンアップデートのリスク

2026年は PostgreSQL 17 が安定版になった。うちは 16 から 17 への移行を検討してるんだけど、本番環境での検証に3ヶ月予定してる。

理由は単純——バージョンアップで挙動が変わることがあるからだ。過去に 14→15 アップグレードで、特定のクエリが 10倍遅くなったことがある。プランナーの改善のせいだったんだけど、統計情報を再取得したら戻ったんだ。

-- メジャーアップグレード前の検証
ANALYZE;  -- 統計情報を再計算
VACUUM;   -- デッドタプル削除

-- 本番相当のクエリでプラン比較
EXPLAIN ANALYZE 
SELECT * FROM large_table WHERE condition;

本番環境でのアップグレードは、レプリカを経由する Blue-Green デプロイ方式で実施する。ダウンタイムなしで新バージョンを検証できるから、リスクが大幅に下がるんだ。

本当に必要な3つの運用ルール

個人的には、これら3つが揃ってないと本番は安定しないと強く感じてる:

1. 接続プール導入は必須(PgBouncer or RDS Proxy)

  • デフォルトの session モードは使わない
  • 環境に応じて transaction モードのパラメータを調整する

2. バキューム設定は static じゃなく dynamic(テーブルごとに調整)

  • 自動バキュームはあくまで補助手段
  • テーブルの書き込みパターンに応じて細かく調整が必要

3. 監視は “数値” で、感覚では判断しない(CloudWatch Enhanced Monitoring 必須)

  • パターン認識が本番安定性を左右する
  • 異常値を検出したら即座にアラートを設定

これらがないと、深夜の電話は避けられない。

まとめ

3年間の運用経験から言えることは、PostgreSQL本番運用は「地味な作業」の連続ってこと。派手な新機能より、日々の監視と小さな調整の積み重ねが本当に大事なんだ。

  • 接続管理: PgBouncer の transaction モードで接続枯渇を防止。デフォルトの session モードは本番向きじゃない
  • バキューム: 自動バキューム設定は表面的な解決に過ぎない。テーブルごとに細かく調整し、スキーマ設計段階から意識が必要
  • 監視: CloudWatch Enhanced Monitoring で I/O・CPU・メモリの詳細把握。パターン認識が本番安定性を左右する
  • パラメータ: テンプレート値は参考に過ぎず、本番ワークロードで実測値に基づく調整が必須
  • アップグレード: Blue-Green デプロイで検証期間を確保。本番での予期しない挙動を事前に発見できる

2026年時点で、PostgreSQL 本番運用はツール + ナレッジ + 監視の組み合わせなんだ。どれが欠けても本番は不安定になる。深夜 2 時の電話は避けたいならね。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事