データベース接続
データベースは限られた数の同時接続しか処理できません。各接続にはRAMが必要であり、利用可能なリソースを拡張せずにデータベース接続制限を単純に増やすだけでは
- ✔ より多くのプロセスが接続できる場合がありますが、
- ✘ データベースのパフォーマンスに著しく影響し、メモリ不足エラーによりデータベースがシャットダウンする可能性があります
アプリケーションが接続を管理する方法もパフォーマンスに影響します。このガイドでは、サーバーレス環境と長時間実行プロセスにおける接続管理のアプローチについて説明します。
このガイドは、リレーショナルデータベースと、Prisma ORM接続プールの設定および調整方法に焦点を当てています(MongoDBはMongoDBドライバ接続プールを使用します)。
長時間実行プロセス
長時間実行プロセスの例には、Herokuや仮想マシンなどのサービスでホストされているNode.jsアプリケーションが含まれます。長時間実行環境での接続管理のガイドとして、次のチェックリストを使用してください
- 推奨されるプールサイズ(
connection_limit
)から開始し、調整します PrismaClient
のグローバルインスタンスを1つ持っていることを確認してください
推奨される接続プールサイズ
長時間実行プロセス向けに最初に設定する推奨接続プールサイズ(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
オブジェクトをエクスポートするモジュールを作成します。
import { PrismaClient } from '@prisma/client'
let prisma = new PrismaClient()
export default prisma
このオブジェクトは、モジュールが最初にインポートされたときにキャッシュされます。以降のリクエストでは、新しいPrismaClient
を作成するのではなく、キャッシュされたオブジェクトが返されます。
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
をグローバル変数として保存できます。
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のインポートと使用方法は変わりません。
import { prisma } from './client'
async function main() {
const allUsers = await prisma.user.findMany()
}
main()
CLIコマンドごとに作成される接続数
Postgres、MySQL、SQLiteを使用したローカルテストでは、各Prisma CLIコマンドは通常、単一の接続を使用します。以下の表は、これらのテストで観察された範囲を示しています。お使いの環境では、わずかに異なる結果が生成される可能性があります。
コマンド | 接続数 | 説明 |
---|---|---|
migrate status | 1 | マイグレーションのステータスを確認します |
migrate dev | 1~4 | 開発環境で保留中のマイグレーションを適用します |
migrate diff | 1~2 | データベーススキーマをマイグレーション履歴と比較します |
migrate reset | 1~2 | データベースをリセットし、マイグレーションを再適用します |
migrate deploy | 1~2 | 本番環境で保留中のマイグレーションを適用します |
db pull | 1 | データベーススキーマをPrismaスキーマにプルします |
db push | 1~2 | Prismaスキーマをデータベースにプッシュします |
db execute | 1 | 生のSQLコマンドを実行します |
db seed | 1 | 初期データでデータベースをシードします |
サーバーレス環境(FaaS)
サーバーレス環境の例には、AWS Lambda、Vercel、またはNetlify FunctionsでホストされているNode.js関数が含まれます。サーバーレス環境での接続管理のガイドとして、次のチェックリストを使用してください
- サーバーレス接続管理の課題を理解する
- 外部接続プーラーを使用しているかどうかに応じて、プールサイズ(
connection_limit
)を設定し、オプションでプールサイズを調整する - ハンドラーの外部で
PrismaClient
をインスタンス化し、明示的に$disconnect()
しない - 関数同時実行を構成し、アイドル接続を処理する
サーバーレスの課題
サーバーレス環境では、各関数がPrismaClient
の独自のインスタンスを作成し、各クライアントインスタンスが独自の接続プールを持ちます。
次の例を考えてみましょう。単一のAWS Lambda関数がPrismaClient
を使用してデータベースに接続しています。connection_limit
は3です。
トラフィックの急増により、AWS Lambdaは負荷増加に対応するために2つの追加のLambdaを起動します。各LambdaはPrismaClient
のインスタンスを作成し、それぞれがconnection_limit
を3とします。これにより、データベースへの最大接続数は9になります。
200の同時実行関数(したがって600の可能な接続数)がトラフィックの急増📈に対応すると、データベースの接続制限が非常に迅速に使い果たされる可能性があります。さらに、一時停止された関数は、デフォルトで接続を開いたままにし、他の関数による使用をブロックします。
- まず
connection_limit
を1
に設定することから始めます - より小さいプールサイズでは不十分な場合、PgBouncerのような外部接続プーラーの使用を検討してください
推奨される接続プールサイズ
サーバーレス環境での推奨プールサイズ(connection_limit
)は、以下に依存します。
- 外部接続プーラーを使用しているかどうか
- 関数が並列でクエリを送信するように設計されているかどうか
外部接続プーラーなし
外部接続プーラーを使用していない場合、まずプールサイズ(connection_limit
)を1に設定し、その後最適化します。各受信リクエストは短命のNode.jsプロセスを開始し、connection_limit
が高い多数の同時実行関数は、トラフィックの急増中にデータベースの接続制限を迅速に使い果たす可能性があります。
次の例は、接続URLでconnection_limit
を1に設定する方法を示しています。
- PostgreSQL
- MySQL
postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public&connection_limit=1
mysql://USER:PASSWORD@HOST:PORT/DATABASE?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_limit
とpool_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
プロパティに、データベースへの直接接続を提供する環境変数を追加する必要があります。
# 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
を更新できます。
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と組み合わせて使用した場合、接続プーリングの利点を提供しません。