PostgreSQL『connection limit exceeded for non-superusers』の原因と対処

  • 作成日 2025.09.24
  • 更新日 2025.10.06
  • その他
PostgreSQL『connection limit exceeded for non-superusers』の原因と対処

新規接続が上限に達し、一般ユーザー(非スーパーユーザー)の接続を受け付けられない状態で発生する。典型メッセージの読み方、発生条件、即効復旧(安全にセッションを開放)、恒久対策(接続数設計・プーリング・設定値の見直し)、クラウド環境での注意点までを体系化した。

エラーメッセージの読み方(代表例)

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

・「max_connections に達したが、残りは superuser(と複製接続)用として予約」
・一般ユーザーでは新規接続できないため、まずは「管理者でログインできる枠」を確保する設計が重要

発生条件の早見表

□ サーバ全体の max_connections に達した
□ superuser_reserved_connections ぶんが確保されておらず、管理者枠が足りない
□ 役割(ROLE)またはデータベースごとの CONNECTION LIMIT を超過
□ 接続プールの誤設定(アプリ側/中間層)がスパイクで接続を張り続ける
□ idle / idle in transaction の放置セッションが堆積
□ バックグラウンドワーカーや autovacuum、複製(WAL sender)もスロットを使用

最短復旧:まず管理者でログインするための枠を確保

-- postgresql.conf(またはパラメータグループ)で予約枠を設定
superuser_reserved_connections = 3   -- 例:管理用に3スロット確保
-- ↑反映には再起動が必要(クラウドは再起動/適用が別途必要)

-- いまログインできるなら、不要接続を終了して枠を空ける
SELECT pid, usename, application_name, state, backend_start
FROM pg_stat_activity
ORDER BY backend_start;

-- 安全に切ってよい候補(例:長時間 idle だが tx でない)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE pid <> pg_backend_pid()
  AND state = 'idle'
  AND backend_type = 'client backend'
  AND usename IN ('appuser');  -- 影響範囲を限定

・「idle in transaction」はトランザクションを開いたまま放置のため、業務影響に注意して個別判断
・autovacuum/複製/バックグラウンドは基本的に切らない

原因特定:接続の内訳を可視化

-- 総数・ユーザー別・アプリ別
SELECT
  count(*) AS total,
  count(*) FILTER (WHERE state = 'active') AS active,
  count(*) FILTER (WHERE state LIKE 'idle%') AS idle
FROM pg_stat_activity;

SELECT usename, count(*)
FROM pg_stat_activity GROUP BY usename ORDER BY 2 DESC;

SELECT application_name, count(*)
FROM pg_stat_activity GROUP BY application_name ORDER BY 2 DESC;

-- 役割/DBごとの接続上限(-1は無制限)
SELECT rolname, rolconnlimit FROM pg_roles ORDER BY rolname;
SELECT datname, datconnlimit FROM pg_database ORDER BY datname;

-- 現在の上限と予約枠
SHOW max_connections;
SHOW superuser_reserved_connections;

ロール/データベース単位の上限に達している場合の対処

-- 役割(ユーザー)単位の上限を見直す
ALTER ROLE appuser CONNECTION LIMIT 50;  -- -1 は無制限

-- データベース単位の上限
ALTER DATABASE mydb CONNECTION LIMIT 200;

-- 一時的に解放する(該当ロール/DBの不要接続を終了)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE usename = 'appuser'  -- または datname='mydb'
  AND pid <> pg_backend_pid();

・上限緩和は“根本”ではない。アプリやプールの同時接続数と整合させることが本筋

アプリ側の基本設計:接続プールを正しく使う

# 例:Java/HikariCP
maximumPoolSize=30
minimumIdle=5
idleTimeout=300000
# → DB側の max_connections から管理/他サービス/バッチ/メンテ枠を差し引き、
#    合算で超えないよう各アプリのプール上限を配分

# 例:Node.js (pg-pool)
new Pool({ max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000 });

# 例:Ruby on Rails
pool: 10  # DB1台に対しアプリプロセス総数×pool <= 設計上限

・プロセス/コンテナのスケールアウト時に合算で上限超過しやすい
・「リクエスト数=接続数」にならないよう、プールで多重化を吸収

中間プール(PgBouncer等)の活用:コネクションを桁違いに削減

# PgBouncer(transaction poolingの例)
[databases]
mydb = host=127.0.0.1 dbname=mydb

