メインコンテンツにスキップ

データベース接続

データベースが処理できる同時接続数には限りがあります。各接続はRAMを必要とするため、利用可能なリソースをスケールせずにデータベース接続制限を単純に引き上げると

  • ✔ より多くのプロセスが接続できるようになるかもしれませんが、
  • ✘ データベースのパフォーマンスに著しく影響を与え、メモリ不足エラーによりデータベースがシャットダウンする可能性があります。

アプリケーションが接続を管理する方法もパフォーマンスに影響を与えます。このガイドでは、サーバーレス環境および長時間実行プロセスにおける接続管理へのアプローチ方法について説明します。

警告

このガイドでは、リレーショナルデータベースと、Prisma ORM接続プールの設定およびチューニング方法について重点的に説明します(MongoDBはMongoDBドライバー接続プールを使用します)。

長時間実行プロセス

長時間実行プロセスの例としては、Herokuや仮想マシンなどのサービスでホストされているNode.jsアプリケーションがあります。長時間実行環境での接続管理のガイドとして、次のチェックリストを使用してください。

長時間実行プロセスで最初に始める推奨接続プールサイズ(connection_limit)は、デフォルトのプールサイズnum_physical_cpus * 2 + 1)÷アプリケーションインスタンス数です。

情報

num_physical_cpusは、アプリケーションが実行されているマシンのCPU数を指します。

アプリケーションインスタンスが1つの場合

  • デフォルトのプールサイズがデフォルトで適用されます(num_physical_cpus * 2 + 1)- connection_limitパラメータを設定する必要はありません。
  • オプションでプールサイズをチューニングできます。

アプリケーションインスタンスが複数の場合

  • connection_limitパラメータを手動で設定する必要があります。たとえば、計算されたプールサイズが10で、アプリケーションのインスタンスが2つある場合、connection_limitパラメータは5以下にする必要があります。
  • オプションでプールサイズをチューニングできます。

長時間実行アプリケーションにおけるPrismaClient

長時間実行アプリケーションでは、以下をお勧めします

単一のPrismaClientインスタンスの再利用

単一のインスタンスを再利用するには、PrismaClientオブジェクトをエクスポートするモジュールを作成します

client.ts
import { PrismaClient } from '@prisma/client'

let prisma = new PrismaClient()

export default prisma

オブジェクトは、モジュールが最初にインポートされたときにキャッシュされます。後続のリクエストは、新しいPrismaClientを作成するのではなく、キャッシュされたオブジェクトを返します。

app.ts
import prisma from './client'

async function main() {
const allUsers = await prisma.user.findMany()
}

main()

上記の例を完全に再現する必要はありません。目標は、PrismaClientがキャッシュされていることを確認することです。たとえば、contextオブジェクトPrismaClientをインスタンス化し、Expressアプリに渡すことができます。

明示的に$disconnect()しないでください

リクエストを継続的に処理する長時間実行アプリケーションのコンテキストでは、明示的に$disconnect()する必要はありません。新しい接続を開くには時間がかかり、クエリごとに切断するとアプリケーションの速度が低下する可能性があります。

ホットリロードがPrismaClientの新しいインスタンスを作成するのを防ぐ

Next.jsのようなフレームワークは、変更されたファイルのホットリロードをサポートしており、再起動せずにアプリケーションへの変更を確認できます。ただし、フレームワークがPrismaClientのエクスポートを担当するモジュールをリフレッシュすると、開発環境で不要なPrismaClientのインスタンスが追加される可能性があります。

回避策として、グローバル変数はリロードされないため、開発環境でのみPrismaClientをグローバル変数として保存できます。

client.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }

export const prisma =
globalForPrisma.prisma || new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

Prisma Clientのインポートおよび使用方法は変わりません

app.ts
import { prisma } from './client'

async function main() {
const allUsers = await prisma.user.findMany()
}

main()

CLIコマンドごとに作成される接続数

Postgres、MySQL、およびSQLiteを使用したローカルテストでは、各Prisma CLIコマンドは通常、単一の接続を使用します。以下の表は、これらのテストで観察された範囲を示しています。環境によっては、わずかに異なる結果になる場合があります。

コマンド接続数説明
migrate status1マイグレーションのステータスを確認します
migrate dev1–4開発環境で保留中のマイグレーションを適用します
migrate diff1–2データベーススキーマとマイグレーション履歴を比較します
migrate reset1–2データベースをリセットし、マイグレーションを再適用します
migrate deploy1–2本番環境で保留中のマイグレーションを適用します
db pull1データベーススキーマをPrismaスキーマにプルします
db push1–2Prismaスキーマをデータベースにプッシュします
db execute1生のSQLコマンドを実行します
db seed1初期データでデータベースをシードします

