はじめに
Connection Poolについてまとめました。
背景
IndexやN+1問題があり対処してきましたが、Connection Poolはサーバー側だけの問題ではなくなります。
業務では見たこともありますが、あまり触れない分野なのでまとめました。
Connection Poolとは、すぐにDBに接続できるように準備された枠組みです。
例でいうと、タクシー乗り場に似ています。タクシーがある限りすぐに乗車できますが、ない場合は待つ必要があります。
Connection Pool
Supabaseをみると、Database SettingsでConnection poolingがあります。
Connection pool sizeが15と書いています。
図にすると、下のようになります。
┌───────────────┐
│ Next.js │
│ pg.Client │
└───────┬───────┘
│
│ Client connections
│ 最大 200
▼
┌─────────────────────────┐
│ Supabase Transaction │
│ Pooler │
│ │
│ Client 1 ─┐ │
│ Client 2 ─┤ │
│ ... │ │
│ Client 200┘ │
│ │
│ Connection Pool │
│ 最大 15 │
└───────────┬─────────────┘
│
│ DB connections
│ 最大 15
▼
┌─────────────────────────┐
│ PostgreSQL │
└─────────────────────────┘
大切なこととして、クライアントの接続数≠DBの接続数があります。
DB connections分接続し、さらに接続を増やしてみた
最大15本の接続までしかできないですが、その状態で16本目を実行しました。
15本使うように接続し、10秒間待機させる間に別の1本を流すことを検証してみました。
コード
import { supabase } from "../utils/supabase";
import pg from "pg";
export async function testConnectionPool16() {
const { Client } = pg;
function createClient() {
return new Client({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
ssl: {
rejectUnauthorized: false,
},
});
}
// ① 15本の接続を作る
async function main() {
console.log("=== Connection Pool枯渇テスト開始 ===");
// ① 15本のClientを作成
const clients = Array.from(
{ length: 15 },
() => createClient()
);
const connectedClients: InstanceType<typeof Client>[] = [];
// ② 15本すべてPoolerへ接続
for (let i = 0; i < clients.length; i++) {
try {
await clients[i].connect();
connectedClients.push(clients[i]);
console.log(
`Connection ${i + 1}: 接続成功`
);
} catch (error) {
console.error(
`Connection ${i + 1}: 接続失敗`,
error
);
break;
}
}
console.log(
`接続成功数: ${connectedClients.length}`
);
// ③ 15本すべてでトランザクションを開始
// その中で10秒間DB接続を使用する
const sleepPromises = connectedClients.map(
async (client, index) => {
try {
console.log(
`Connection ${index + 1}: BEGIN`
);
await client.query("BEGIN");
console.log(
`Connection ${index + 1}: pg_sleep開始`
);
await client.query(
"SELECT pg_sleep(10)"
);
console.log(
`Connection ${index + 1}: pg_sleep終了`
);
await client.query("COMMIT");
console.log(
`Connection ${index + 1}: COMMIT`
);
} catch (error) {
console.error(
`Connection ${index + 1}: DB処理失敗`,
error
);
}
}
);
// ④ 15本がDB接続を使用中になったあと、
// 16本目を開始
await new Promise((resolve) =>
setTimeout(resolve, 1000)
);
console.log(
"--------------------------------"
);
console.log(
"16本目のDB処理を開始します"
);
const client16 = createClient();
// ⑤ 16本目のPooler接続
const startConnect16 = performance.now();
try {
await client16.connect();
const endConnect16 = performance.now();
console.log(
"16本目: Pooler接続成功"
);
console.log(
`16本目: connect時間 ${(endConnect16 - startConnect16).toFixed(2)} ms`
);
// ⑥ 16本目でトランザクション開始
console.log(
"16本目: BEGIN開始"
);
const startBegin16 = performance.now();
await client16.query("BEGIN");
const endBegin16 = performance.now();
console.log(
`16本目: BEGIN時間 ${(endBegin16 - startBegin16).toFixed(2)} ms`
);
// ⑦ 16本目のSELECT
console.log(
"16本目: SELECT開始"
);
const startQuery16 = performance.now();
await client16.query("SELECT 1");
const endQuery16 = performance.now();
console.log(
`16本目: SELECT時間 ${(endQuery16 - startQuery16).toFixed(2)} ms`
);
await client16.query("COMMIT");
console.log(
"16本目: COMMIT"
);
} catch (error) {
console.error(
"16本目: エラー",
error
);
} finally {
await client16.end();
console.log(
"16本目: 接続終了"
);
}
// ⑧ 15本の処理終了を待つ
await Promise.all(sleepPromises);
console.log(
"15本のDB処理がすべて終了しました"
);
// ⑨ 15本を切断
await Promise.all(
connectedClients.map(
(client) => client.end()
)
);
console.log(
"15本の接続をすべて切断しました"
);
console.log(
"=== Connection Pool枯渇テスト終了 ==="
);
}
main().catch((error) => {
console.error("エラー:", error);
});
}
結果として、
16本目: Pooler接続成功
16本目: BEGIN時間 約119ms
16本目: SELECT時間 約162ms
となりました。
DBに15本処理を実行中でも16本目の処理は成功したので、
「Pool sizeが15だから16本目は必ず接続できない」という単純な仕組みではないことが分かりました。
DB connections数が増やしたほうがシステム拡張したさいに便利ではないか
ここが重要です。
システムを拡張したさいに同時接続数も増えるため、Connection Pool数を増やしたほうがいいと考えていました。
しかし、増やすと、接続ごとのメモリ使用量、CPU負荷が増える可能性があります。
一概に増やしたほうがいいわけではなくその場に応じた設定を考える必要があります。
おわりに
業務でも設定することが少ないので、すごく参考になりました。
参考文献

