Webアプリケーションが遅い、同時アクセスが増えるとエラーが出る…こんな悩み、ありませんか?
実は、その原因の多くがデータベース接続の管理方法にあるんです。今回お話しする「コネクションプール」を使えば、この問題がスッキリ解決します!
コネクションプールって聞くと難しそうですが、実は「プール」という名前の通り、とってもシンプルな仕組みなんですよ。
そもそもコネクションプールって何?初心者にも分かる基本の「き」

プールのない世界を想像してみよう
まず、コネクションプールがない場合を考えてみましょう。
レストランに例えると:
- お客さん(アプリケーション)が来るたびに
- 新しいウェイター(データベース接続)を雇って
- 注文が終わったらクビにする
これ、めちゃくちゃ非効率ですよね? 雇うのにも時間がかかるし、コストもかかります。
コネクションプールがある世界
コネクションプールを使うと:
- 最初から数人のウェイター(接続)を用意しておく
- お客さんが来たら、空いているウェイターが対応
- 注文が終わったら、次のお客さんを待つ
これなら効率的!これがコネクションプールの基本的な考え方です。
技術的に言うと…
データベースへの接続(コネクション)を、あらかじめ複数作っておいて、使い回す仕組みのことです。
コネクションプールのメリット:
- 接続・切断の時間を削減(高速化)
- サーバーのリソース消費を抑える(効率化)
- 同時接続数を管理できる(安定化)
なぜコネクションプールが必要なの?数字で見る驚きの効果
接続にかかる時間の真実
PostgreSQLへの新規接続には、実は結構な時間がかかるんです:
1回の接続で起きること:
- TCP/IPコネクションの確立(5〜10ms)
- 認証処理(5〜15ms)
- セッション初期化(5〜10ms)
合計で20〜35msもかかることがあります!
「たった20ミリ秒?」と思うかもしれませんが…
1秒間に100回アクセスがあったら:
- プールなし:2〜3.5秒も接続だけに使う
- プールあり:ほぼ0秒(すでに接続済みだから)
この差、大きいですよね!
メモリ使用量の違い
PostgreSQLは、接続ごとにメモリを消費します:
接続1つあたり:
- 基本メモリ:約1〜5MB
- work_mem設定値:4MB(デフォルト)
100人が同時アクセスした場合:
- プールなし:100接続 × 9MB = 900MB
- プールあり(10接続):10接続 × 9MB = 90MB
10分の1のメモリで済むんです!
コネクションプールの種類と選び方
1. アプリケーションレベルのプール
プログラムの中で管理する方法です。
代表的なライブラリ:
Node.js の場合(node-postgres)
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
database: 'mydb',
user: 'myuser',
password: 'mypass',
max: 20, // 最大接続数
idleTimeoutMillis: 30000, // アイドルタイムアウト
connectionTimeoutMillis: 2000, // 接続タイムアウト
});
// 使い方
async function getUsers() {
const result = await pool.query('SELECT * FROM users');
return result.rows;
}
Python の場合(psycopg2)
from psycopg2 import pool
connection_pool = pool.SimpleConnectionPool(
1, # 最小接続数
20, # 最大接続数
host='localhost',
database='mydb',
user='myuser',
password='mypass'
)
# 接続を取得
conn = connection_pool.getconn()
# 使い終わったら返却
connection_pool.putconn(conn)
Java の場合(HikariCP)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost/mydb");
config.setUsername("myuser");
config.setPassword("mypass");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
HikariDataSource ds = new HikariDataSource(config);
2. 外部プーリングツール
アプリケーションとは別に、専用のプーリングサーバーを使う方法です。
PgBouncer(最も人気!)
特徴:
- 軽量で高速
- 設定がシンプル
- 3つのプールモード
基本的な設定(pgbouncer.ini):
[databases]
mydb = host=localhost port=5432 dbname=mydb
[pgbouncer]
listen_port = 6432 listen_addr = * auth_type = md5 auth_file = /etc/pgbouncer/userlist.txt pool_mode = transaction max_client_conn = 1000 default_pool_size = 25
Pgpool-II(高機能!)
特徴:
- コネクションプール以外も多機能
- レプリケーション対応
- 負荷分散機能あり
でも設定が複雑なので、最初はPgBouncerがおすすめです。
実践!コネクションプールの設定と最適化
最適なプールサイズの決め方
プールサイズは大きければいいわけじゃありません!
計算式:
最適な接続数 = (CPUコア数 × 2) + ディスク数
例:4コアCPU、SSD1台の場合
(4 × 2) + 1 = 9接続
意外と少ないでしょう?でも、これが効率的なんです。
パフォーマンステスト結果
実際のテスト結果を見てみましょう:
テスト環境:
- PostgreSQL 14
- 4コアCPU、8GB RAM
- 同時100ユーザーアクセス
結果:
プールサイズ | 平均レスポンス時間 | スループット |
---|---|---|
なし(都度接続) | 250ms | 400 req/s |
5接続 | 80ms | 1250 req/s |
10接続 | 45ms | 2222 req/s |
20接続 | 48ms | 2083 req/s |
50接続 | 65ms | 1538 req/s |
10接続あたりが最適ですね!
設定の注意点
アンチパターン(やってはいけないこと):
- プールサイズを大きくしすぎる
- DBサーバーのリソースを無駄遣い
- かえってパフォーマンス低下
- タイムアウト設定を忘れる
- 接続が永遠に待ち続ける
- デッドロックの原因に
- エラー処理を怠る
- 接続エラー時の再試行がない
- アプリケーションがクラッシュ
トラブルシューティング:よくある問題と解決法

