Convexでリアルタイム同期が驚くほど楽になった話
株式会社Bizmarq

チャットアプリ、共同編集、ライブダッシュボード。リアルタイム機能を作ろうとすると、私たちはいつも同じ作業を繰り返してきました。WebSocketサーバーを立て、接続管理と再接続ロジックを書き、Redis Pub/Subでメッセージブローカーを組み、クライアント側の状態同期を整え、スケールアウト時のセッション管理まで面倒を見る。ただデータを同期したいだけなのに、インフラ周りで時間を取られてしまう。
リアクティブバックエンドの「Convex」を試したところ、この部分がごっそり不要になりました。この記事では、実際に触ってわかった仕組みと使いどころを共有します。
Convexとは何か
Convexは、データベース・サーバー関数・リアルタイム同期が一体化したリアクティブなバックエンドプラットフォームです。私たちが便利だと感じたポイントは3つあります。
- TypeScriptだけで書ける — SQLもORMも使わず、クエリもミューテーションもTypeScriptで記述します。
- 自動でリアルタイム同期される — クライアントがクエリを購読すると、データが変わるたびに自動で再実行され、結果がプッシュされます。WebSocketの実装は書きません。
- ACIDトランザクションが保証される — 楽観的並行制御とシリアライザブル分離レベルを採用しており、データの不整合が起きにくい設計です。
実装のイメージ
リアルタイムなメッセージ一覧を例に、コードの要点だけ見ていきます。
まずスキーマ定義。テーブルとインデックスをTypeScriptで宣言します。
export default defineSchema({
messages: defineTable({
author: v.string(),
body: v.string(),
createdAt: v.number(),
}).index("by_created", ["createdAt"]),
});
次にクエリ関数。これだけでリアルタイムに購読できるクエリになります。SQLは一切書いていません。
export const list = query(async ({ db }) => {
return await db
.query("messages")
.withIndex("by_created")
.order("desc")
.take(50);
});
データを書き込むミューテーション関数も同じくTypeScriptで書きます。
export const send = mutation({
args: { author: v.string(), body: v.string() },
handler: async ({ db }, { author, body }) => {
await db.insert("messages", { author, body, createdAt: Date.now() });
},
});
最後にフロントエンド側。Reactなら useQuery で購読するだけです。
const messages = useQuery(api.messages.list);
const sendMessage = useMutation(api.messages.send);
useQuery で購読すれば、他のユーザーがメッセージを送った瞬間に画面が更新されます。WebSocketの接続管理もRedisも書いていません。ここが最大のポイントです。
裏側で起きていること
自動でリアルタイム同期される仕組みは、大まかに次の流れになっています。
- クライアントが
useQueryを呼ぶと、WebSocket経由で購読が始まる - Convexがクエリを実行し、どのテーブル・行に依存しているかを追跡する
- ミューテーションでデータが変わると、影響を受けるクエリを特定する
- 該当クエリを再実行し、購読中のクライアントへ差分をプッシュする
依存関係の追跡が自動なので、「どのクエリがどのデータに依存しているか」を自分で管理する必要がありません。自前でリアルタイム機能を組むと、この依存管理と状態同期こそが一番バグを生みやすい部分でした。そこを丸ごと引き受けてくれるのは大きな効きどころです。
従来手法との比較
私たちがこれまで取ってきた選択肢と並べると、役割分担が見えてきます。
- ポーリング — 実装は単純だが、データの鮮度がリクエスト間隔に依存する。fetchとタイマーの管理が増える。
- WebSocket自前 — リアルタイム性は高いが、スケール時にRedisやセッション管理が必要になり、socket.ioと状態管理のコードが増える。
- Convex — サーバー側はTypeScript関数のみ。スケーリングはマネージド。型定義も自動生成され、クライアントは
useQueryを呼ぶだけ。
WebSocketを自前で組むとスケール時のインフラ負担が重くのしかかりますが、Convexはそこがマネージドなので、アプリ本来のロジックに集中できます。
向いているケース、向いていないケース
万能ではありません。私たちの整理はこうです。
向いているのは、リアルタイムコラボレーション(ドキュメント共同編集やホワイトボード)、チャット・メッセージング、ライブダッシュボード、ターンベースゲームの状態同期、通知・アクティビティフィードなど。「複数ユーザーの状態を素早く揃えたい」用途と相性がよいです。
一方で、FPSのように極端な低レイテンシが求められるリアルタイムゲーム、既存のRDBMSに強く依存したシステムの移行、SQLを直接書きたいケースには不向きです。ここは無理にConvexへ寄せず、従来手法を選ぶべきだと考えています。
はじめ方
導入は数コマンドで完了します。
npm create convex@latest
React・Next.js・Vue・Svelteのテンプレートが用意されています。開発サーバーを起動すると、対象フォルダ内の変更が自動でデプロイされ、ホットリロードでバックエンドを開発できます。無料枠も月間100万関数呼び出し・ストレージ・帯域と、プロトタイプや小規模な本番なら十分に試せる範囲です。
まとめ
リアルタイム同期を自前で実装すると、WebSocketの接続管理・Pub/Sub・状態同期と、本来つくりたい機能以外のコードがどんどん増えていきます。Convexはその部分を丸ごと引き受け、TypeScriptで型安全に、ACIDトランザクションを保ちながら書けるのが魅力です。
私たちはプロダクトづくりで「本質でない作業をどれだけ減らせるか」を重視しています。リアルタイム機能を入れたいとき、Convexは選択肢として十分に検討する価値があると感じました。用途を見極めたうえで、小さく試してみることをおすすめします。