[pgbouncer]
pool_mode = transaction     ; tx単位でサーバ接続を使い回す
max_client_conn = 2000      ; クライアント総数
default_pool_size = 50      ; DBへの実接続上限(合算設計の要)
server_idle_timeout = 30s
server_reset_query = DISCARD ALL

・長時間接続しっぱなしのアプリでも、DBへの実接続数を小さく保てる
・セッション変数を多用する場合は session pooling との相性を検討

放置セッション対策:タイムアウトとリーク防止

-- 取引放置の自動切断
SET idle_in_transaction_session_timeout = '5min';   -- postgresql.confでも可

-- クライアント側のソケット生存確認(OS/Driver設定)
-- tcp_keepalives_idle / tcp_keepalives_interval / tcp_keepalives_count を適切化
SHOW tcp_keepalives_idle;

・フレームワーク側でも“接続返却忘れ”を監視(finally/ensureでclose)
・LB/NGWのアイドル切断との相性にも注意

max_connections を上げる前に考えること

# postgresql.conf(一例)
max_connections = 300
superuser_reserved_connections = 3

・単純増加はプロセス/メモリ消費の増大を招く(バックエンド1本=OSプロセス)
・work_mem は「接続×演算」単位で消費しうるため、上げすぎるとスワップ/OS圧迫の原因
・まずはプールで収容し、やむを得ない場合に限り段階的に増やす

複製/バックグラウンドが使うスロットの理解

-- 複製送信枠
SHOW max_wal_senders;

-- バックグラウンドワーカーの存在(参考)
SELECT * FROM pg_stat_activity
WHERE backend_type NOT IN ('client backend');

・WAL sender や logical replication も接続を使う
・メンテ中の一時ジョブやETLが想定以上に接続を開くことがある

クラウド環境(RDS/Aurora/Cloud SQLなど)での要点

-- 設定反映は「パラメータグループ」や「DBフラグ」を変更し、適用/再起動が必要
-- 監視はpg_stat_activityに加え、サービス固有メトリクス(接続数/エラー)を併用
-- メンテナンスウィンドウ中の自動フェイルオーバーで接続嵐が起きないよう、
-- クライアント側の接続リトライ/バックオフを実装

・マネージドでは max_connections の上限がインスタンスタイプ依存
・プーリング(PgBouncer on sidecar/Proxy)を標準構成にするのが無難

“いま切ってよい接続”の安全な選別クエリ集

-- 1) 長時間 idle のクライアント接続候補(30分以上アイドル)
SELECT pid, usename, application_name, client_addr, state, now()-state_change AS idle_for
FROM pg_stat_activity
WHERE state = 'idle' AND now()-state_change > interval '30 minutes'
ORDER BY idle_for DESC;

-- 2) アプリ特定(例:古いリビジョン)だけを終了
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE application_name = 'myapp-old'
  AND state LIKE 'idle%';

-- 3) 自分自身/重要バックグラウンドを除外
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE pid <> pg_backend_pid()
  AND backend_type = 'client backend'
  AND state LIKE 'idle%';

再発防止の設計チェックリスト

□ superuser_reserved_connections > 0 を確保(緊急用ログイン枠)
□ アプリ全体の“合算プール上限”がDB側の設計値を超えない
□ スパイク時のバックオフ/リトライ(指数的バックオフ+Jitter)
□ idle_in_transaction_session_timeout を有効化
□ PgBouncer等の中間プールを導入(transaction pooling を優先検討)
□ 役割/DBの CONNECTION LIMIT を正しく設定し、誤爆を防ぐ
□ 接続監視ダッシュボード(pg_stat_activity 集計)を常設

最小再現→解消の通し例

-- 準備:小さめの上限(テスト用)
ALTER SYSTEM SET max_connections = 20;
ALTER SYSTEM SET superuser_reserved_connections = 2;
SELECT pg_reload_conf();  -- サービスによっては再起動が必要

-- 疑似的に大量接続(別端末/pgbench -C など)を発生させると…
-- 一般ユーザーでの新規接続が失敗:
-- FATAL: connection limit exceeded for non-superusers

-- 対処1:不要な idle を解放
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state='idle' AND backend_type='client backend' AND pid<>pg_backend_pid();

-- 対処2:役割上限の見直し(必要な場合)
ALTER ROLE appuser CONNECTION LIMIT 30;

-- 対処3:PgBouncer(transaction pooling)経由に切替
-- (default_pool_size を 20程度に設定し、DB実接続を抑制)