コンテンツにスキップ

制限事項

サーバーひとつにセッションひとつ

Section titled “サーバーひとつにセッションひとつ”

PostgreSQL をシングルユーザーモードで動かしているため、サーバーやフォークへの接続はすべて 1 つのバックエンドセッションを共有します。pgmem はトランザクションモードのコネクションプーラーと同じ方法で接続を多重化します。ある接続が BEGIN(あるいは Sync 前のパイプラインメッセージや COPY FROM STDIN)からトランザクション終了までバックエンドを占有し、その間ほかの接続は待ちます。どんなサイズのコネクションプールも動きますが、並列ではなく直列に処理されます。並列性はフォークで得ます。

その結果、トランザクションプーリングでおなじみの制約が付きます。

  • セッション状態は共有されます。 生きている接続の間で SET、一時テーブル、アドバイザリーロックが共有されます。SET LOCAL を使ってください。プリペアドステートメントは接続ごとに名前へ接頭辞を付けるので安全です。ほかに生きている接続がない状態で新しい接続が始まると、セッションは新しいバックエンドと同じ状態にリセットされます。
  • LISTENNOTIFY は接続単位で動きます。 通知はそのチャネルを LISTEN している接続に届きます。
  • アイドル状態のトランザクションの後ろでの待ちは打ち切られます。 ある接続がトランザクションの中にいる間、ほかの接続は動けません。トランザクションを持つ側が何もせずに止まっている間に別の接続が待つと、待っている側の接続は WaitTimeout(既定 2 秒)後に SQLSTATE 55P03 で切断され、保持している接続を示すメッセージが返ります。トランザクションのコールバックの中で、トランザクションのハンドルではなくプールでクエリを投げたときによく起きます。単に遅い文なら最後まで待ちます。
  • snapshot は開いているトランザクションを待ちます。 スナップショットの前にテンプレートへの接続はすべてコミットするか閉じてください。ラッパーは 30 秒で busy エラーにします。

サーバーが持つすべてのデータベースに接続でき、CREATE DATABASE も動きます。バックエンドが一度に提供するデータベースは 1 つで、別のデータベースへの接続が来るとそのデータベースでバックエンドを再起動します。再起動は 10 ms を大きく下回ります。提供されなくなったデータベースの接続は、再び提供されたときにプリペアドステートメントと LISTEN の登録を取り戻しますが、SET の値と一時テーブルは戻りません。Prisma のシャドウデータベースはこれだけで動きます。データベースを行き来し続ける接続は、切り替えのたびに再起動のコストを払います。存在しないデータベースは SQLSTATE 3D000 で拒否します。

サーバーやフォークを閉じても、クライアントの接続は切りません。各接続は、クライアントが閉じるか、メッセージを送るか、30 秒たつまで開いたままです。メッセージには SQLSTATE 57P01 で応答します。node-postgres などのプールは、アイドル状態の接続を足元で切られると処理されないエラーを投げるためです。

バックグラウンドプロセスが必要な機能

Section titled “バックグラウンドプロセスが必要な機能”
  • パラレルクエリとパラレルインデックス作成は無効です(max_parallel_workers=0)。
  • バックグラウンドワーカーを使う拡張は使えません。拡張機能を参照してください。
  • io_methodsync に固定しています。
  • 既定の shared_buffers=32MB で、サーバー 1 つあたりの常駐メモリはおよそ 150 MB です。生きているフォークはそれぞれ自分の複製を持ちます。大きなキャッシュが必要なテストでは、サーバーパラメータで shared_buffers を増やしてください。
  • サーバーは 32 ビットの WebAssembly で、扱えるのは最大 4 GiB です。
  • statement_timeout は、プロトコルメッセージの合間と、バックエンドが時計を読むかスリープするときに発火します。どちらもしない CPU ループの中では発火しません。

フォーク枠はスナップショットごとにあります。MaxForks はそのスナップショットから同時に起動できるフォーク数を制限し、テンプレートサーバーは枠に数えません。既定値は利用可能な CPU 数です。フォークごとにデータディレクトリとバッファキャッシュを複製するため、上限はメモリ使用量も抑えます。すべての枠が埋まると、次のフォーク要求は既存フォークが閉じて枠が空くまで待ちます。

API 上限の設定 空き枠を待つ時間の上限
Go SnapshotOptions.MaxForks または pgmemtest.Options.MaxForks Snapshot.Fork(ctx) は context がキャンセルされるか期限を迎えるまで待ちます。サーバーロガーを有効にしている場合、5 秒を超える待機をログに出します。
Python server.snapshot(max_forks=n) snapshot.fork(timeout=秒)。省略または None なら無期限に待ちます。期限を超えると ProtocolError の code は pool_timeout です。
Java Server.snapshot(maxForks) または JUnit の .maxForks(n) Snapshot.fork(Duration) または JUnit の .forkTimeout(Duration)。既定ではタイムアウトしません。期限を超えると pool_timeout で失敗します。
Node.js PgmemServer.start({ maxForks: n }) fork({ timeoutMs }) または withFork(fn, { timeoutMs })。省略すると無期限に待ちます。期限を超えると PgmemError の code は pool_timeout です。

JUnit 拡張で設定した上限は、登録したテンプレートごとに適用されます。スナップショットの close() は新しいフォークを拒否しますが、稼働中のフォークは止めません。Go の Snapshot.Wait() は、フォークがすべて閉じるまで待ちます。

タイムアウトは待ちの種類ごとに異なる

Section titled “タイムアウトは待ちの種類ごとに異なる”

フォーク枠を待つ時間は、スナップショット作成や別の接続の処理を待つ時間を制限しません。それぞれ別の設定です。

待つもの 設定 期限を超えたとき
スナップショット作成時に、開いているトランザクションが終わるのを待つ Go: Snapshot の context。Python: server.snapshot(timeout=30.0)。Java: Server.snapshot(maxForks, timeout)(既定 30 秒)。Node.js: snapshotTimeoutMs(既定 30,000 ms)。 busy で失敗します。テンプレートへの接続をコミットするか閉じてください。
アイドルトランザクションを持つ別接続の後ろで待つ Go: Options.WaitTimeout。Node.js: waitTimeoutMs(既定 2,000 ms)。 待っていた接続は SQLSTATE 55P03 で終了します。フォーク枠の不足とは別の状態です。
サーバープロセスの起動を待つ Python: pgmem.start(timeout=30)。Java: .readyTimeout(Duration)(既定 30 秒)。Node.js: startupTimeoutMs(既定 30,000 ms)。Go: Start の context。 期限内にサーバーが ready にならなければ起動に失敗します。
フォークをスナップショットへ戻す Go: Server.Reset(ctx) または Restore(ctx)。Node.js: fork.reset({ timeoutMs })(既定 5,000 ms)。 開いたトランザクションが終わらなければ busy で失敗します。

TLS もパスワード認証もありません。サーバーは 127.0.0.1 だけで待ち受け、URL には sslmode=disable が付きます。ICU の照合順序は使えません。