ElectricSQLは、PostgreSQLのデータの一部を「Shape」という単位で切り出し、HTTP経由でブラウザやモバイルアプリへ流し続ける同期エンジンです。データベースそのものではありません。日本語の解説にはSQLiteとの双方向同期やCRDTによる競合解決を中心に説明するものが残っていますが、その構成は2024年7月の作り直しで廃止されています。この記事では2026年9月時点の最新版(同期サービス1.8.1)を基準に、仕組み、導入手順、書き込みと認証の設計、2026年8月のDatabricks合流までを公式ドキュメントとリリース情報から整理します。
まとめ
- ElectricSQLとして知られる同期エンジン(現行の製品ページではPostgres Sync)は、Postgresの論理レプリケーションで変更を受け取り、Shape単位でクライアントへ配信する読み取り専用の同期エンジンです。書き込みはアプリ側のAPIが担います。
- 旧版のSQLite双方向同期・CRDT・DDLXは2024年7月に作り直しで外れ、2025年3月17日に1.0が正式版になりました。2026年9月7日時点の最新版は同期サービス1.8.1です。
- 導入の条件は、PostgreSQL 14以上、
wal_level=logical、REPLICATION属性を持つロール、論理レプリケーションに対応する接続経路の4点です。接続は原則としてPostgresへの直接接続を使います。 - Electricはデータベースの中身をそのまま配信するため、本番では
ELECTRIC_SECRETを設定し、認可はアプリ側のプロキシで行います。 - 2026年8月11日にElectricのDatabricks合流が発表されました。オープンソース版は継続し、ホスティングのElectric Cloudは終了に向かっています。
以下、旧版との違いから順に説明します。
ElectricSQLの旧版と現行版:同期方向と提供機能の違い
「ElectricSQL」で検索して見つかる情報は、2024年7月より前の旧版と現行版が混ざっています。どちらの説明かを先に見分けないと、存在しないAPIで設計を始めることになります。
2024年の作り直しで外れた機能の対照表
開発元は2024年7月17日のブログで、同期エンジンを一から作り直す方針(当初の名称はElectric Next)を発表しました。理由として、旧版は範囲が広すぎて安定させられず、利用者がDockerのネットワークやマイグレーションツール、クライアント側のビルドツールの問題に時間を取られていたと説明しています。
| 項目 | 旧版(0.x、2024年7月まで) | 現行版(1.x) |
|---|---|---|
| 同期の方向 | Postgresとクライアントの双方向 | Postgresからクライアントへの読み取りのみ |
| クライアント側の保存先 | SQLite、PGliteなど | 指定なし(メモリ、PGlite、TanStack DBなど) |
| 通信方式 | WebSocket(Satellite) | HTTP(キャッシュ可能) |
| 競合解決 | CRDTで自動 | 書き込みを受けるAPI側で設計 |
| 認可 | DDLXによる権限制御を構想(未実装) | プロキシなど自前のHTTP認可 |
| npmパッケージ | electric-sql(非推奨) |
@electric-sql/clientほか |
旧版のコードはelectric-sql/electric-oldリポジトリ、ドキュメントはlegacy.electric-sql.comに残っていますが、開発元は旧版をサポートしないと明言しています。npmのelectric-sqlパッケージ(最終版0.12.1)にも「同期エンジンを作り直した」という非推奨メッセージが付いています。ALTER TABLE ... ENABLE ELECTRICや型付きクライアントの生成コマンドが出てくる解説は旧版のものなので、そのまま使えません。
1.0正式版から1.8までのリリース経緯
作り直した版は2024年12月10日にBETA、2025年3月17日に1.0.0となり、開発元は「APIは安定し、ミッションクリティカルな本番アプリで使える」と宣言しました。1.0の発表では、一般的なPostgres 1台で100万の同時接続クライアントまで遅延とメモリ使用量が横ばいだったというクラウドでのベンチマーク結果と、Trigger.devなどの本番利用例が挙げられています。
リリースは週単位で続いています。同期サービスは2026年9月1日に1.8.0、9月7日に1.8.1が出ており、TypeScriptクライアント@electric-sql/clientは1.5.28、Reactフック@electric-sql/reactは1.0.57です(いずれも2026年9月9日公開)。1.8.0にはテレメトリのメトリクス名の接尾辞をマイクロ秒記号μsからASCIIのusへ変える破壊的変更が入っているため、監視ダッシュボードを組んでいる場合は更新前に確認が必要です。
2026年のDatabricks合流とElectric Cloudの終了
開発元は2026年4月29日にAIエージェント向けのElectric Agentsを発表し、GitHubリポジトリの説明も「The agent platform built on sync.」に変わりました。ドキュメントはelectric-sql.comからelectric.axへ移っています。
さらに2026年8月11日、ElectricはDatabricksに合流し、PostgresのLakebaseにデータ基盤と同期の仕組みを加えると発表しました。合流先は、2025年にDatabricksが買収したサーバーレスPostgresのNeonのチームです。発表では、Postgres Sync(この記事で扱う同期エンジン)、PGlite、TanStack DB、Durable Streamsは既存のライセンスのままオープンソースを続けるとしています。一方で、ホスティングサービスのElectric Cloudは終了に向かっており、利用者はセルフホストか他社への移行が必要です。新しく採用するなら、自前で同期サービスを運用する前提で評価してください。
Shapeを単位にした同期の仕組み
Electricが解決するのは「Postgresの一部のデータを、多数のクライアントへ、最新の状態のまま届ける」という問題です。部分レプリケーション、配信先への振り分け、データの配送を同期サービスが引き受けます。
論理レプリケーションからHTTPのShapeログまでの流れ
同期サービスはElixirで書かれたWebサービスで、Dockerイメージとして配布されています。Postgresには論理レプリケーションで接続し、WAL(ライトアヘッドログ)に書かれた変更を受け取ります。受け取った変更は、クライアントが要求しているShapeの条件に一致するものだけがShapeごとのログに追記され、クライアントはHTTPでそのログを読み進めます。データベースの変更を外部へ流すという点ではCDC(Change Data Capture)と同じ系統の技術ですが、配信先がKafkaなどではなく、エンドユーザーのアプリである点が違います。
ShapeはSQLのWHERE句で絞った、1テーブル分の部分レプリカです。基本はtable、where、columnsの3つで定義します。columnsを指定する場合は主キーを必ず含めます。
/v1/shapeのパラメータとライブモード
ShapeはHTTPのGET /v1/shapeで取得します。最初のリクエストはoffset=-1でログの先頭から取得を始めます。データが大きい場合は複数の応答に分かれるため、応答ヘッダーのelectric-handle(Shapeの識別子)とelectric-offset(次に読む位置)を次のリクエストに付けて続きを読みます。
# 初回同期:todosテーブルを先頭から取得
curl -i 'http://localhost:3000/v1/shape?table=todos&offset=-1'
# 追いついた後:handleとoffsetを付けてライブモードで待機
curl -i 'http://localhost:3000/v1/shape?table=todos&live=true&handle=HANDLE&offset=OFFSET'
live=trueを付けると、サーバーは新しい変更が届くまで接続を保持し(ロングポーリング)、タイムアウトした場合はup-to-dateのメッセージだけを返します。ロングポーリングの代わりにServer-Sent Eventsを使うlive_sseもあります。制御メッセージには、部分スナップショットの終了を示すsnapshot-endなどもあります。通常の同期で扱うup-to-dateは「サーバーが把握していた全データを受け取った」、must-refetchは「手元のデータを捨てて最初から同期し直す」を意味します。クライアントライブラリはこれらを自動で処理するため、HTTPを直接叩くのは動作確認やクライアントの自作に限られます。
導入手順:PostgreSQLの要件確認からShape取得まで
同期サービスを起動する前に、Postgresの論理レプリケーション設定と接続ロールの権限を確認します。
PostgreSQL側の4つの要件
公式のデプロイガイドが挙げる要件は、PostgreSQL 14以上であること、論理レプリケーションが有効(wal_level=logical)であること、REPLICATION属性を持つロールで接続すること、の3点です。4点目は接続経路で、原則として接続プーラーを経由しない直接接続を選びます。多くの接続プーラーは論理レプリケーションに対応していないためです。公式ガイドはPgBouncerが1.23から論理レプリケーションに対応したことにも触れていますが、推奨はあくまで直接接続です。レプリケーション以外の問い合わせだけをプーラーに向けたい場合はELECTRIC_POOLED_DATABASE_URLを別に指定します。
-- 現在の設定を確認(logical 以外なら変更が必要)
SHOW wal_level;
-- 変更はサーバーの再起動後に反映される
ALTER SYSTEM SET wal_level = 'logical';
-- 同期サービス用のロール
CREATE ROLE electric WITH LOGIN REPLICATION PASSWORD 'change-me';
マネージドPostgresの多くはwal_level=logicalが既定で無効です。SQLではなく管理画面のパラメータ設定で変更するサービスもあるため、各サービスの手順に従ってください。publicationの作成などロールに追加で与える権限は構成によって変わるので、公式のPostgreSQL Permissionsガイドで自分の構成に合うものを選びます。
Dockerでの同期サービス起動と動作確認
同期サービスはelectricsql/electricイメージで起動し、既定のポートは3000です。公式手順のlatestタグは更新のたびに中身が変わるため、ここでは版を固定しています。
docker run \
-e "DATABASE_URL=postgresql://electric:[email protected]:5432/app" \
-e "ELECTRIC_INSECURE=true" \
-p 3000:3000 \
-t electricsql/electric:1.8.1
ELECTRIC_INSECURE=trueはシークレットなしで動かす設定です。公式はネットワーク等でアクセスを制限した本番利用にも言及していますが、本記事では開発用途に限定します。本番ではELECTRIC_SECRETを設定し、同期サービスが書き出すShapeログの保存先ELECTRIC_STORAGE_DIR(既定は./persistent)を再起動後も消えない永続ボリュームに置きます。起動後は、接続先にtodosテーブルが存在し、同期用ロールに必要な権限があることを確認してから、前章のcurlを実行します。さらに行を追加・更新し、その変更がライブ同期で届くことを確認します。PostgresごとDockerで試したい場合は、公式が配布するdocker-compose.yamlを取得してdocker compose upで起動する方法もあります。
Supabaseにつなぐときの直接接続URLとIPv6
SupabaseのPostgresは論理レプリケーションが有効で、Electricに必要な権限もそろっています。注意点は接続URLです。プール経由のURLは論理レプリケーションに対応していないため、Supabaseの接続設定で接続プーラーの表示をオフにし、直接接続のURLを使います。この直接接続URLはIPv6専用なので、Electric側にELECTRIC_DATABASE_USE_IPV6=trueを設定します。実行環境がIPv6を使えない場合は、SupabaseのProまたはTeamプランでIPv4アドオンを有効にする方法があります。
TypeScript/ReactクライアントでのShape購読
アプリからは@electric-sql/clientを使います。ShapeStreamが変更メッセージの流れ、Shapeがそれを適用した最新の行の集合です。
import { ShapeStream, Shape } from '@electric-sql/client'
type Todo = { id: string; title: string; done: boolean }
const stream = new ShapeStream<Todo>({
url: 'http://localhost:3000/v1/shape',
params: {
table: 'todos',
where: 'project_id = $1',
params: ['p1'],
columns: ['id', 'title', 'done'],
},
})
const shape = new Shape(stream)
shape.subscribe(({ rows }) => {
// rows は条件に一致する行の最新状態
console.log(rows.length)
})
whereに値を埋め込まずparamsで渡すと、SQLインジェクションを避けられます。Reactでは@electric-sql/reactのuseShapeが同じ処理をフックにまとめています。
import { useShape } from '@electric-sql/react'
export function TodoList() {
const { data, isLoading } = useShape<Todo>({
url: new URL('/api/todos', location.origin).toString(),
params: { table: 'todos' },
})
if (isLoading) return null
return <ul>{data.map((t) => <li key={t.id}>{t.title}</li>)}</ul>
}
この例のurlが同期サービスではなくアプリの/api/todosを向いているのは、後述する認可用のプロキシを通すためです。urlには絶対URLを渡します。1.5.28のクライアントは/api/todosのような相対URLをそのままnew URL()に渡すため、ブラウザでもInvalid URLで同期が始まりません。どちらの例も@electric-sql/client 1.5.28と@electric-sql/react 1.0.57の型定義に対して型検査が通ることを確認しています。
書き込みの設計:4つのパターンとTanStack DBとの組み合わせ
公式ドキュメントは「Electricは読み取り側の同期を行い、書き込み側の同期は行わない」と明記しています。書き込みはアプリのAPIを通してPostgresに入れ、その結果がShape経由でクライアントに戻ってくる、という一方向の流れで設計します。公式ガイドは実装の重さの順に4つのパターンを示しています。
| パターン | オフライン書き込み | 向く用途 |
|---|---|---|
| 1. オンライン書き込み | 不可 | 閲覧中心のアプリ、ダッシュボード |
| 2. 楽観的状態 | 可(リロードで消える) | 回線が不安定なモバイル |
| 3. 共有・永続の楽観的状態 | 可 | SaaS、共同作業ツール |
| 4. ローカルDB経由の同期 | 可 | ローカルファースト、デスクトップ |
4番目はPGliteなどの組み込みDBに読み書きし、変更をバックグラウンドで送る方式です。アプリのコードはネットワークを意識しなくて済みますが、公式ガイド自身が、依存が重いこと、クライアント側のスキーマ定義が複雑になること、ロールバックの扱いが難しくなることを欠点に挙げています。まず1か2で始め、オフライン書き込みが要件になった時点で3に進むのが無理のない順序です。
2と3を自前で組む代わりに、Electricと共同開発されているTanStack DBを使う方法があります。@tanstack/electric-db-collectionのelectricCollectionOptionsでShapeを読み込み、書き込みハンドラーではAPIが返したトランザクションIDを返します。次の例は1件の挿入を想定し、Todo型には前の例の定義を使います。api.createは、行を保存して同じトランザクションのIDを返すアプリ側の関数として別途実装します。
import { createCollection } from '@tanstack/db'
import { electricCollectionOptions } from '@tanstack/electric-db-collection'
export const todos = createCollection(
electricCollectionOptions({
id: 'todos',
getKey: (item: Todo) => item.id,
shapeOptions: { url: new URL('/api/todos', location.origin).toString(), params: { table: 'todos' } },
onInsert: async ({ transaction }) => {
const res = await api.create(transaction.mutations[0].modified)
return { txid: res.txid }
},
})
)
返したトランザクションIDの変更がShapeで届いた時点で、楽観的に表示していた行が同期済みの行に置き換わります。ここで失敗しやすいのがトランザクションIDの取得場所です。TanStack DBのドキュメントによると、pg_current_xact_id()を書き込みと同じトランザクションの内側で取得しないとIDが一致せず、確定待ちがいつまでも終わりません。
認証とセキュリティ:Electricを直接公開しない構成
公式のデプロイガイドは、Electricが既定ではデータベースの中身へ公開アクセスを許す状態になると警告しています。ブラウザから同期サービスへ直接つながせる構成は取らず、アプリのAPIを前段に置きます。公式が推奨する方式は2つです。
- プロキシ認可:Shapeのリクエストをアプリのプロキシで受け、ユーザーを認証し、
tableとwhereをサーバー側で決めてから同期サービスへ転送する。 - ゲートキーパー認可:先にアプリのAPIでShape専用のトークンを発行し、以降のShapeリクエストではプロキシがそのトークンだけを検証する。認可の判定にDB検索が要る場合に向く。
プロキシ認可の要点を、公式サンプルをもとにWeb標準のRequest/Responseで書くと次のようになります。
import { ELECTRIC_PROTOCOL_QUERY_PARAMS } from '@electric-sql/client'
export async function GET(request: Request) {
const url = new URL(request.url)
const origin = new URL('/v1/shape', process.env.ELECTRIC_URL ?? 'http://localhost:3000')
// クライアントからは同期プロトコル用のパラメータだけを通す
url.searchParams.forEach((value, key) => {
if (ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) origin.searchParams.set(key, value)
})
const user = await authenticate(request) // 自前の認証処理
if (!user) return new Response('unauthorized', { status: 401 })
// テーブルと絞り込み条件、シークレットはサーバー側で付ける
origin.searchParams.set('table', 'todos')
origin.searchParams.set('where', '"org_id" = $1')
origin.searchParams.set('params[1]', user.orgId)
origin.searchParams.set('secret', process.env.ELECTRIC_SECRET ?? '')
const res = await fetch(origin, { signal: request.signal })
const headers = new Headers(res.headers)
headers.delete('content-encoding')
headers.delete('content-length')
headers.append('Vary', 'Authorization, Cookie')
return new Response(res.body, { status: res.status, headers })
}
ELECTRIC_PROTOCOL_QUERY_PARAMSにはoffset、handle、liveなどが入っており、tableやwhereは含まれません。クライアントが別のテーブル名を送ってきても転送されないため、見せてよい範囲をサーバー側で固定できます。content-encodingとcontent-lengthを消しているのは、fetchが本文を展開した後もこれらのヘッダーが残り、ブラウザ側で解凍に失敗するのを防ぐためです。
採用判断:ElectricSQLが向く構成と向かない構成
Electricが力を発揮するのは、Postgresが正本で、読み取りが書き込みより圧倒的に多く、画面を常に最新に保ちたいアプリです。管理画面、ダッシュボード、チームで共有するタスクや案件の一覧が典型で、APIを都度呼ぶ代わりにローカルのShapeから描画するため、画面遷移のたびに通信を待つ必要がなくなります。HTTPで配信するのでCDNやキャッシュ用プロキシを前に置けるのも、WebSocket型の仕組みにない利点です。
次の条件に当てはまる場合は採用を見送るか、別の選択肢を先に検討してください。
- オフライン中の書き込みをPostgresとの間で自動マージしたい:現行のElectricは書き込み側を担わず、競合解決は自分で設計することになります。旧版の双方向同期を期待して選ぶと、要件を満たせません。
- ホスティングに任せたい:Electric Cloudは終了に向かっており、同期サービスと永続ストレージを自前で運用する体制が前提になります。
- 論理レプリケーション非対応の接続プーラー経由でしかPostgresにつなげない:直接接続など、論理レプリケーションに対応する接続経路を用意できなければ動きません。
- 通知やチャットのように、変更イベントを受け取れば十分:行の集合をクライアントに保持する必要がないなら、Supabase Realtimeのようなイベント配信の仕組みで足ります。
よくある質問
ElectricSQLとSupabaseは一緒に使えますか?
使えます。SupabaseのPostgresは論理レプリケーションが有効で、Electricに必要な権限もそろっています。同期サービスはSupabaseの外(自前のサーバーやコンテナ基盤)で動かし、接続にはプール経由ではなく直接接続のURLを使います。直接接続URLはIPv6専用なので、ELECTRIC_DATABASE_USE_IPV6=trueを設定するか、ProまたはTeamプランのIPv4アドオンを使います。行の同期はElectric、認証はSupabase Authという分担も可能ですが、その場合もShapeのリクエストは自前のプロキシで認可します。
ElectricSQLはSQLiteと同期できますか?
SQLiteとの同期は2024年7月に廃止された旧版の機能で、現行版のクライアントはSQLiteを前提にしていません。Shapeで受け取った行をどこに保持するかはアプリ側で選びます。ブラウザ内でSQLを使いたい場合は、同じ開発元のWASM版PostgresであるPGliteと、Shapeを取り込むための@electric-sql/pglite-syncパッケージを組み合わせる構成が公式に用意されています。SQLiteでの保持は、Shapeの行を自分で書き込む処理を作れば可能です。
ElectricSQLはCRDTで競合を解決しますか?
現行版は解決しません。CRDTによる競合解決は旧版の機能です。現行のElectricは読み取り側の同期だけを行い、書き込みはアプリのAPIを経由してPostgresに入るため、同じ行への同時更新をどう扱うかはAPIとPostgresのトランザクションで決めます。共同編集のテキストのようにCRDTが必要なデータには、Yjsと組み合わせるための@electric-sql/y-electricパッケージが別に公開されています。
ElectricSQLは無料で使えますか?ライセンスは?
同期エンジンはApache License 2.0のオープンソースで、セルフホストすればライセンス費用はかかりません。2026年8月11日の発表でも、Postgres Sync、PGlite、TanStack DB、Durable Streamsは既存のライセンスのまま公開を続けるとしています。有料だったのはホスティングのElectric Cloudで、こちらは終了に向かっているため、新規の採用ではセルフホストの運用費(同期サービスのサーバーと永続ストレージ)を見積もってください。
ElectricSQLのGitHubリポジトリと公式ドキュメントはどこですか?
ソースコードはGitHubのelectric-sql/electricで、同期サービス、TypeScriptクライアント、Reactフック、サンプルが同じリポジトリにあります。2026年9月15日時点のスター数は、GitHub APIの値で10,361です。公式ドキュメントはelectric.axに移っており、旧ドメインのelectric-sql.comからは転送されます。legacy.electric-sql.comは廃止された旧版の資料なので、現行版の実装には使わないでください。