HN公開: Triplit - サーバーとクライアントで動作するオープンソースの同期データベース
(github.com/aspen-cloud)- Triplitは、サーバーとブラウザ間でデータをリアルタイム同期するオープンソースのデータベースで、アプリにTypescriptパッケージとして組み込んで使うフルスタックデータベースを掲げている
- サーバー保存とクライアントクエリの同期をまとめて処理し、増分アップデート、属性単位の競合解決、ローカルキャッシュ、オフラインモード、自動再接続をサポートする
- SQLite、IndexedDB、LevelDB、Memoryなどのプラガブルストレージをサポートし、サーバー側の永続ストレージと管理ダッシュボードを提供する
- Reactとvanilla Javascriptでクエリ・変更APIを利用でき、React・Svelteバインディング、CLI、Console、Serverなどで構成されるモノレポとして提供される
- 高速なインタラクションのための楽観的更新、失敗した更新のロールバック・リトライ、サーバーで強制される読み取り・書き込み権限、CRDTベースのコラボレーション機能も提供する
Triplitが提供するもの
- Triplitは、サーバーとブラウザ間でデータをリアルタイム同期するオープンソースのデータベース
- アプリにTypescriptパッケージとして追加できる同期データストアを提供する
- サーバーにデータを保存し、クライアントのクエリを賢く同期する
- Triplitはこの形態をフルスタックデータベースと呼んでいる
- Local Firstコミュニティでの発表動画も提供されている: presentation
主な機能
-
リアルタイム同期
- 増分アップデートをサポートする
- 属性単位の競合解決を提供する
-
ローカルファーストの使い勝手
- 完全なクライアント側データベースに基づくローカルキャッシュを提供する
- 楽観的更新により、すべてのインタラクションを高速に感じられるようにする
- オフラインモード、自動再接続、一貫性保証をサポートする
-
サーバー保存と運用
- サーバー側の永続ストレージを提供する
- 管理ダッシュボードを含む
- SQLite、IndexedDB、LevelDB、Memoryなどのプラガブルストレージプロバイダーをサポートする
-
データモデルとAPI
- 複雑なデータモデル向けのリレーショナルクエリをサポートする
- スキーマを通じてデータ安全性とTypescriptの自動補完を提供する
- vanilla JavascriptとReactで、クエリ・変更のためのシンプルなAPIを提供する
-
コラボレーションとセキュリティ
- サーバーで読み取りと書き込みの両方について権限を強制する
- CRDTsベースのコラボレーション・マルチプレイヤー機能を提供する
- デルタパッチを使ってネットワークトラフィックを減らし、低レイテンシを目指す
- 失敗した更新に対してロールバックとリトライを管理する
モノレポ構成
- TriplitDB: ブラウザ、Node、Deno、React NativeなどのJS環境で動作するよう設計されたDBで、ネットワーク上の複数ライターと一貫性を保ちながら、高速でライブ更新されるクエリを提供する
- Client: ローカル・リモートのTriplitDBとやり取りするブラウザライブラリ
- CLI: プロジェクトのスキャフォールディング、フルスタック開発環境の実行、サーバーマイグレーションなどのためのコマンドラインツール
- React:
@triplit/client用Reactバインディング - Svelte:
@triplit/client用Svelteバインディング - Console: Triplitプロジェクトのデータを表示・変更し、スキーマを管理するアプリ
- Server: Triplitクライアント間でデータを同期するNodeサーバー
- Server-core: Triplitサーバーを作るためのプロトコル非依存ライブラリ
- Docs: Nextraで作られたTriplitドキュメント
- Types: Triplitプロジェクト群が共有する型
- UI: shadcnベースの共有UIコンポーネント
クイックスタートの流れ
- 新規プロジェクトは
npm create triplit-app@latest my-appで開始する - 既存プロジェクトには
@triplit/cliを開発依存関係としてインストールしたうえで、npm run triplit initを実行する my-app/triplit/schema.tsにスキーマを定義する- 例では
todosコレクションにid、text、completedフィールドを定義する completedはデフォルト値がfalseのBooleanフィールドとして設定される
- 例では
npm run triplit devで開発用の同期サーバーを起動する- 開発サーバーは、アプリがサーバーと同期するために必要な環境変数を出力する
- Viteの例では
VITE_TRIPLIT_SERVER_URL=http://localhost:6543 VITE_TRIPLIT_TOKEN=copied-in-from-triplit-dev
- Viteの例では
Reactの使用例と同期確認
- Reactの例では
TriplitClientとuseQueryを使用する - クライアントはスキーマ、サーバーURL、トークンを受け取って作成される
useQuery(client.query('todos'))でtodosクエリの結果を購読する- チェックボックス変更時に
client.updateでcompleted値を反転する - アプリを起動したあと別のブラウザタブを開くと、データがリアルタイム同期されることを確認できる
ドキュメントと連絡チャネル
- 全体の開始ガイド: getting started guide
- より詳しいチュートリアル: building a real-time todo app with Triplit, Vite, and React
- 質問、導入サポート、新機能のプレビューはDiscordで受けられる
- 最新の発表はTwitter/Xで確認できる
1件のコメント
Hacker News のコメント
Triplitをプロジェクト https://github.com/thanhnguyen2187/cryptaa で使ってみたが、期待どおりに動作した
データモデルは、単一の中央DBを真実の源泉とするよりも、より分散型/P2Pに近い構想によく合っている。ただし、セルフホスティングとクエリ言語には不満がある
サーバー認証トークンの生成方法がドキュメントでは明確でなかったため、CLIの
devコマンドでトークンを作成した。システムサービスでトークンが平文ログに残るのはセキュリティ上望ましくないが、より大きなアクセス権限の問題が前提になるとは思うカスタムのクエリDSLはSQLのように
UNIQUE、COUNTといった表現力が足りず、一部の集計を自前で行う必要がある最近 Evolu https://www.evolu.dev/docs を見たが、スコープと機能が似ているように見える。Triplitには
.subscribe()があり、Evoluにはない。EvoluはKyselyベースの型付きSQLなのでクエリがより馴染みやすく高度で、ブラウザではEvoluがOPFS上のSQLiteを使う一方、TriplitはIndexedDBを使っているようだRedditに投稿した記事: https://www.reddit.com/r/sveltejs/comments/1dndpj8/cryptaa_a...
クエリについてはまだ集計がないがロードマップにはあり、インクリメンタルクエリエンジンを活用すれば面白い方向に進めると考えている
たとえば1時間ごとに更新されるデータダッシュボードは、既存のシステム(Postgres、MongoDBなど)では毎回クエリを最初から再実行する必要があるが、Materializeに近い方法で新しいデータだけを処理すれば、はるかに効率よく更新し続けられる
Evoluはまだ直接使ってみていないが、Discordで比較した人がいるかもしれない: https://triplit.dev/discord
useQueryや分離された方式で subscribe をサポートしているこうした優れたオフライン同期プロトコルを持つDBを使うとき、異なるクライアントバージョンを同時にアップグレードできない状況で、スキーマの進化をどう扱うのか気になる
以前、モバイルのヘルスアプリでこの問題に苦労した背景がある
必要であれば、2つのバージョンに同時に書き込む二重書き込みを行うことになる
SQL DBの互換性を壊す変更を無停止でライブマイグレーションするのと似ているが、切り替え時点が顧客次第なので、そのロジックをより長く維持する必要がある
最新バージョンを調整するテーブルも重要で、破壊的変更がある場合は、遅れているクライアントにユーザーへアップグレードを促させるべきだ
全クライアントでサポートする最小バージョンと連動させて、二重読み取り/書き込みをどれだけ維持するかも決められる
Triplitは後方互換でない変更を作ると警告を出し、ドキュメントで確認できる: https://www.triplit.dev/docs/schemas/updating#pushing-the-sc...
ただし時間がたつと、紛らわしい名前の多い雑然としたスキーマ定義が自然に生まれがちだ
これを修正するソリューションはまだリリースしていないが、苦痛を減らすためのいくつかの取り組みを進めている。さまざまなアプローチの背景としてはCambriaの文書が素晴らしい: https://www.inkandswitch.com/cambria/
ユーザーがスマートフォンを2年間引き出しに入れたままにしていた可能性もあるので、各クライアントができるだけ早く自分自身をマイグレーションすればよい
誰もがそれぞれ独自のマイグレーション管理方法を新たに作る必要がなくなる
クライアントがDBに直接書き込めることがどんなアプリで許容されるのか、そしてバックエンドロジックなしでどう耐えられるのか、よく理解できない
SupabaseとFirestoreについても同じ疑問があり、何かを見落としている気がする
企業環境では当然その逆だが、それを無視した議論を見るともどかしい
特にテック系Twitterで特定のスタックや作業方法を擁護する内容を見ると、ビジネスシステムを作ったことがなくCRUDだけを作ってきたのがあまりに明らかで、経験豊富な開発者がなぜ同意しないのか理解していないことが多い
バックエンドロジックが多いものにはあまり向いていないと思う
Supabaseには、たとえば行レベルセキュリティという機能がある
クライアントがSupabaseへリクエストを送ることはできるが、Supabaseがバックエンドで追加クエリを実行し、入ってきたリクエストが許可されるかを判断する
簡単な例として、
UserIDカラムの値がリクエストした認証済みユーザーと同じ場合に限り、その行を読み書き・更新できるようにできるユーザー設定を Triplit に保存してきており、この設定は管理者が管理できる必要があった
ユーザーにはアプリが常にローカルで動いているように感じられるべきで、インターネット品質もしばしば良くない一方、複数のデバイスを行き来して使い、管理者が他ユーザーの設定を見て管理する必要があったため、同期が必要だった
全体として Triplit はフロントエンド開発者体験もサポートも非常に優れており、Issue や機能リクエストを見つけるとチームが非常に素早く対応してくれる
高可用性デプロイについての答えが出れば、Postgres の代わりにより重要なデータも移す予定
なぜ AGPL ライセンスを選んだのか気になる
Local First Discord サーバー https://localfirstweb.dev/ で YouTube 発表 https://www.youtube.com/playlist?list=PLTbD2QA-VMnXFsLbuPGz1... を見た気がするので、Show HN で見かけてうれしい
TypeScript を使っていないので主な対象ではないかもしれず、Web と違って接続が不安定なモバイルアプリでローカルファーストを主に使っており、Flutter と Rust バックエンドを使用している
ElectricSQL や PowerSync のような他のローカルファーストソリューションは、クライアントとサーバー DB を直接同期するため、クライアント/サーバーに対してより独立している
CRDT ベースのソリューションも FFI でクライアントとサーバーから使える。例えば automerge は Rust なので、Flutter 側では
flutter_rust_bridge経由の FFI、Web では WASM、バックエンドでは Rust として使えるTriplit は、異なるクライアント間の競合なしの解決というより、サーバーを真実の源泉とする、より古典的なクライアント・サーバー同期に見える
なぜクライアントとサーバーにより独立した DB レイヤー方式ではなく、言語レベルのソリューションを選んだのか気になるし、今後 JS ベース以外の言語やフレームワークをサポートするのは難しそうに見える
また Supabase と競争しようとしているようだが、Supabase も Postgres の DB レベル同期と CRDT を実験中なので https://news.ycombinator.com/item?id=33931971 追いつく可能性もありそう
ただし出発点としては純粋な TypeScriptに集中することにした。市場が十分に大きく、PWA の未来を信じており、そこに集中してこそ最高の体験を作れると見ているため
いつかはもっとプラットフォーム非依存なものを作ることになると思うが、時期ははっきりしない
ElectricSQL と Supabase のチームはいずれも優秀で思慮深く、SQL 領域で成長し続けると思われるが、これがアプローチの最も根本的な違い
Triplit は SQL を避けることで開発者に最高の体験を提供できると考えており、2つの哲学が共存する余地は十分にある
LWW なら、クライアントの情報量が演算数に対して線形に増えるのか気になる
つまり、ユーザーが DB を多く変更するほど演算ログが増え続けるのか、それともチェックポイントを置くのか、ユーザーが1日に数百万回の演算を行う場合に容量面でどうスケールするのか気になる
ただし LWW レジスター自体が履歴保存を要求するわけではなく、長期間オフラインだったクライアントも効率的に同期できるようにするための現在の実装にすぎない
1日100万演算までは、まだ完全に到達したとは言いにくいが、サーバーが権限を持つ利点がある
将来的には Triplit サーバーが各クライアントの最終同期タイムスタンプを追跡し、Postgres が dead tuple を
VACUUM処理する方式に似た形で、履歴を段階的に枝刈りできるTauri で使えるように Rust バインディングがあるとよさそう
Tauri の成長、近く登場するモバイルデバイス対応、最近の SQLite 人気まで合わせると、オフラインファーストアプリの隙間を埋め、多くの開発チームの標準的な選択肢になり得る
ElectricSQL は DB レイヤーで動作するので言語非依存で、サーバーで Rust を使っているため、Rust バインディングがクライアントとサーバーの両方で動作できる
一緒に開発したければ知らせてほしい
それなら Triplit はそのまま動作するはず
React Native アプリでしばらく Triplit を使ってきており、とてもよく動く
強くおすすめするし、自分に必要な条件をすべて満たした唯一のローカルファースト DBだった
適切で sane なクエリ言語(SQL ではない)、優れた TypeScript サポート、オフライン対応、React Native 対応があり、オープンソースでセルフホスト可能な点もよい
既存の PostgreSQL DB と一緒に使うことはできないのか気になる
まだ公開できる準備は整っていないが、近いうちに人々が試せるようにしたい
自分もそちらを使う方向に傾いている