PostgreSQL FATAL: remaining connection slots are reserved for non-replication superuser connections の修正方法

intermediate🐘 PostgreSQL2026-03-21| PostgreSQL 12〜16、Linux/Ubuntu/Debian、永続的なDB接続を使用するあらゆるアプリケーション

Error Message

FATAL: remaining connection slots are reserved for non-replication superuser connections
#postgresql#max-connections#connection-pool#pg_hba#performance

エラー内容

FATAL: remaining connection slots are reserved for non-replication superuser connections

PostgreSQLがmax_connectionsの上限に達し、通常ユーザーからの新しい接続を受け付けなくなりました。デフォルトで3スロットが常にスーパーユーザーログイン専用として予約されています。これにより、管理者は引き続き接続して問題を修正できます。それ以外のユーザーはこの制限に引っかかります。

根本原因

すべてのPostgreSQLインスタンスには、postgresql.confmax_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:アイドル接続を強制終了する(即時対処)

idleidle 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_bufferswork_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_activesv_activeを確認します。cl_activeが200でsv_activeが15であれば、PgBouncerは期待通りに機能しています。

予防策

  • pg_stat_activityのカウントがmax_connectionsの80%を超えたらアラートを出す — 100%になるまで待たないこと
  • 最初からコネクションプーリングを追加する。本番稼働中のアプリに後から導入するのは本当に苦労します
  • postgresql.confidle_in_transaction_session_timeout = '5min'を設定して、スタックしたトランザクションを自動的に終了させる
  • statement_timeoutを設定して、暴走クエリが無期限に接続を保持する前に切断する
  • Kubernetesでは:各ポッドが独自のプールを持つため、総接続数 = ポッド数 × pool_sizeになります。20ポッドで各10接続だと、すでに200スロットを消費します。この構成ではPgBouncerは必須です

Related Error Notes