2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PR: JISOU | Reactプログラミングコーチング
AIで不安を感じたら!    JISOUメンバー募集中

Connection Poolの理解。Supabaseで接続数の検証

2
Posted at

はじめに

Connection Poolについてまとめました。

背景

IndexやN+1問題があり対処してきましたが、Connection Poolはサーバー側だけの問題ではなくなります。
業務では見たこともありますが、あまり触れない分野なのでまとめました。

Connection Poolとは、すぐにDBに接続できるように準備された枠組みです。
例でいうと、タクシー乗り場に似ています。タクシーがある限りすぐに乗車できますが、ない場合は待つ必要があります。

Connection Pool

Supabaseをみると、Database SettingsでConnection poolingがあります。
Connection pool sizeが15と書いています。

image.png

図にすると、下のようになります。

┌───────────────┐
│ 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本を流すことを検証してみました。

コード
TestConnectionPool16
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負荷が増える可能性があります。
一概に増やしたほうがいいわけではなくその場に応じた設定を考える必要があります。

おわりに

業務でも設定することが少ないので、すごく参考になりました。

参考文献

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?