小さなショップのデータ層に 70 本のテスト(参照のみ 50 本、更新 20 本)を書きました。共有の PostgreSQL サーバー 1 台(ここでは Testcontainers で起動)では、更新テストは共有データのリセットを挟んで 1 本ずつ順番に走るしかありません。pgmem ではマイグレーションとシードデータの投入後にスナップショットを 1 回取り、参照テストはそのデータベースを共用し、更新テストだけが自分のコピーをフォークするので、スナップショット以降はすべて並列に走ります。
同じ 70 本のテスト(参照 50、更新 20)を、同じスキーマ・マイグレーション・シードデータで、go test の並列度 8 で実行しました。各タイムラインは Apple M3(8 コア)で 5 回走らせたうち合計時間が中央値だった実行です。バーは各実行モデルのノードで、左に短くまとまっているほど速いことを表します。
スイート全体の実時間
pgmem204 ms
5 回の範囲 196 ms〜219 ms
Testcontainers 共有サーバー 1 台1.73 s
5 回の範囲 1.70 s〜1.81 s
PostgreSQL 共有サーバー 1 台646 ms
5 回の範囲 633 ms〜671 ms
docker run 共有サーバー 1 台2.48 s
5 回の範囲 2.26 s〜3.59 s
テストは go test -parallel の 8 枠に描いています。1 つの枠には、そこで順に走ったテストが並びます。 タイムラインはすべて同じ時間軸です。
- DB 起動
- スキーマ投入 + マスタデータ投入
- スナップショット
- フォーク(更新テスト専用のコピー)
- サンプルデータ投入
- データのリセット
- 参照テスト
- 更新テスト
pgmem 204 ms 合計テストプロセス内。参照テストはテンプレートを共用し、更新テストはスナップショットからフォークして、すべて並列に走るフォークは 20 本で、更新テスト(20 本)1 本につき 1 本です。参照テスト 50 本はフォークせず、テンプレートのデータベースをそのまま使います。Testcontainers 共有サーバー 1 台 1.73 s 合計公式イメージを testcontainers-go の postgres モジュールでオンメモリ起動(tmpfs、fsync なし)。更新テストはリセットを挟んで順番に走るコピーはなく、参照テスト 50 本と更新テスト 20 本が 1 つのデータベースを共有します。
スイートの中身、計測ハーネス、各ノードの時間はベンチマークのページで説明しています。プロジェクトは bench/modelcase にあります。
本物の PostgreSQL
PostgreSQL 18.3 を WebAssembly にコンパイルし、Go に変換したものです。SQL の解釈もプランナの挙動もエラーコードも、エミュレータではなく PostgreSQL そのものです。
インストールするものがない
ライブラリと対応するプラットフォームのバイナリを導入するだけです。Docker デーモンも、CI 上のコンテナランタイムも要りません。データベースのファイルをディスクに書きません。
読み取りは共有、書き込みは分離
マイグレーションとシードは一度だけ。読み取り専用テストではフォークを共有し、書き込みテストには専用の複製を渡します。書き込みテスト同士も並列に走らせられます。
ドライバを選ばない
PostgreSQL のワイヤプロトコルで待ち受けるので、pgx、psycopg、JDBC、node-postgres、ORM、マイグレーションツールがそのまま動きます。
s, err := pgmem.Start(ctx, pgmem.Options{Database: "app"})
db, _ := sql.Open("pgx", s.DSN())
with pgmem.start(database="app") as pg:
with psycopg.connect(pg.template.dsn) as conn:
try (Pgmem pg = Pgmem.builder().database("app").start();
Connection conn = DriverManager.getConnection(pg.template().jdbcUrl())) {
conn.createStatement().execute("SELECT 1");
import { PgmemServer } from '@pgmem/core';
const server = await PgmemServer.start({ database: 'app' });
const pool = new pg.Pool({ connectionString: server.url });
try { await pool.query('SELECT 1'); }
finally { await pool.end(); }
} finally { await server.close(); }
同じスキーマ、同じクエリ、同じテスト単位の分離を、同じマシンで計測しました。比較相手は公式イメージを使った docker run、Testcontainers、devbox で入れた PostgreSQL です。
pgmem の値は 2026-09-18 に 5 回計測した中央値です。Docker、Testcontainers、devbox の比較値は 2026-09-14 の基準値を引き継いでいます。Docker と Testcontainers のメモリは OrbStack を再起動して 3 回測定しました。バーは各カード内の最大値を基準にし、短いほど値が小さくなります。
起動から最初のクエリまで
起動コマンドから TCP SELECT 1 まで。取得時間は除外し、Devbox は初期化済みクラスタを使います。
pgmem · Go ライブラリ70 ms
Docker · PostgreSQL 181.65 s
Testcontainers · Go1.09 s
devbox · services up411 ms
テストごとの複製
DB を分離し、結合クエリを実行する時間の中央値。
インデックス SELECT
ウォームアップ後に行う TCP 経由の検索。
pgmem · Go ライブラリ29 µs
Docker · PostgreSQL 1893 µs
Testcontainers · Go102 µs
devbox · services up28 µs
20万行ソート
20 万行を並べ替えて数える時間の中央値。
pgmem · Go ライブラリ117 ms
Docker · PostgreSQL 18259 ms
Testcontainers · Go258 ms
devbox · services up228 ms
DB と計測プロセスのメモリ
スキーマ準備後、クエリ前の値です。Docker と Testcontainers はコンテナ使用量(Testcontainers は PostgreSQL と Ryuk)と計測プロセスの増分を合算します。OrbStack VM の変化は別に示します。
pgmem · Go ライブラリ103 MiB
Docker · PostgreSQL 1877 MiB
Testcontainers · Go137 MiB
devbox · services up36 MiB
初回取得サイズ
同じ MB スケールですが、Go のリンク増分、圧縮イメージ、devbox の取得量で測る対象が異なります。
pgmem · Go ライブラリ33.1 MB
Docker · PostgreSQL 18117.9 MB
Testcontainers · Go120.0 MB
devbox · services up112.7 MB
現在の darwin/arm64 向け strip 済み pgmem バイナリは、エクスポートを絞った
AOT バックエンドを含めて 36.7 MB です。ベンチマークページには pgmem だけを
再計測した最新値を記録し、比較対象は再現可能な基準値として残しています。
クエリ性能、計測方法、再現手順はベンチマークのページにあります。