データベース接続
データベースが処理できる同時接続数には限りがあります。各接続は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
がキャッシュされていることを確認することです。たとえば、context
オブジェクトでPrismaClient
をインスタンス化し、Expressアプリに渡すことができます。
明示的に$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つの追加のラムダを生成します。各ラムダは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 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
プロパティにデータベースへの直接接続を提供する環境変数を追加する必要があります
# 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を使用できます
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と一緒に使用しても接続プーリングのメリットは提供されません。