「too many connections」エラー
原因: PostgreSQLの最大接続数を超えた
確認方法:
-- 現在の接続数を確認
SELECT count(*) FROM pg_stat_activity;
-- 最大接続数を確認
SHOW max_connections;
解決策:
- プールサイズを減らす
- PostgreSQLの
max_connections
を増やす(postgresql.conf) - 不要な接続を切断
接続がタイムアウトする
原因と対策:
// タイムアウト設定を調整
const pool = new Pool({
// ...
connectionTimeoutMillis: 5000, // 5秒に延長
idleTimeoutMillis: 60000, // 1分に延長
statement_timeout: 30000 // クエリタイムアウト
});
プールが枯渇する(接続が足りない)
監視クエリ:
-- アクティブな接続を確認
SELECT
pid,
usename,
application_name,
state,
query_start,
now() - query_start AS duration
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY duration DESC;
長時間実行されているクエリがあれば、最適化が必要です。
PgBouncerの実践的な設定例
インストールと基本設定
Ubuntu/Debianの場合:
sudo apt-get install pgbouncer
設定ファイル(/etc/pgbouncer/pgbouncer.ini):
[databases]
; プール設定
myapp_db = host=127.0.0.1 port=5432 dbname=production
[pgbouncer]
; 基本設定 listen_addr = 127.0.0.1 listen_port = 6432 ; 認証設定 auth_type = md5 auth_file = /etc/pgbouncer/userlist.txt ; プール設定 pool_mode = transaction default_pool_size = 20 min_pool_size = 5 reserve_pool_size = 5 max_client_conn = 1000 max_db_connections = 50 ; タイムアウト設定 server_idle_timeout = 600 server_lifetime = 3600 query_wait_timeout = 120 ; ログ設定 logfile = /var/log/pgbouncer/pgbouncer.log log_connections = 1 log_disconnections = 1
プールモードの選び方
PgBouncerには3つのモードがあります:
1. セッションモード
- 接続を閉じるまでプール
- 一番安全だけど効率は低め
2. トランザクションモード(推奨!)
- トランザクション単位でプール
- バランスが良い
3. ステートメントモード
- SQL文単位でプール
- 最高効率だけど制限あり
ほとんどの場合、トランザクションモードがベストです!
監視とメンテナンス
重要な監視項目
-- PgBouncerの統計情報を確認(PgBouncer接続時)
SHOW STATS;
SHOW POOLS;
SHOW CLIENTS;
SHOW SERVERS;
-- PostgreSQL側の監視
SELECT
application_name,
count(*) as connection_count,
state
FROM pg_stat_activity
GROUP BY application_name, state
ORDER BY connection_count DESC;
アラート設定の目安
監視すべき指標:
- プール使用率が80%を超えたら警告
- 待機クライアント数が10を超えたら警告
- 平均クエリ実行時間が1秒を超えたら確認
ベストプラクティス:プロが教える最適化のコツ
1. 環境ごとの設定を分ける
// 環境変数で切り替え
const poolConfig = {
development: { max: 5 },
production: { max: 20 }
};
const pool = new Pool({
...poolConfig[process.env.NODE_ENV],
// 共通設定
});
2. ヘルスチェックの実装
// 定期的に接続を確認
setInterval(async () => {
try {
await pool.query('SELECT 1');
console.log('Pool is healthy');
} catch (err) {
console.error('Pool health check failed', err);
// アラート送信など
}
}, 30000); // 30秒ごと
3. グレースフルシャットダウン
// アプリ終了時にプールを正しく閉じる
process.on('SIGTERM', async () => {
await pool.end();
process.exit(0);
});
まとめ:コネクションプールでアプリを高速化!
コネクションプール、思ったより簡単でしたよね?
押さえておきたいポイント:
- プールサイズ = (CPUコア数 × 2) + ディスク数
- PgBouncerを使えば簡単に導入できる
- トランザクションモードが多くの場合最適
- 監視を忘れずに設定する
導入効果:
- レスポンス時間が5〜10倍速くなる
- 同時接続数が10倍以上増やせる
- サーバーリソースを効率的に使える
最初は小さく始めて、徐々に最適化していけば大丈夫。 パフォーマンスの改善を実感できるはずです!
次のステップとして、読み書き分離(Read Replica)やコネクションプーリングと組み合わせたキャッシュ戦略についても学んでみてくださいね。
快適なデータベースライフを!
コメント