サーバーレス環境(FaaS)

サーバーレス環境の例としては、AWS Lambda、Vercel、またはNetlify FunctionsでホストされているNode.js関数があります。サーバーレス環境での接続管理のガイドとして、次のチェックリストを使用してください。

サーバーレスの課題

サーバーレス環境では、各関数が独自のPrismaClientインスタンスを作成し、各クライアントインスタンスには独自の接続プールがあります。

単一のAWS Lambda関数がPrismaClientを使用してデータベースに接続する次の例を考えてみましょう。connection_limitは3です

An AWS Lambda function connecting to a database.

トラフィックの急増により、AWS Lambdaは負荷の増加を処理するために2つの追加のラムダを生成します。各ラムダはPrismaClientのインスタンスを作成し、それぞれconnection_limitが3であるため、データベースへの最大9つの接続になります

Three AWS Lambda function connecting to a database.

トラフィックの急増📈に対応する200の同時実行関数(したがって600の可能な接続)は、データベース接続制限を非常に迅速に使い果たす可能性があります。さらに、一時停止された関数はデフォルトで接続を開いたままにし、別の関数で使用されるのをブロックします。

  1. connection_limit1に設定することから始めます
  2. より小さなプールサイズでは不十分な場合は、PgBouncerのような外部接続プーラーの使用を検討してください

サーバーレス環境で推奨されるプールサイズ(connection_limit)は、以下に依存します

外部接続プーラーなしの場合

外部接続プーラーを使用していない場合は、最初にプールサイズ(connection_limit)を1に設定してから、最適化します。受信リクエストごとに短命のNode.jsプロセスが開始され、connection_limitが高い多くの同時実行関数は、トラフィックの急増時にデータベース接続制限をすぐに使い果たす可能性があります。

次の例は、接続URLでconnection_limitを1に設定する方法を示しています

postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public&connection_limit=1
ヒント

AWS Lambdaを使用しており、connection_limitを設定していない場合は、予想されるデフォルトのプールサイズに関する情報について、次のGitHub issueを参照してください:https://github.com/prisma/docs/issues/667

外部接続プーラーを使用している場合

外部接続プーラーを使用している場合は、デフォルトのプールサイズ(num_physical_cpus * 2 + 1)を開始点として使用し、プールサイズを調整します。外部接続プーラーは、トラフィックの急増がデータベースを圧倒するのを防ぐはずです。

並列リクエストの最適化

プールサイズを1に設定してもデータベース接続制限をほとんどまたはまったく超えない場合は、接続プールサイズをさらに最適化できます。クエリを並行して送信する関数を検討してください

Promise.all() {
query1,
query2,
query3
query4,
...
}

connection_limitが1の場合、この関数はクエリを並行ではなくシリアル(1つずつ)で送信することを強制されます。これにより、関数のリクエスト処理能力が低下し、プールタイムアウトエラーが発生する可能性があります。トラフィックの急増が発生するまで、connection_limitパラメータを調整します

  • データベース接続制限を使い果たさない
  • プールタイムアウトエラーが発生しない

サーバーレス環境におけるPrismaClient

ハンドラーの外でPrismaClientをインスタンス化する

再利用の可能性を高めるために、関数ハンドラーのスコープ外でPrismaClientをインスタンス化します。ハンドラーが「ウォーム」(使用中)である限り、接続は再利用できる可能性があります

import { PrismaClient } from '@prisma/client'

const client = new PrismaClient()

export async function handler() {
/* ... */
}

明示的に$disconnect()しないでください

コンテナが再利用される可能性があるため、関数の最後に明示的に$disconnect()する必要はありません。新しい接続を開くには時間がかかり、関数のリクエスト処理能力が低下します。

その他のサーバーレスに関する考慮事項

コンテナの再利用

関数の後続の近くの呼び出しが同じコンテナにヒットするという保証はありません。たとえば、AWSはいつでも新しいコンテナを作成することを選択できます。

コードはコンテナをステートレスであると想定し、接続が存在しない場合にのみ接続を作成する必要があります - Prisma Client JSはすでにこのロジックを実装しています。

ゾンビ接続

「削除対象」とマークされ、再利用されていないコンテナは、接続を開いたままにし、しばらくの間その状態を維持できます(不明であり、AWSからは文書化されていません)。これにより、データベース接続の利用率が最適ではなくなる可能性があります。

潜在的な解決策は、アイドル接続をクリーンアップすることです(serverless-mysqlはこのアイデアを実装していますが、Prisma ORMでは使用できません)。

同時実行数制限

