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

データベース接続

データベースは限られた数の同時接続しか処理できません。各接続には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のインスタンスを1つ作成し、アプリケーション全体で再利用する
  • ✔ ホットリロードによる新しいインスタンスの作成を防止するため開発環境のみで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がキャッシュされるようにすることです。たとえば、Expressアプリに渡すcontextオブジェクト内でPrismaClientをインスタンス化することもできます。

明示的に$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_limit3です。

An AWS Lambda function connecting to a database.

トラフィックの急増により、AWS Lambdaは負荷増加に対応するために2つの追加のLambdaを起動します。各LambdaはPrismaClientのインスタンスを作成し、それぞれがconnection_limit3とします。これにより、データベースへの最大接続数は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イシューを参照してください: https://github.com/prisma/docs/issues/667

外部接続プーラーを使用する場合

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

並列リクエストの最適化

プールサイズが1に設定されていても、データベース接続制限を超えることがめったにない場合、接続プールサイズをさらに最適化できます。並列でクエリを送信する関数を考えてみましょう。

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

connection_limitが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_limitpool_timeoutパラメータをさらに調整できます。

プールサイズの増加

v6.7.0以降、Prisma ORMはqueryCompilerプレビュー機能を備えています。

有効にすると、Prisma ClientはRustベースのクエリエンジンバイナリなしで生成されます。:

generator client {
provider = "prisma-client-js"
previewFeatures = ["queryCompiler", "driverAdapters"]
}

queryCompilerと合わせて、ドライバアダプタプレビュー機能が必要です。queryCompilerプレビュー機能を使用する場合、接続プールサイズは使用しているネイティブJSドライバを介して設定されます。

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

プールサイズを増やすには、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"

その後、新しい直接URLを使用するようにschema.prismaを更新できます。

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と組み合わせて使用した場合、接続プーリングの利点を提供しません

© . All rights reserved.