PGlite(ピージーライト)は、PostgreSQLをWebAssembly(WASM)にビルドし、TypeScriptのライブラリとして配布しているデータベースです。npm install だけでNode.js・Bun・Deno・ブラウザの中にPostgreSQLが立ち上がるため、ローカル開発やテストのためにDockerでPostgreSQLのコンテナを起動する必要がなくなります。
この記事は、2026年10月28日公開の @electric-sql/pglite 0.5.13 を実際に動かした結果をもとに、導入、Dockerの代わりに使う手順、Pythonからの利用、pgvectorでの類似度検索までをまとめたものです。0.5.0で拡張機能の読み込み方が変わったため、ネット上の多くのサンプルはそのままでは動きません。その点も実行結果つきで説明します。
まとめ:PGliteの要点
- PGliteはWASM版のPostgreSQLです。0.5.13の
SELECT version()は「PostgreSQL 18.6 (PGlite 0.5.13)」を返します。 - データはメモリ・ファイル(Node.js)・IndexedDB(ブラウザ)のいずれかに置けます。接続は1本だけなので、本番サーバーのPostgreSQLの代わりにはなりません。
- Dockerの代わりに使うときは
pglite-serverを起動します。psqlやORMからpostgresql://postgres:[email protected]:5432/postgresで接続できますが、複数の接続は1本ずつ順番に処理されます。 - pgvectorは0.5.0から別パッケージの
@electric-sql/pglite-pgvectorです。旧来の@electric-sql/pglite/vectorはERR_PACKAGE_PATH_NOT_EXPORTEDで失敗します。 - 文章の類似度検索はコサイン距離
<=>を使います。Intel MacのNode.jsで384次元・1万件を測ると、インデックスなしの検索が21〜71ミリ秒、HNSWの構築が約45秒(同じPCのネイティブのPostgreSQL 18.6では約2秒)でした。インデックスは、実データで測って応答時間が足りなくなってから張ります。
PGliteの仕組みと現在の版
PGliteはElectricSQLが開発しているオープンソースのプロジェクトで、Apache-2.0とPostgreSQL Licenseのデュアルライセンスから選択できます。公式READMEは「Node.js、Bun、Denoとブラウザで動き、gzipで3MB、pgvectorやPostGISなど多くの拡張に対応する」と説明しています。PostgreSQL本体のシングルユーザーモードをWASMから呼び出す作りで、Linuxの仮想マシンは使いません。ネイティブのPostgreSQLを別にインストールする必要もありません。npmのダウンロード数APIによると、2026年10月22〜28日の1週間のダウンロード数は36,019,104回でした。
同じREADMEには「PGlite is single user/connection.」という制約も明記されています。PGliteは1つのプロセスの中から1本の接続で使うデータベースで、ネットワーク越しに複数のアプリから共有する使い方は想定していません。
0.4系と0.5系の違い
2026年6月2日公開の0.5.0で、CHANGELOGに「Upgrade to Postgres 18.3; move other extensions to their own npm packages」とあるとおり、PostgreSQLが18系に上がり、pgvectorなどの拡張が本体から別パッケージに分かれました。0.5.9でPostgreSQL 18.6に更新されています。手元で両方の版を動かした結果は次のとおりです。
| PGlite | PostgreSQL | pgvector | pgvectorのimport元 |
|---|---|---|---|
| 0.4.6 | 17.5 | 0.8.1 | @electric-sql/pglite/vector |
| 0.5.13 | 18.6 | 0.8.7 | @electric-sql/pglite-pgvector |
0.5.13で旧来の書き方のまま import('@electric-sql/pglite/vector') を実行すると、ERR_PACKAGE_PATH_NOT_EXPORTED(Package subpath ‘./vector’ is not defined by “exports”)で止まります。ZennやDevelopersIOで公開されている2024〜2025年の解説記事は、確認した範囲ではすべてこの旧パスで書かれていました。
もう1つの落とし穴はデータディレクトリです。0.4.6で作ったディレクトリ(PG_VERSION は17)を0.5.13で開くと「PGlite failed to initialize properly」で起動しません。PostgreSQLのメジャー版が違うデータファイルは読めないためです。ファイルやIndexedDBにデータを残している場合は、上げる前に旧版で pglite-toolsのpgDump を使ってSQLとして書き出し、新しい版の exec() で流し込みます。
Node.jsでのPGlite導入と永続化設定
本体はnpmパッケージ1つです。Bunなら bun install、Denoなら deno add npm:@electric-sql/pglite で入ります。
npm install @electric-sql/pglite
import { PGlite } from '@electric-sql/pglite'
const db = await PGlite.create() // メモリ上(プロセス終了で消える)
// const db = await PGlite.create('./pgdata') Node.jsでファイルに保存
// const db = await PGlite.create('idb://my-pgdata') ブラウザでIndexedDBに保存
const { rows } = await db.query('SELECT version()')
console.log(rows[0].version)
// PostgreSQL 18.6 (PGlite 0.5.13) on wasm32-unknown-emscripten, ...
Intel MacのNode.js 26.5.0で計測すると、メモリ上のインスタンスの起動には約6〜7秒かかりました。起動のたびに初期化(initdb相当)が走るためです。テストでテストケースごとに新しく作ると、その分だけ遅くなります。テストファイルごとに1回作り、ケースの間は TRUNCATE やトランザクションのロールバックで状態を戻すほうが速く済みます。ファイル保存の ./pgdata は空の状態で約38MBでした。
ORMからはドライバ経由で使えます。Drizzleは drizzle-orm/pglite、Prismaはドライバアダプタを経由して接続します。Drizzle側の書き方はDrizzle ORM(ドリズル)とは?使い方・Prismaとの比較・v1.0 RCの変更点で扱っています。ブラウザの複数タブで1つのデータベースを共有したい場合は、PGliteWorkerで1つのWorkerに集約します。
pglite-serverによるDocker代替と接続制約
アプリが pg や psycopg のような通常のPostgreSQLクライアントで接続する作りの場合、PGliteをimportするようにコードを書き換えなくても済みます。@electric-sql/pglite-socket に入っている pglite-server が、PGliteをPostgreSQLのワイヤプロトコルで公開するからです。docker run postgres の代わりにこれを起動します。
psqlやORMから接続する手順
npm install @electric-sql/pglite @electric-sql/pglite-socket @electric-sql/pglite-pgvector
npx pglite-server --db=./pgdata --port=5432 \
--extensions=@electric-sql/pglite-pgvector:vector
# 別のターミナルから(SSLには対応していない)
PGSSLMODE=disable psql "postgresql://[email protected]:5432/postgres"
拡張を読み込むときは、--extensions=vector と書かないでください。0.2.16の --help は vector を組み込みの拡張として例示していますが、実行すると存在しないパッケージ @electric-sql/pglite-vector を探して起動に失敗しました。パッケージ名:エクスポート名 の形で書けば読み込まれ、CREATE EXTENSION vector のあと extversion は0.8.7になります。
開発サーバーと一緒に起動したいときは --run を使います。--include-database-url を付けると、子プロセスの環境変数 DATABASE_URL に postgresql://postgres:[email protected]:5432/postgres が入ります。0.2.16で試すと、子プロセスが終了コード2で失敗したときはサーバーも停止しましたが、終了コード0で正常に終わったときはサーバーが起動したまま残りました。テストのスクリプトを --run で流す場合は、終了後にサーバーのプロセスを止める処理を入れておきます。
npx pglite-server --run "npm run dev" --include-database-url
PostgreSQLのワイヤプロトコルを使うため、GoのpgxやPythonのSQLAlchemyからも接続する構成を取れます。ただしSSL、接続プール、セッション状態などの互換性は、使用するドライバと設定で確認します。
pglite-serverの同時接続制限と待機動作
PGlite本体の接続は1本だけなので、pglite-serverは複数の接続を1本にまとめて順番に処理します。既定の --max-connections は1です。1本目のpsqlで SELECT pg_sleep(5) を実行している間に2本目を繋ぐと、次の結果になりました。
| 設定 | 2本目の接続 |
|---|---|
| 既定(–max-connections=1) | server closed the connection unexpectedly |
| –max-connections=5 | 約4秒待たされたあと成功 |
接続プールを持つWebフレームワークやORM、並列実行するテストランナーは複数の接続を張ります。-m を上げないと2本目で接続が切れ、上げても処理は並行せず待たされます。pglite-socketのドキュメントも、通常のPostgreSQLとは違うためすべての使い方が動く保証はない、と注意しています。
Docker・Testcontainersとの使い分け
判断の基準は「本番と同じPostgreSQLの挙動が必要かどうか」です。
- PGliteで足りる:単体テストやリポジトリ層のテスト、ローカルでの試作、ブラウザだけで完結するデモ。Dockerデーモンが無いCIやPCでもそのまま動きます。
- DockerやTestcontainersにする:同時に書き込む処理やロック・デッドロックの検証、レプリケーション、PGliteで利用できないPostgreSQL拡張、本番と同じマイナー版で確かめたい移行テスト。コンテナで使い捨てのDBを作る方法はTestcontainersとは?Dockerで作る使い捨てテスト環境と2.0系の移行点で解説しています。
py-pgliteによるPythonテストと依存環境
PythonからPGliteを使うライブラリが py-pglite です。PyPIでの説明は「Python testing library for PGlite」で、用途はpytestで使う一時的なPostgreSQLです。中身はPythonでPostgreSQLを再実装したものではありません。Python 3.14の仮想環境に0.5.3を入れて確かめると、次のように動いていました。
- 初回の起動時に作業ディレクトリで
npm installを実行し、Node.jsでPGliteとpglite-socketを起動してソケットで接続する。Node.jsとnpmが入っていないと使えません。 - 指定しているPGliteは
^0.3.0のため、PostgreSQLは17.4、pgvectorは0.8.0でした。0.5系のPGliteとは版が違います。 - Pythonは3.10以上が必要です(macOS標準の3.9では入りません)。最新の0.5.3は2025年9月17日公開で、それ以降の更新はありません。
pip install "py-pglite[extensions,sqlalchemy,psycopg]"
from sqlalchemy import text
from py_pglite import PGliteConfig
from py_pglite.sqlalchemy import SQLAlchemyPGliteManager
def test_vector():
cfg = PGliteConfig(extensions=["pgvector"])
with SQLAlchemyPGliteManager(cfg) as m:
with m.get_engine().connect() as c:
c.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))
d = c.execute(text("SELECT '[1,0]'::vector <=> '[1,1]'::vector")).scalar()
assert round(d, 4) == 0.2929
このテストは5.18秒で通りました。SQLAlchemyを入れているとpytestのフィクスチャ pglite_engine と pglite_session も使えます。本番のPythonアプリは通常のPostgreSQLとpgvectorに接続し、py-pgliteはそのテスト用、と分けて使うのが前提です。
pgvectorによる類似度検索の実装
ここからはPGliteにpgvectorを入れて、文章の意味の近さで検索する手順です。pgvector自体のインストールや仕様の全体像はpgvectorとは?インストール手順(Windows・Ubuntu・Docker)と使い方にまとめています。
拡張の有効化とvector列の作成
PGliteの拡張一覧のとおり、0.5系では別パッケージから vector をimportして extensions に渡します。
npm install @electric-sql/pglite @electric-sql/pglite-pgvector
import { PGlite } from '@electric-sql/pglite'
import { vector } from '@electric-sql/pglite-pgvector'
const db = await PGlite.create({ extensions: { vector } })
await db.exec(`
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE faqs (
id serial PRIMARY KEY,
question text,
category text,
embedding vector(1536)
);
`)
extensions を渡さずに CREATE EXTENSION vector を実行すると「extension “vector” is not available」になります。vector(N) の次元と違う長さを入れると「expected 3 dimensions, not 2」のように拒否されるため、Embeddingモデルの出力次元に合わせて列を定義します。
Embeddingモデルと次元数
OpenAIのAPIで文章をベクトルにする場合、OpenAIのEmbeddingsガイドによると既定の次元は text-embedding-3-small が1536、text-embedding-3-large が3072です。dimensions パラメータで短くでき、このパラメータはtext-embedding-3以降のモデルだけが受け付けます。text-embedding-ada-002 もモデル一覧に残っていますが、次元を変えられないため新しく作るなら3系を選びます。テキストを外部に送れない場合やブラウザで完結させたい場合は、Transformers.jsでローカルのモデルを動かせばEmbeddingの生成から検索までPGliteと同じページの中で済みます。日本語に強いモデルの選び方は埋め込みモデルとは?仕組みと日本語モデルの選び方を参照してください。
距離演算子の選び方
pgvectorの距離演算子は次の4つが中心です(ほかにバイナリベクトル用の <~> ハミング距離と <%> ジャッカード距離があります)。PGlite 0.5.13とpgvector 0.8.7で、[1,0,0] を基準に a=[1,0,0]・b=[0.9,0.1,0]・e=[2,0,0] を比べた結果を並べます。
| 演算子 | 指標 | a | b | e(向き同じ・長さ2倍) |
|---|---|---|---|---|
<=> |
コサイン距離 | 0 | 0.0061 | 0 |
<-> |
ユークリッド距離(L2) | 0 | 0.1414 | 1.0000 |
<#> |
負の内積 | -1.0000 | -0.9000 | -2.0000 |
<+> |
L1距離 | 0 | 0.2000 | 1.0000 |
コサイン距離だけが、向きが同じで長さが違う e を a と同じ「距離0」として扱います。文章のEmbeddingは向きに意味が表れるため、文章の検索では <=> を使います。<#> は小さいほど近くなるよう内積の符号を反転させた値で、長さの大きい e が先頭に来ます。OpenAIのEmbeddingのように長さ1に正規化されたベクトルなら、<#> とコサインで並び順は同じになります。演算子の意味を逆に書いた解説もあるため、pgvectorのREADMEで確認してください。計算式の背景はコサイン類似度とは?計算式・仕組みと類似検索での実装で解説しています。
類似度検索SQLとカテゴリでの絞り込み
以下は検索部分の抜粋です。文書を埋め込んでfaqsへ登録し、同じモデル・同じ次元数で作った検索文の数値配列をqueryEmbeddingに設定してから実行します。検索は距離の小さい順に並べます。類似度として表示するなら 1 - 距離 にします。
const q = JSON.stringify(queryEmbedding) // '[0.012,-0.034,...]'
const { rows } = await db.query(
`SELECT id, question, 1 - (embedding <=> $1) AS similarity
FROM faqs
WHERE category = $2
AND embedding <=> $1 < 0.35
ORDER BY embedding <=> $1
LIMIT 3`,
[q, '返品']
)
WHEREの < 0.35 は「十分に似た候補が無ければ0件にする」ための閾値です。適切な値はモデルとデータで変わるので、実際の問い合わせ文を数十件流して決めます。HNSWを追加した場合はカテゴリ条件で候補が減るため、全件走査の結果と比較し、必要に応じて hnsw.ef_search や反復スキャン(hnsw.iterative_scan)を調整します。キーワードの完全一致に弱い点を全文検索で補う設計は、ハイブリッド検索とは?RAGでBM25とベクトル検索を統合する仕組みで扱っています。
PGliteのHNSW性能測定と導入判断
pgvectorの近似検索インデックスHNSWはPGliteでも作れますが、構築には時間がかかります。384次元のランダムなベクトル1万件をメモリ上に置き、同じ検索ベクトルで各検索を3回実行した結果です(Intel Mac・Node.js 26.5.0・PGlite 0.5.13)。表の検索時間は3回の最小値〜最大値で、構築時間は1回の測定値です。
| 処理 | 時間 |
|---|---|
| インデックスなし(全件走査)の上位5件検索 | 21〜71ミリ秒 |
| HNSWインデックスの構築(vector_cosine_ops) | 約45秒 |
| 参考:同じPCのネイティブのPostgreSQL 18.6での構築 | 約2.1秒 |
| HNSWを使った上位5件検索 | 4〜22ミリ秒 |
1万件ではインデックスなしでも100ミリ秒を切りました。HNSWの構築は、maintenance_work_memが同じ64MBのネイティブのPostgreSQL 18.6(pgvector 0.8.7)より約20倍遅くなりました。約45秒はNode.jsでの測定値です。ブラウザのメインスレッドで構築するとその間ページの操作を妨げるため、重い処理はPGliteWorkerに移します。
インデックスを張るかどうかは件数で一律に決めず、実際の次元数・端末・検索頻度で全件走査の時間を測り、要求する応答時間を超えたときに検討します。通常のPostgreSQLへ移すかどうかも、応答時間、メモリ使用量、同時利用、運用管理の要件で判断します。張る場合は、pgvectorのREADMEにあるとおり vector 型のHNSWは2,000次元までです。3072次元のモデルを使うなら dimensions で次元を減らすか halfvec を検討します。検索で使う演算子とインデックスの演算子クラスも揃えます(<=> なら vector_cosine_ops)。揃えないとインデックスは使われません。HNSWのパラメータの決め方はHNSWとは?階層グラフの仕組みとM・ef_constructionの決め方を参照してください。
CREATE INDEX ON faqs USING hnsw (embedding vector_cosine_ops);
PGliteを使うべきでない場面
PGliteは、1つのプロセスから1本の接続で使うデータベースです。次の条件のどれかに当てはまるなら、最初から通常のPostgreSQL(Dockerやマネージドサービス)にします。
- 複数のアプリサーバーやワーカーから同じデータに読み書きする(pglite-serverでも処理は1本ずつ順番になる)。
- サーバー側で複数利用者のデータを共有し、自動フェイルオーバーやレプリケーションを必要とする。PGliteにはダンプと再読込のAPIがありますが、バックアップの保存・復元手順はアプリ側で設計する必要があります。
- 同時更新・ロック・デッドロックの挙動を確かめるテスト。接続が1本なので再現できません。
- 必要なPostgreSQL拡張をPGlite向けにビルド・動作確認できない。標準で提供される拡張は拡張一覧にあるものだけです。
反対に、PostgreSQLの機能(JSONB・全文検索・pgvector)をそのまま使える組み込みDBが欲しい場面では、SQLiteより本番との差が小さくなります。ブラウザのPGliteとサーバーのPostgreSQLを同期させたい場合は、同じ開発元のElectricSQLが組み合わせの候補です。社内文書の検索やRAGを本番の規模で組む段階では、RAG構築支援のように、PostgreSQLとpgvectorの設計から相談する選択肢もあります。
よくある質問
PGliteはDockerのPostgreSQLの代わりになりますか?
ローカル開発・単体テスト・ブラウザ内のアプリなら代わりになります。アプリから直接importするか、pglite-server を起動して通常のPostgreSQLとして接続します。ただし接続は順番に処理されるため、同時接続やロックの検証、本番用途には使えません。
PythonでPGliteは使えますか?
py-pgliteで使えます。用途はpytestのテストで、内部でNode.jsとnpmを使ってPGliteを起動するため、Node.jsのインストールが必要です。0.5.3が動かすのはPGlite 0.3系(PostgreSQL 17.4・pgvector 0.8.0)です。
@electric-sql/pglite/vector のimportがエラーになるのはなぜですか?
PGlite 0.5.0以降はpgvectorが別パッケージになったためです。npm install @electric-sql/pglite-pgvector を実行し、import { vector } from '@electric-sql/pglite-pgvector' に書き換えます。pglite-serverの --extensions も @electric-sql/pglite-pgvector:vector と書きます。
PGliteとSQLiteはどう違いますか?
どちらも組み込みのデータベースですが、PGliteの中身はPostgreSQL 18.6そのものです。本番がPostgreSQLなら、スキーマ・SQL・pgvectorの検索をそのままローカルで動かせます。SQLiteでベクトル検索をするにはsqlite-vecなど別の拡張を使い、SQLの書き方も変わります。
PGliteは本番環境で使えますか?
サーバー側の本番データベースとしては使えません。1本の接続しか持たず、複数プロセスからの共有やレプリケーションの仕組みがないためです。ブラウザやデスクトップアプリの中で、利用者ごとにローカルのデータを持つ用途なら本番でも使えます。