エラー内容
FATAL: remaining connection slots are reserved for non-replication superuser connections
PostgreSQLがmax_connectionsの上限に達し、通常ユーザーからの新しい接続を受け付けなくなりました。デフォルトで3スロットが常にスーパーユーザーログイン専用として予約されています。これにより、管理者は引き続き接続して問題を修正できます。それ以外のユーザーはこの制限に引っかかります。
根本原因
すべてのPostgreSQLインスタンスには、postgresql.confのmax_connectionsで制御される同時接続数のハードリミットがあります。デフォルトは100です。アプリがそのスロット(スーパーユーザー用に予約された3スロットを除く)を使い切ると、次の接続試行は即座に拒否されます — キューイングも待機もありません。
よくある原因:
- コネクションプーラーなし — アプリの各スレッドまたはプロセスが独自の専用接続を開く
- 接続リーク — 接続を開くが閉じないコード
- トラフィックスパイク — 突然の負荷増加により、接続が再利用される前にプールが枯渇する
- プーリングなしで1つのDBを複数のアプリインスタンスが共有する場合(例:10個のNode.jsポッド × 各10接続 = 即座に100接続)
- 実際のワークロードに対して
max_connectionsの設定値が低すぎる
ステップ1:現在の接続状態を確認する
スーパーユーザー用の予約スロットはまだ使用可能なので、postgresとして接続します:
psql -U postgres -d mydb
接続を消費しているものの内訳を確認します:
SELECT count(*), state, usename, application_name
FROM pg_stat_activity
GROUP BY state, usename, application_name
ORDER BY count DESC;
次に、上限にどれだけ近いか確認します:
SELECT max_conn, used, res_for_super, max_conn - used - res_for_super AS available
FROM
(SELECT count(*) used FROM pg_stat_activity) t1,
(SELECT setting::int res_for_super FROM pg_settings WHERE name = 'superuser_reserved_connections') t2,
(SELECT setting::int max_conn FROM pg_settings WHERE name = 'max_connections') t3;
availableが0または負の場合 — それが問題の原因として確定です。
修正1:アイドル接続を強制終了する(即時対処)
idleやidle in transactionの接続が大量にある場合は、今すぐ終了させます:
-- 10分以上アイドル状態の接続を強制終了
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
AND state_change < NOW() - INTERVAL '10 minutes'
AND usename != 'postgres';
idle in transactionの接続は別途処理します — これらはロックを保持しており、より影響が大きくなります:
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND state_change < NOW() - INTERVAL '5 minutes';
これは一時的な余裕を作るだけです。根本的な原因に対処しなければ、問題は解決しません。
修正2:max_connectionsを増やす
まず設定ファイルの場所を確認します:
SHOW config_file;
次にpostgresql.confを編集します:
# postgresql.conf
max_connections = 200 # 元は100
reserved_connections = 3 # スーパーユーザー用に確保
適用するために再起動します:
sudo systemctl restart postgresql
**500に引き上げる前に:**各PostgreSQL接続はおよそ5〜10 MBのRAMを消費します。200接続では、クエリがデータに触れる前に、接続オーバーヘッドだけで1〜2 GBになります。定常的に100接続に達しているなら、問題はほぼ確実に上限値が低いのではなく、プーリングが不足していることです。
max_connectionsを変更する際は、shared_buffersとwork_memも見直してください — 接続数が増えると、全体的にメモリへの負荷が増大します。
修正3:PgBouncerを導入する(恒久的な修正)
PgBouncerはアプリとPostgreSQLの間のプロキシとして機能します。アプリはデータベースに直接話しかけていると思っていますが、PgBouncerは少数の実際のDB接続を何千ものアプリリクエストに対して再利用しています。
sudo apt install pgbouncer
/etc/pgbouncer/pgbouncer.iniを設定します:
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb
[pgbouncer]
listen_port = 6432
listen_addr = 127.0.0.1
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction ; またはsession
max_client_conn = 1000 ; アプリ側の接続数
default_pool_size = 20 ; 実際のPostgreSQL接続数
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
log_connections = 1
log_disconnections = 1
各ユーザーの認証情報のMD5ハッシュを含む/etc/pgbouncer/userlist.txtを作成します:
"myuser" "md5passwordhash"
ハッシュを生成します:
echo -n "passwordusername" | md5sum
PgBouncerを有効化して起動します:
sudo systemctl enable pgbouncer
sudo systemctl start pgbouncer
アプリケーションの接続先を5432の代わりに6432に変更します。この設定により、PostgreSQLのmax_connectionsは25〜50まで削減でき、PgBouncerはその上で1000以上のアプリ接続を処理します。チームがPgBouncerのプール20接続に対して500の同時アプリ接続を運用するケースはよくあります。
修正4:アプリケーションレベルのコネクションプーリング
PgBouncerに手を伸ばす前に、使用しているフレームワークがすでにプーリングを提供しているかどうか、そして実際に使用しているかどうかを確認してください。
Node.js(pgライブラリ):
const { Pool } = require('pg');
const pool = new Pool({
max: 10, // プール内の最大接続数
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// リクエストごとにclient.connect()ではなくpool.query()を使用する
Python(SQLAlchemy):
from sqlalchemy import create_engine
engine = create_engine(
'postgresql://user:pass@localhost/mydb',
pool_size=10,
max_overflow=20,
pool_timeout=30,
pool_recycle=1800,
)
Django:CONN_MAX_AGEを設定して、リクエストのたびに再接続するのではなく、リクエスト間で接続を再利用します:
DATABASES = {
'default': {
...
'CONN_MAX_AGE': 60, # 秒数;0 = リクエストごとに閉じる
}
}
修正を確認する
変更適用後、接続数をリアルタイムで監視します:
-- 繰り返し実行するか、監視ツールに組み込む
SELECT count(*) AS total_connections,
count(*) FILTER (WHERE state = 'active') AS active,
count(*) FILTER (WHERE state = 'idle') AS idle,
count(*) FILTER (WHERE state = 'idle in transaction') AS idle_in_tx
FROM pg_stat_activity
WHERE datname = 'mydb';
PgBouncerを使用している場合は、プールの統計を直接確認します:
psql -p 6432 -U pgbouncer pgbouncer -c 'SHOW POOLS;'
cl_activeとsv_activeを確認します。cl_activeが200でsv_activeが15であれば、PgBouncerは期待通りに機能しています。
予防策
pg_stat_activityのカウントがmax_connectionsの80%を超えたらアラートを出す — 100%になるまで待たないこと- 最初からコネクションプーリングを追加する。本番稼働中のアプリに後から導入するのは本当に苦労します
postgresql.confにidle_in_transaction_session_timeout = '5min'を設定して、スタックしたトランザクションを自動的に終了させるstatement_timeoutを設定して、暴走クエリが無期限に接続を保持する前に切断する- Kubernetesでは:各ポッドが独自のプールを持つため、総接続数 = ポッド数 × pool_sizeになります。20ポッドで各10接続だと、すでに200スロットを消費します。この構成ではPgBouncerは必須です