サーバーレス同時実行数制限(並行して実行されているサーバーレス関数の数)によっては、データベースの接続制限を使い果たす可能性があります。これは、あまりにも多くの関数が同時に呼び出され、それぞれが独自の接続プールを持っている場合に発生する可能性があり、最終的にデータベース接続制限を使い果たします。これを防ぐために、サーバーレス同時実行数制限を設定して、データベースの最大接続制限を各関数呼び出しで使用される接続数で割った数よりも小さくすることができます(他の目的で別のクライアントから接続できるようにする必要がある場合があるため)。

接続プールの最適化

クエリエンジンがタイムリミット前にキュー内のクエリを処理できない場合、ログに接続プールタイムアウト例外が表示されます。接続プールタイムアウトは、次の場合に発生する可能性があります

  • 多くのユーザーが同時にアプリにアクセスしている
  • 多数のクエリを並行して送信する(たとえば、await Promise.all()を使用するなど)

推奨プールサイズを設定した後も接続プールタイムアウトが頻繁に発生する場合は、connection_limitおよびpool_timeoutパラメータをさらに調整できます。

プールサイズの増加

プールサイズを増やすと、クエリエンジンはより多くのクエリを並行して処理できます。データベースが増加した同時接続数をサポートできる必要があることに注意してください。そうしないと、データベース接続制限を使い果たします。

プールサイズを増やすには、connection_limitを手動でより高い数値に設定します

datasource db {
provider = "postgresql"
url = "postgresql://johndoe:mypassword@localhost:5432/mydb?schema=public&connection_limit=40"
}

注意:サーバーレス環境でconnection_limitを1に設定することは推奨される開始点ですが、この値は調整することもできます

プールタイムアウトの増加

プールタイムアウトを増やすと、クエリエンジンはキュー内のクエリを処理するためにより多くの時間をかけることができます。次のシナリオでは、このアプローチを検討するかもしれません

  • すでにconnection_limitを増やしている。
  • キューが一定のサイズを超えて成長しないと確信している場合、そうでない場合は最終的にRAMが不足します

プールタイムアウトを増やすには、pool_timeoutパラメータをデフォルト(10秒)よりも大きい値に設定します

datasource db {
provider = "postgresql"
url = "postgresql://johndoe:mypassword@localhost:5432/mydb?connection_limit=5&pool_timeout=20"
}

プールタイムアウトの無効化

プールタイムアウトを無効にすると、クエリエンジンは接続を待機してからx秒後に例外をスローするのを防ぎ、キューを構築できるようになります。次のシナリオでは、このアプローチを検討するかもしれません

  • 限られた時間内に多数のクエリを送信しています。たとえば、データベース内のすべての顧客をインポートまたは更新するジョブの一部として。
  • すでにconnection_limitを増やしている。
  • キューが一定のサイズを超えて成長しないと確信している場合、そうでない場合は最終的にRAMが不足します

プールタイムアウトを無効にするには、pool_timeoutパラメータを0に設定します

datasource db {
provider = "postgresql"
url = "postgresql://johndoe:mypassword@localhost:5432/mydb?connection_limit=5&pool_timeout=0"
}

外部接続プーラー

Prisma AccelerateやPgBouncerのような接続プーラーは、アプリケーションがデータベースの接続制限を使い果たすのを防ぎます。

データベースで他のアクション(マイグレーションやイントロスペクションなど)を実行するためにPrisma CLIを使用する場合は、Prismaスキーマのdatasource.directUrlプロパティにデータベースへの直接接続を提供する環境変数を追加する必要があります

.env
# Connection URL to your database using PgBouncer.
DATABASE_URL="postgres://root:password@127.0.0.1:54321/postgres?pgbouncer=true"

# Direct connection URL to the database used for migrations
DIRECT_URL="postgres://root:password@127.0.0.1:5432/postgres"

その後、schema.prismaを更新して新しいダイレクトURLを使用できます

schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}

directUrlフィールドの詳細については、こちらをご覧ください。

Prisma Accelerate

Prisma Accelerateは、Prismaによって構築されたマネージド外部接続プーラーであり、Prisma Data Platformに統合されており、接続プーリングを処理します。

PgBouncer

PostgreSQLは特定の数の同時接続のみをサポートしており、サービス使用量が増加すると、特にサーバーレス環境では、この制限に非常に早く達する可能性があります。

PgBouncerはデータベースへの接続プールを保持し、Prisma Clientとデータベースの間に位置することで、受信クライアント接続をプロキシ処理します。これにより、データベースが任意の時点で処理する必要があるプロセス数が削減されます。PgBouncerは、データベースへの接続数を制限し、接続が利用可能になったときに配信するための追加の接続をキューに入れます。PgBouncerを使用するには、「PgBouncerでのPrisma Clientの設定」を参照してください。

AWS RDS Proxy

AWS RDS Proxyが接続をピン留めする方法により、Prisma Clientと一緒に使用しても接続プーリングのメリットは提供されません