- データベース
PostgreSQL too many clientsの直し方|接続数と接続プール
FATAL: sorry, too many clients already は、PostgreSQL の接続数が max_connections の上限に達したときのエラーです。 多くの場合、原因は「上限が小さい」ことではなく、使っていない接続が残っている(接続リーク・idle in transaction)か、アプリの台数 × プールの大きさが上限を超えていることです。まず誰が接続を使っているかを調べ、そのあとで設定を変えます。
| 手順 | すること | 使うもの |
|---|---|---|
| 1. 今すぐ空ける | 長く止まっている接続を切る | pg_terminate_backend(pid) |
| 2. 原因を探す | ユーザー・アプリ・状態ごとに数える | pg_stat_activity |
| 3. 止まった取引を防ぐ | 一定時間でセッションを切る | idle_in_transaction_session_timeout |
| 4. 数を合わせる | 台数 × プール ≦ 上限 − 予約 | HikariCP の maximumPoolSize |
| 5. 接続を束ねる | 多数のクライアントを少数の接続に | PgBouncer |
| 6. 上限を上げる | メモリを見積もってから | max_connections(再起動が要る) |
確認環境:PostgreSQL 16.15(Ubuntu 24.04、apt)で max_connections を 5・20・100 にして接続を張り、エラーを再現しました。クライアントは psql、Python の psycopg 3.3.6・psycopg_pool 3.3.3、Java 21 + PostgreSQL JDBC ドライバー 42.7.8 です。PgBouncer 1.22.0(apt)を実際に立てて、100 クライアントを5本の接続で処理するところまで試しています。設定項目と既定値は PostgreSQL 16・PgBouncer・HikariCP の公式ドキュメントで、HikariCP のログの文面は HikariCP のソース(7.1.1-SNAPSHOT)で確かめました(確認日 2026年10月13日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「PostgreSQL too many clientsの直し方|接続数と接続プール」
エラー文は3種類ある
PostgreSQL 16 では、接続の上限に関わるエラーが3種類あります。max_connections=5・superuser_reserved_connections=1・reserved_connections=1 にして、一般ユーザー app で接続を張っていくと、次の順に出ました。
psql: error: connection to server at "127.0.0.1", port 5481 failed: FATAL: remaining connection slots are reserved for roles with privileges of the "pg_use_reserved_connections" role
psql: error: connection to server at "127.0.0.1", port 5481 failed: FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5481" failed: FATAL: sorry, too many clients already
| エラー文 | いつ出るか | 意味 |
|---|---|---|
sorry, too many clients already |
接続が max_connections に達した |
スーパーユーザーも含め、もう誰も入れない |
remaining connection slots are reserved for roles with the SUPERUSER attribute |
空きが superuser_reserved_connections 以下 |
残りはスーパーユーザー用 |
remaining connection slots are reserved for roles with privileges of the "pg_use_reserved_connections" role |
空きが「2つの予約の合計」以下(16から) | 残りは pg_use_reserved_connections を持つロール用 |
too many connections for role "app" |
ロールの CONNECTION LIMIT に達した |
そのロールだけの上限 |
too many connections for database "shop" |
データベースの CONNECTION LIMIT に達した |
そのデータベースだけの上限 |
一般ユーザーのアプリに出るのは、ほとんどが2番目か3番目です。 「remaining connection slots are reserved」は、上限そのものではなく「予約分を除いた枠」を使い切った合図です。どれも SQLSTATE は 53300(too_many_connections)です。Java(JDBC)では次のように受け取りました。
org.postgresql.util.PSQLException
SQLState=53300
message=FATAL: remaining connection slots are reserved for roles with privileges of the "pg_use_reserved_connections" role
ロール・データベースの上限は、次の SQL で設定し、確かめられます。
ALTER ROLE app CONNECTION LIMIT 50; -- -1 で上限なし(既定)
ALTER DATABASE shop CONNECTION LIMIT 80;
SELECT rolname, rolconnlimit FROM pg_roles WHERE rolconnlimit <> -1;
SELECT datname, datconnlimit FROM pg_database;
一般ユーザーが使える数
ドキュメントの既定値は max_connections が通常 100、superuser_reserved_connections が 3、reserved_connections(16で追加)が 0 です。
一般ユーザーが使える接続数 = max_connections − superuser_reserved_connections − reserved_connections
既定値なら = 100 − 3 − 0 = 97
max_connections=100・予約の合計2 の設定で、psycopg から接続を張り続けると、98本目までつながり、99本目で「reserved for roles with privileges of "pg_use_reserved_connections"」になりました。
運用担当のロールに pg_use_reserved_connections を付けておくと、アプリが枠を使い切っても、スーパーユーザーを使わずに調査用の接続ができます。
-- postgresql.conf か ALTER SYSTEM で(どちらも再起動で反映)
ALTER SYSTEM SET reserved_connections = 3;
GRANT pg_use_reserved_connections TO ops;
まず空きを作る(応急処置)
接続できる管理用のセッション(スーパーユーザー、または予約枠を持つロール)から、止まっている接続を切ります。
-- 5分以上 idle in transaction のまま止まっている接続を切る
SELECT pid, usename, application_name, pg_terminate_backend(pid) AS killed
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'idle in transaction (aborted)')
AND state_change < now() - interval '5 minutes';
-- 30分以上 何もしていない(idle)接続を切る(特定アプリだけに絞る)
SELECT pid, usename, application_name, pg_terminate_backend(pid) AS killed
FROM pg_stat_activity
WHERE state = 'idle'
AND application_name = 'batch'
AND state_change < now() - interval '30 minutes';
| 関数 | すること | 影響 |
|---|---|---|
pg_cancel_backend(pid) |
実行中の SQL だけを止める | 接続は残る(枠は空かない) |
pg_terminate_backend(pid) |
接続そのものを切る | 途中のトランザクションはロールバック |
接続を切られたアプリは、次の SQL でエラーになります。 接続プールを使っていれば、多くの場合プールが新しい接続に張り直します。条件を付けずに全部を切ると、処理中の更新もロールバックされるので、usename・application_name・経過時間で絞ります。
pg_stat_activity で誰が使っているかを調べる
pg_stat_activity は、今ある接続(とバックグラウンドの処理)を1行ずつ表すビューです。 調べるときは、backend_type = 'client backend' でクライアントの接続だけに絞ります。
使用数と残り
SELECT count(*) AS used,
current_setting('max_connections')::int AS max_conn,
current_setting('superuser_reserved_connections')::int
+ current_setting('reserved_connections')::int AS reserved,
current_setting('max_connections')::int
- current_setting('superuser_reserved_connections')::int
- current_setting('reserved_connections')::int
- count(*) AS free_for_normal
FROM pg_stat_activity
WHERE backend_type = 'client backend';
used | max_conn | reserved | free_for_normal
------+----------+----------+-----------------
11 | 100 | 2 | 87
free_for_normal が一般ユーザーの残りです。監視するなら、この値が一定数を下回ったら通知する形にします。
ユーザー・アプリ・状態ごとに数える
SELECT usename, application_name, client_addr, state, count(*)
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY 1, 2, 3, 4
ORDER BY count(*) DESC;
アイドルの接続5本・idle in transaction 3本・実行中2本を作って実行した結果です。
usename | application_name | client_addr | state | count
----------+------------------+-------------+---------------------+-------
app | batch | 127.0.0.1 | idle | 5
app | web | 127.0.0.1 | idle in transaction | 3
app | report | 127.0.0.1 | active | 2
postgres | psql | | active | 1
application_name が空だと、どのアプリの接続か分かりません。 接続文字列で名前を付けておくと、この集計がそのまま原因の特定に使えます。
| ドライバー・ツール | 名前の付け方 |
|---|---|
| JDBC(pgjdbc) | jdbc:postgresql://db:5432/shop?ApplicationName=order-api |
| libpq(psql・psycopg など) | postgresql://app@db/shop?application_name=batch |
| 環境変数 | PGAPPNAME=batch |
state の意味
| state | 意味 | 多いときに疑うこと |
|---|---|---|
active |
SQL を実行中 | 遅い SQL・ロック待ち(wait_event を見る) |
idle |
次の SQL を待っている | プールの持ちすぎ、閉じ忘れ |
idle in transaction |
BEGIN 後、COMMIT していない | トランザクションの閉じ忘れ・処理の途中で外部 API 待ち |
idle in transaction (aborted) |
エラー後に ROLLBACK していない | 例外処理でロールバックしていない |
idle が多いのは、プールが接続を持っているだけのこともあります。問題になりやすいのは idle in transaction です。
idle in transaction を見つけて防ぐ
SELECT pid, usename, application_name,
now() - xact_start AS xact_age,
now() - state_change AS idle_for,
left(query, 60) AS last_query
FROM pg_stat_activity
WHERE state LIKE 'idle in transaction%'
ORDER BY xact_start;
pid | usename | application_name | xact_age | idle_for | last_query
------+---------+------------------+-----------------+-----------------+---------------------------------------------
3758 | app | web | 00:00:03.69623 | 00:00:03.694939 | update orders set status='paid' where id=$1
3759 | app | web | 00:00:03.686028 | 00:00:03.684994 | update orders set status='paid' where id=$1
query 列は「最後に実行した SQL」です。 idle in transaction の行では、この SQL のあとに COMMIT されていないことを示しています。アプリのどこでトランザクションを始めたかを探す手がかりになります。
idle in transaction は、接続の枠を使うだけでなく、更新した行のロックを持ち続け、VACUUM が不要な行を消すのも止めます。ロックで待たされている側の調べ方は関連記事「PostgreSQLロック確認SQL|ブロッキング特定から解除までコピペ集」、VACUUM への影響は関連記事「PostgreSQLのVACUUMとautovacuum|監視SQLと設定値の目安」で扱っています。
idle_in_transaction_session_timeout
一定時間 idle in transaction のままのセッションを、サーバーが切る設定です。 既定は 0(無効)、単位を付けなければミリ秒です。
-- 全体(再読み込みで反映。再起動は要らない)
ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';
SELECT pg_reload_conf();
-- アプリのロールだけ
ALTER ROLE app SET idle_in_transaction_session_timeout = '60s';
2秒に設定して、UPDATE のあと3秒待ってから次の SQL を送ると、こうなりました。
psycopg.errors.IdleInTransactionSessionTimeout | 25P03 | terminating connection due to idle-in-transaction timeout
サーバーのログにも FATAL: terminating connection due to idle-in-transaction timeout が出ます。このログが出ていたら、アプリにトランザクションを閉じ忘れている箇所があります。
| 設定 | 切る対象 | 既定 | 注意(ドキュメントより) |
|---|---|---|---|
idle_in_transaction_session_timeout |
トランザクション中で止まった接続 | 0(無効) | ロックを長く持たせないために使える |
idle_session_timeout(14から) |
トランザクション外で止まった接続 | 0(無効) | 接続プール経由の接続には慎重に。対話用のユーザーだけに付ける例 |
statement_timeout |
実行が長すぎる SQL | 0(無効) | postgresql.conf で全体に付けるのは推奨されない |
idle_session_timeout を2秒にしたときのエラーは psycopg.errors.IdleSessionTimeout | 57P05 | terminating connection due to idle-session timeout でした。HikariCP などのプールは、アイドルの接続を持っておくのが仕事なので、プールのロールに idle_session_timeout を付けると、プールの接続が切られてエラーの元になります。 付けるなら、psql などで人が使うロールに限ります。statement_timeout については関連記事「statement timeoutの直し方|PostgreSQL」で扱っています。
max_connections を上げる前に考えること
max_connections は再起動しないと変わりません。上げる前に、次の3つを確かめます。
1. 本当に足りないのか
pg_stat_activity で idle が大半なら、足りないのは上限ではなく、接続を持ちすぎているプールの設定です。上限を上げても、プールの台数が増えればまた同じエラーになります。
2. 共有メモリが増える
max_connections だけを変えて、起動時に確保する共有メモリ(shared_memory_size、15から見られる)を比べました(shared_buffers は 128MB のまま)。
| max_connections | shared_memory_size |
|---|---|
| 100 | 143MB |
| 500 | 163MB |
| 1000 | 184MB |
| 5000 | 378MB |
SHOW shared_memory_size;
3. 接続ごとのメモリと work_mem
PostgreSQL は1接続ごとに1つのプロセス(バックエンド)を作ります。 何もしていない接続のプロセスは、確認環境(PostgreSQL 16.15)で Rss 16MB 前後、そのうち共有分を除いた Pss が約 2.4MB でした。問題は SQL を実行したときで、並べ替えやハッシュの1つの処理ごとに work_mem まで(ハッシュは hash_mem_multiplier 倍まで)メモリを使えます。
300万行を並べ替える SQL を1接続で実行し、プロセスの専用メモリ(Private_Dirty)の最大値を測りました。
| work_mem | 実行中の最大 | 実行後 |
|---|---|---|
| 4MB(既定) | 33MB | 24MB |
| 256MB | 415MB | 34MB |
同時に動く接続 × 1つの SQL の中の並べ替え・ハッシュの数 × work_mem が重なると、メモリが足りなくなります。上限を上げるときは、同時に active になる数(pg_stat_activity の state = 'active' の最大値)と work_mem を合わせて見積もります。
目安のおおまかな式
shared_buffers + (同時に active な接続数 × work_mem × 1 SQL あたりの並べ替え・ハッシュの数) + OS のキャッシュ用 < 物理メモリ
変え方
ALTER SYSTEM SET max_connections = 200; -- postgresql.auto.conf に書かれる
-- 再起動(例:sudo systemctl restart postgresql)
SHOW max_connections;
ALTER SYSTEM RESET max_connections; -- 元に戻す(これも再起動で反映)
ドキュメントでは、レプリカ(スタンバイ)の max_connections はプライマリ以上にする必要があるとされています。プライマリだけ上げると、スタンバイで問い合わせができなくなります。AWS RDS などのマネージドサービスでは、パラメータグループで設定します。
接続プールで数を合わせる
接続の数は「アプリの台数 × 1台のプールの最大数」で決まります。 台数を増やしたとき、デプロイで新旧のアプリが同時に動いたときに、上限を超えやすくなります。
(アプリの台数 × maximumPoolSize) + バッチ・管理ツールの接続 ≦ max_connections − 予約の合計
例:max_connections=100(予約3)なら、97 を アプリ8台 × 10 = 80 と バッチ・運用 17 で分ける
ローリングデプロイで新旧が重なる時間は、台数が一時的に増えます。その分の余裕も残しておきます。
HikariCP(Spring Boot)の設定項目
| 項目 | 既定値 | 意味 |
|---|---|---|
maximumPoolSize |
10 | プールが持つ接続の最大数(使用中 + 空き) |
minimumIdle |
maximumPoolSize と同じ |
空きとして持っておく最小数 |
connectionTimeout |
30000(30秒) | プールから接続を借りるまで待つ最大時間 |
idleTimeout |
600000(10分) | 空きの接続を閉じるまでの時間(minimumIdle < 最大のときだけ) |
maxLifetime |
1800000(30分) | 1本の接続を使う最大の時間 |
keepaliveTime |
120000(2分) | 空きの接続に生存確認を送る間隔 |
leakDetectionThreshold |
0(無効) | 借りたまま返さない接続をログに出すまでの時間(2000以上) |
# application.yml(Spring Boot)
spring:
datasource:
url: jdbc:postgresql://db:5432/shop?ApplicationName=order-api
username: app
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 10
minimum-idle: 10
connection-timeout: 5000 # 30秒待たせずに早めにエラーにする
max-lifetime: 1800000
leak-detection-threshold: 60000 # 60秒返さない接続をログに出す
プールは大きいほど速いわけではありません。 HikariCP の wiki「About Pool Sizing」では、PostgreSQL プロジェクトの目安として connections = ((core_count * 2) + effective_spindle_count) が紹介され、4コア・ディスク1台なら 9(切り上げて 10)という例が載っています。1つのスレッドが同時に複数の接続を借りる作りでは、デッドロックを避ける最小の大きさとして pool size = Tn x (Cm - 1) + 1(Tn:スレッドの最大数、Cm:1スレッドが同時に持つ接続の最大数)も示されています。
DataSource の設定で起動に失敗するときは、関連記事「Failed to configure a DataSourceの直し方|原因表」で扱っています。
PgBouncer で接続を束ねる
PgBouncer は、アプリと PostgreSQL の間に入る軽い接続プーラーです。 アプリからの多数の接続を受け、PostgreSQL へは少数の接続だけを張って使い回します。アプリが多数の台数・プロセスで動くとき(PHP・Python のワーカー、サーバーレスなど)に効きます。
試した設定(全文)
; pgbouncer.ini
[databases]
shop = host=127.0.0.1 port=5481 dbname=shop
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
admin_users = app
pool_mode = transaction
max_client_conn = 500
default_pool_size = 5
reserve_pool_size = 2
reserve_pool_timeout = 3
max_db_connections = 10
server_idle_timeout = 600
query_wait_timeout = 120
max_prepared_statements = 100
logfile = /var/log/postgresql/pgbouncer.log
pidfile = /var/run/postgresql/pgbouncer.pid
userlist.txt には、PostgreSQL の SCRAM のハッシュをそのまま書けます。
sudo apt install pgbouncer
sudo -u postgres psql -At -c "SELECT format('\"%s\" \"%s\"', rolname, rolpassword) FROM pg_authid WHERE rolname = 'app'" \
| sudo tee /etc/pgbouncer/userlist.txt
sudo -u postgres pgbouncer -d /etc/pgbouncer/pgbouncer.ini # -d はデーモンとして起動
psql "host=127.0.0.1 port=6432 dbname=shop user=app" # アプリの接続先をポート 6432 にする
直接つなぐ場合との比較
max_connections=20(予約2)の PostgreSQL に、100 のクライアントが同時に接続し、それぞれ短い SQL(pg_sleep(0.05) を含む)を20回実行しました。
| 接続先 | 成功した SQL | エラー | PostgreSQL 側の接続 |
|---|---|---|---|
| PostgreSQL に直接 | 360 / 2000 | 82 クライアントが接続できず | 18 本(上限 − 予約) |
| PgBouncer(transaction) | 2000 / 2000 | 0 | 最大 5 本 |
直接つないだときの82件の内訳は、sorry, too many clients already 55件、reserved for roles with the SUPERUSER attribute 21件、reserved for roles with privileges of the "pg_use_reserved_connections" role 6件でした。PgBouncer 経由では、実行中の様子を管理コンソールで見られます。
psql "host=127.0.0.1 port=6432 dbname=pgbouncer user=app" -c "SHOW POOLS"
database | user | cl_active | cl_waiting | ... | sv_active | sv_idle | ... | maxwait_us | pool_mode
-----------+-----------+-----------+------------+-----+-----------+---------+-----+------------+-------------
shop | app | 5 | 95 | ... | 5 | 0 | ... | 960487 | transaction
cl_waiting(待っているクライアント)が 95、sv_active(使っているサーバー接続)が 5 です。クライアントは並んで順番を待ち、エラーにはなりません。その代わり、全体の時間は待ちの分だけ延びます(この例で約20秒)。待ちが query_wait_timeout(既定 120秒)を超えると、クライアントにエラーが返ります。
設定項目
| 項目 | 既定値 | 意味 |
|---|---|---|
pool_mode |
session |
サーバー接続を返すタイミング(session・transaction・statement) |
max_client_conn |
100 | 受け付けるクライアント接続の最大数 |
default_pool_size |
20 | ユーザー × データベースの組ごとのサーバー接続の最大数 |
min_pool_size |
0 | 少なくとも持っておくサーバー接続の数 |
reserve_pool_size |
0 | 待ちが長いときに追加で使える接続の数 |
reserve_pool_timeout |
5 | 何秒待たされたら予備の接続を使うか |
max_db_connections |
0(無制限) | データベースごとのサーバー接続の上限(ユーザーによらず) |
server_idle_timeout |
600 | 使われないサーバー接続を閉じるまでの秒数 |
query_wait_timeout |
120 | クライアントが接続の空きを待つ最大の秒数 |
max_prepared_statements |
200(1.24.0 から。1.22 の設定例は 0) | transaction モードで扱うプリペアドステートメントの数 |
PostgreSQL 側の接続数の上限は、default_pool_size(または max_db_connections)× ユーザー・データベースの組の数 + reserve_pool_size で決まります。 これが max_connections から予約を引いた数に収まるようにします。
transaction モードの注意:prepared statement already exists
最初、max_prepared_statements を書かずに(PgBouncer 1.22 では 0)、psycopg 3 から PgBouncer に同じ負荷をかけたところ、100 クライアント中 95 がこのエラーになりました。
prepared statement "_pg3_0" already exists
psycopg 3 は、同じ SQL を5回実行すると、サーバー側のプリペアドステートメントに切り替えます。 transaction モードでは、トランザクションごとに別のサーバー接続が割り当てられるので、名前がぶつかります。次のどちらかで直りました。
| 直し方 | 設定 | 結果 |
|---|---|---|
| PgBouncer 側(1.21 から) | max_prepared_statements = 100 |
2000 / 2000 成功 |
| クライアント側 | psycopg:prepare_threshold=None |
2000 / 2000 成功 |
JDBC(pgjdbc)も、同じ SQL を prepareThreshold(既定 5)回実行するとサーバー側の準備に切り替えます。PgBouncer の機能表では、transaction モードで使えないものとして次が挙げられています。
| 機能 | session | transaction |
|---|---|---|
SET / RESET |
使える | 使えない |
LISTEN |
使える | 使えない |
PREPARE / DEALLOCATE(SQL 文) |
使える | 使えない |
| プロトコルのプリペアドステートメント | 使える | max_prepared_statements > 0 のとき使える |
WITH HOLD カーソル |
使える | 使えない |
| セッション単位のアドバイザリロック | 使える | 使えない |
| ON COMMIT DROP 以外の一時テーブル | 使える | 使えない |
アプリがこれらを使っているときは、pool_mode = session にするか、使い方を変えます。
アプリ側の接続リーク
接続リークは、借りた接続を閉じない(プールに返さない)バグです。 呼ばれるたびに接続が1本ずつ残り、いつか上限に達します。
プールなしの場合:too many clients になる
// 悪い例:close しないので、呼ぶたびに接続が残る
static int countOrdersLeaky() throws SQLException {
Connection con = DriverManager.getConnection(URL, "app", "apppass");
Statement st = con.createStatement();
ResultSet rs = st.executeQuery("select 1");
rs.next();
return rs.getInt(1);
}
max_connections=5(予約2)でこのメソッドを繰り返し呼ぶと、3回成功して4回目で PSQLException(SQLState 53300)になりました。直し方は try-with-resources で必ず閉じることです。
static int countOrders() throws SQLException {
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement("select count(*) from orders");
ResultSet rs = ps.executeQuery()) {
rs.next();
return rs.getInt(1);
} // ここで rs・ps・con の順に閉じられ、接続はプールに返る
}
プールありの場合:エラーの形が変わる
プールを使っていると、リークしても PostgreSQL の上限には届かず、「プールから借りられない」タイムアウトになります。 psycopg_pool(max_size=3・timeout=2)で、借りて返さない関数を呼ぶと、4回目でこうなりました。
4 PoolTimeout | couldn't get a connection after 2.00 sec
HikariCP では、同じ状況で次の例外になります(HikariCP のソースより)。active が total と同じで waiting が増えていれば、借りたまま返されていない接続があります。
java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms (total=10, active=10, idle=0, waiting=3)
leakDetectionThreshold を設定すると、閾値を超えて返されない接続について、借りた場所のスタックトレースつきで次のログが出ます。
WARN Connection leak detection triggered for {接続名} on thread {スレッド名}, stack trace follows
INFO Previously reported leaked connection {接続名} on thread {スレッド名} was returned to the pool (unleaked)
2行目は、遅れて返された場合のログです。2行目が出るなら、リークではなく「長く借りている処理」です。 時間のかかる処理(外部 API の呼び出しなど)をトランザクションの中で行っていないかを見直します。Spring の @Transactional のメソッドの中で外部 API を呼ぶと、その間ずっと接続を借りたままになり、DB 側では idle in transaction に見えます。トランザクションの範囲については関連記事「Spring @Transactionalが効かない原因8選|ロールバックの直し方」で扱っています。
監視と予防のチェックリスト
| 見るもの | 方法 |
|---|---|
| 一般ユーザーの残り | 上の「使用数と残り」の SQL の free_for_normal |
| 長い idle in transaction | state LIKE 'idle in transaction%' AND state_change < now() - interval '1 minute' の件数 |
| 接続の作成・切断の多さ | log_connections = on・log_disconnections = on(ログが増えるので期間を決めて) |
| プールの待ち | HikariCP のメトリクス(hikaricp.connections.pending など)、PgBouncer の SHOW POOLS の cl_waiting |
| 台数の変化 | オートスケールの最大台数 × maximumPoolSize が上限に収まるか |
接続数の確認の SQL をさらに詳しく扱った関連記事「PostgreSQL接続数の確認|pg_stat_activityの見方と対処」もあります。MySQL で同じ種類のエラーが出たときは、関連記事「MySQL Too many connectionsの原因と直し方」で扱っています。
よくある質問
Q. sorry, too many clients already が出て、スーパーユーザーでもつながりません。
A. superuser_reserved_connections の枠まで使い切った状態です。アプリを止める、アプリのプールを小さくして再起動する、などで接続を減らします。それも難しい場合は、OS から該当するバックエンドのプロセスに kill(SIGTERM)を送る方法もありますが、kill -9 はサーバー全体の再起動(クラッシュリカバリー)を招くので使いません。普段から reserved_connections と pg_use_reserved_connections を設定しておくと、この状態になりにくくなります。
Q. max_connections を変えたのに、SHOW max_connections の値が変わりません。
A. 再起動が必要な設定です。SELECT name, setting, pending_restart FROM pg_settings WHERE name = 'max_connections'; で pending_restart が t なら、再起動待ちです。
Q. idle の接続がたくさんあるのは問題ですか。
A. プールが持っている接続なら、それ自体は設計どおりです。PostgreSQL のドキュメントでも、トランザクションの外で止まっているセッションは、サーバーに大きな負担をかけないとされています。問題は、その数が上限を圧迫している場合で、プールの minimumIdle・maximumPoolSize を見直します。
Q. 接続プールは HikariCP と PgBouncer のどちらを使えばよいですか。 A. 役割が違います。HikariCP は1つのアプリの中のプール、PgBouncer は複数のアプリ・プロセスの接続をまとめるプールで、両方を組み合わせることもあります。Java のアプリが数台なら HikariCP の大きさを合わせる方法、プロセスの数が多い・台数が大きく変わる構成では PgBouncer が向いています。
まとめ
- エラーは3種類。一般ユーザーに出やすいのは 「remaining connection slots are reserved」 と 「sorry, too many clients already」。SQLSTATE はどれも
53300 - 一般ユーザーが使える数は
max_connections − superuser_reserved_connections − reserved_connections - まず
pg_stat_activityで ユーザー・application_name・state ごとに数える。接続文字列で名前を付けておく - idle in transaction は
idle_in_transaction_session_timeoutで切れる。ログに出たら閉じ忘れの合図 max_connectionsは 再起動が必要。接続ごとのメモリは小さくても、同時実行 × work_mem でメモリが増える- アプリの台数 × プールの最大数 を上限に収める。PgBouncer の transaction モードでは
max_prepared_statementsを設定する - リークは try-with-resources で閉じる。HikariCP の
leakDetectionThresholdで借りた場所が分かる
参考・出典
確認日はいずれも 2026年10月13日です。
- PostgreSQL 16 Documentation「20.3. Connections and Authentication」:https://www.postgresql.org/docs/16/runtime-config-connection.html
- PostgreSQL 16 Documentation「20.11. Client Connection Defaults」:https://www.postgresql.org/docs/16/runtime-config-client.html
- PostgreSQL 16 Documentation「28.2. The Cumulative Statistics System(pg_stat_activity)」:https://www.postgresql.org/docs/16/monitoring-stats.html
- PostgreSQL 16 Documentation「Appendix A. PostgreSQL Error Codes」:https://www.postgresql.org/docs/16/errcodes-appendix.html
- PgBouncer「Configuration」:https://www.pgbouncer.org/config.html
- PgBouncer「Features」:https://www.pgbouncer.org/features.html
- PgBouncer「Changelog」:https://www.pgbouncer.org/changelog.html
- HikariCP(README の設定項目):https://github.com/brettwooldridge/HikariCP
- HikariCP Wiki「About Pool Sizing」:https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「PostgreSQL too many clientsの直し方|接続数と接続プール」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。