ウェブ界隈の低レイテンシ・リアルタイム同期通信:技術比較と実装ガイド
別プロジェクトでリアルタイムゲーム作ってて、プレイヤー間の動きをリアルタイムに同期させる必要があった。前に書いた数万人同時視聴イベントの設計パターンでは「疑似同期」(サーバ時刻基準、一方向性が強い)について解説した。
でも今回は違う。クライアント間で直接、またはサーバ経由で「今この瞬間」の状態を共有して、双方向のやり取りが必要だった。
こういう「小規模だけど低レイテンシ・双方向リアルタイム同期」の話。
疑似同期 vs 真同期
大規模イベントの設計と低レイテンシ同期は根本から違う。
| 項目 | 大規模イベント(疑似同期) | リアルタイム双方向(真同期) |
|---|---|---|
| 規模 | 数万人 | 数人~数千人 |
| 同期方向 | 一方向(サーバ→全員) | 双方向 |
| 同期精度 | 秒単位でOK | ms単位が必須 |
| サーバ時刻の役割 | 主役(絶対基準) | 補助的 |
| ネットワーク負荷 | 低頻度ポーリング | 常時通信 |
| ユースケース | 放送、一斉視聴 | ゲーム、コラボ、チャット |
疑似同期はサーバ負荷を最小化するための工夫。一方、真同期はクライアント間の状態同期そのものが目的で、どこまで遅延を減らせるかが勝負。
技術比較表
代表的な7つの通信技術を並べてみた。
| 技術 | 平均遅延 | 実装難度 | スケーラビリティ | ブラウザサポート | 双方向 | P2P対応 |
|---|---|---|---|---|---|---|
| WebSocket | 10-50ms | ★★☆ | 中程度(スケールアウト複雑) | ★★★★★ | ✓ | ✗ |
| WebRTC Data Channel | 5-30ms | ★★★★ | 高い(P2P可) | ★★★★☆ | ✓ | ✓ |
| SSE + HTTP/2 | 50-200ms | ★☆☆ | 中程度 | ★★★★★ | ✗(一方向) | ✗ |
| HTTP/3 + QUIC | 5-50ms | ★★★ | 中程度 | ★★★☆☆ | ✓ | ✗ |
| WebTransport | 5-30ms | ★★★★ | 中程度 | ★★☆☆☆(実験段階) | ✓ | ✗ |
| Agora(マネージド) | 5-100ms | ★☆☆ | 非常に高い | ✓ | ✓ | ✓ |
| LiveKit(OSS) | 5-100ms | ★★☆ | 非常に高い(自運用) | ✓ | ✓ | ✓ |
注:遅延は典型値で、ネットワーク状況に大きく依存。実装難度は「言語とライブラリの充実度」「学習コスト」を加味。
WebSocket
TCP上の双方向通信プロトコル。最も標準的で実装例が豊富。
特徴
- ハンドシェイク後はコネクションを持続
- テキスト・バイナリ両対応
- ブラウザサポートほぼ100%
メリット
- 実装がシンプル(ライブラリ充実)
- レイテンシが安定している
- デバッグ・監視ツールが豊富
デメリット
- スケールアウト時、サーバ間の状態同期が複雑
- 複数サーバ運用では「セッションアフィニティ」が必須
- ロードバランサの設定が煩雑
実装例
サーバー(Node.js + ws):
import WebSocket from 'ws';
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
console.log('Received:', message);
// 全クライアントにブロードキャスト
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
クライアント(JavaScript):
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({ type: 'position', x: 100, y: 200 }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
// ゲーム画面の他プレイヤー位置を更新など
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ユースケース
チャット、ボード共編、ゲームの初期段階、リアルタイム通知。
WebRTC Data Channel
P2P通信を実現。ネットワークの相手と直接やり取りできるのが最大の特徴。
特徴
- UDP/SCTP上のデータ通信
- P2P接続(シグナリングサーバが別途必須)
- 複数クライアント対応(メッシュまたはスター型)
メリット
- P2P時、サーバ経由しないので遅延が極小
- NAT越えが標準で対応(STUN/TURN)
- クライアント数が増えてもサーバ負荷が増えない
デメリット
- 実装の学習コストが高い
- シグナリングサーバの設計が複雑
- 複数人通信のメッシュ管理が煩雑
実装例
シグナリングサーバ(Node.js + Socket.IO):
import express from 'express';
import { Server } from 'socket.io';
const app = express();
const io = new Server(8080);
io.on('connection', (socket) => {
socket.on('offer', (data) => {
// AからのofferをBへ中継
socket.broadcast.emit('offer', {
from: socket.id,
offer: data.offer
});
});
socket.on('answer', (data) => {
// BからのanswerをAへ送信
io.to(data.to).emit('answer', {
from: socket.id,
answer: data.answer
});
});
socket.on('ice-candidate', (data) => {
// ICE candidateを相手に中継
io.to(data.to).emit('ice-candidate', {
from: socket.id,
candidate: data.candidate
});
});
});
クライアント側(シンプル版):
const socket = io('http://localhost:8080');
let peerConnection;
socket.on('offer', async (data) => {
peerConnection = new RTCPeerConnection();
// データチャネルを受け取る
peerConnection.ondatachannel = (event) => {
const dc = event.channel;
dc.onmessage = (e) => {
console.log('受信:', e.data);
};
};
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.offer)
);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit('answer', { to: data.from, answer });
});
// 自分からオファーを送信する場合
async function initiateConnection() {
peerConnection = new RTCPeerConnection();
const dc = peerConnection.createDataChannel('game-sync');
dc.onopen = () => {
dc.send(JSON.stringify({ type: 'init', playerId: 'me' }));
};
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
socket.emit('offer', { offer });
}
ユースケース
1対1マッチゲーム、P2Pファイル共有、複数人での低遅延データ交換。
SSE + HTTP/2
サーバからクライアントへの一方向ストリーム。双方向が不要な用途で最高にシンプル。
特徴
- サーバ→クライアント一方向
- HTTP/2で複数接続数制限を回避
- テキストストリーム形式
メリット
- 実装が超シンプル(
EventSource使うだけ) - ファイアウォール・プロキシの互換性が高い
- ブラウザサポート優秀
デメリット
- 一方向のみ(クライアント→サーバはHTTP通常リクエスト)
- サーバ・クライアント間でポーリング送受信する場合は複雑化
実装例
サーバー(Node.js + Express):
import express from 'express';
const app = express();
const clients = new Set();
app.get('/stream', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
clients.add(res);
res.on('close', () => {
clients.delete(res);
});
});
// ブロードキャスト関数
function broadcast(data) {
clients.forEach(res => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
});
}
app.post('/action', (req, res) => {
const action = req.body;
broadcast(action);
res.sendStatus(200);
});
app.listen(8080);
クライアント(JavaScript):
const eventSource = new EventSource('/stream');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('ブロードキャスト:', data);
// 画面更新など
};
eventSource.onerror = () => {
console.error('Connection lost');
};
// クライアント側からのアクション送信(通常のHTTP)
async function sendAction(action) {
await fetch('/action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(action)
});
}
ユースケース
株価・仮想通貨ティッカー、ライブスコアボード、通知システム、リーダーボード。
HTTP/3 + QUIC
UDP上のQUICプロトコル。次世代HTTPとして注目。
特徴
- UDPベース(TCPじゃない)
- HoLブロッキング(Head-of-Line blocking)を回避
- 複数ストリーム並行可
- 0-RTT接続再開
ブラウザサポート
Chrome/Edge: ✓ Safari: ✓(最近対応) Firefox: 実験段階 全体採用率: 約26%(2024年時点)
メリット
- TCPより低遅延
- ネットワーク切り替え時(WiFi→LTE)の耐性
デメリット
- サーバ実装の標準化がまだ途上
- ファイアウォール・プロキシでブロックされることあり
- デバッグが複雑
トーン
HTTP/3対応のCDNやエッジサーバを使えば自動で恩恵を受けられる。Cloudflareなどは既に標準提供。ただし、WebSocketで十分な場面では無理に HTTP/3 に切り替える必要はない。
WebTransport(注目の新技術)
現時点ではプロダクション採用を推奨しません。実験段階です。
QUICとHTTP/3をベースに、ストリーム(信頼性重視)とデータグラム(速度重視)を柔軟に選択できる次世代標準。
特徴
- ストリーム:信頼性あり(WebSocketの後継候補)
- データグラム:信頼性なし、超低遅延(ゲーム向け)
- ブラウザネイティブAPI
ブラウザサポート
Chrome 86+のみ。Safariは実装予定。Firefoxは未定。
メリット
- 最新最高のレイテンシ性能
- ユースケース別の選択肢
デメリット
- ブラウザサポート限定的
- 実装例・解説記事が少ない
- 仕様がまだ流動的
参考実装
// Chrome実験段階
async function connectWebTransport() {
try {
const transport = await WebTransport.connect(
'https://example.com:4433/game'
);
// ストリームベース(信頼性重視)
const writer = transport.datagrams.writable.getWriter();
writer.write(new Uint8Array([1, 2, 3]));
// データグラムベース(速度重視)
transport.incomingUnidirectionalStreams.getReader().read()
.then(({ value }) => {
const reader = value.getReader();
reader.read().then(({ value, done }) => {
console.log('受信:', new TextDecoder().decode(value));
});
});
} catch (e) {
console.error('WebTransport error:', e);
}
}
マネージド型プラットフォーム(Agora の例)
インフラ管理を完全に外部に委譲。スケーリングの心配がない。
特徴
- SaaS型、インフラ管理不要
- グローバルエッジサーバネットワーク
- 標準化されたAPI
メリット
- 運用・スケーリングの手間がゼロ
- グローバル配信時の遅延最小化
- サポート充実
デメリット
- ベンダーロックイン
- カスタマイズの制限
- 月額費用発生
コード例
import AgoraRTC from "agora-rtc-sdk-ng";
const agoraEngine = AgoraRTC.createClient({
mode: "rtc",
codec: "vp8"
});
agoraEngine.on("user-published", async (user, mediaType) => {
await agoraEngine.subscribe(user, mediaType);
});
agoraEngine.on("user-unpublished", async (user) => {
await agoraEngine.unsubscribe(user);
});
// 参加
await agoraEngine.join(appId, channelName, token, uid);
オープンソース型(LiveKit の例)
自前運用で完全コントロール。カスタマイズ自由度が高い。
特徴
- GitHub公開
- Kubernetes対応
- 自前インフラで展開可能
メリット
- カスタマイズ自由度高い
- ロックインなし
- 大規模運用時のコスト削減可能
デメリット
- インフラ構築・運用コストが必要
- トラブル対応は自力
導入例
import {
connect,
Room,
RoomOptions
} from 'livekit-client';
const url = 'ws://your-livekit-server:7880';
const token = await generateToken(userName, roomName);
const room = new Room();
await room.connect(url, token);
room.on('participantConnected', (participant) => {
console.log('参加:', participant.name);
});
ユースケース別ガイド
リアルタイムゲーム
要件:遅延50ms以下、複数プレイヤー(2~100人)、双方向
推奨技術:
- WebSocket(標準選択肢)
- WebRTC Data Channel(P2P時)
実装ポイント:
- プレイヤー位置データはサーバ中継(全員に見せるため)
- 入力は即座に送信(予測入力も併用)
- フレームレート同期(60fps)とサーバチック同期を分離
マルチプレイヤーホワイトボード
要件:遅延100ms以下、複数ユーザー(2~20人)、同期精度中程度
推奨技術:
- WebSocket
- SSE(背景変更など一方向部分)
実装ポイント:
- ドローイングストロークはまとめて送信(毎フレーム送信は重い)
- CRDT(Conflict-free Replicated Data Type)で競合解決(複雑化するので検討段階)
ライブコラボレーション(Figma風)
要件:遅延50ms以下、多数同時接続(10~100人)、Operational Transformation必須
推奨技術:
- WebSocket(OT実装サーバ)
- マネージド型(YjsやPartyKit統合)
実装ポイント:
- 各ユーザーの操作をサーバで正規化(Operational Transformation)
- クライアント側でも楽観的更新で遅延感を消す
チャット・メッセージング
要件:遅延500ms程度で許容、ユーザー数多数、一度配信したら履歴は不要
推奨技術:
- WebSocket(リアルタイム)
- HTTP polling(フォールバック)
実装ポイント:
- メッセージ履歴はDB保存し、接続時のみ取得
- 接続喪失時は再接続で新規メッセージ取得
株価・仮想通貨ティッカー
要件:遅延100~500ms、ブロードキャスト(一方向)、多数のクライアント接続
推奨技術:
- SSE + HTTP/2(シンプル、スケール優秀)
- WebSocket(カスタマイズ必要な場合)
実装ポイント:
- 更新があるたびにSSEでプッシュ
- クライアント側ではローカルストレージに最新値をキャッシュ
リアルタイム音声・ビデオ
要件:遅延10~100ms(コデック依存)、複数人同時接続、高スループット
推奨技術:
- WebRTC(標準、P2P可能)
- マネージド型(Agora、Twilio)
実装ポイント:
- オーディオコーデック(opus)とビデオコーデック(VP8/H.264)の選定
- ネットワーク負荷に応じた品質調整(解像度・フレームレート低下)
YouTube Live風コメントシステム
要件:遅延数秒OK、配信者と視聴者が時刻同期
推奨技術:
- WebSocket + 時刻同期(疑似同期記事の仕組みを活用)
- SSE(一方向配信部分)
実装ポイント:
- コメント配信とビデオ再生の同期には、疑似同期記事で紹介したタイムシートを組み合わせ
- サーバ時刻取得でクライアント間の時刻ズレを補正
技術選択フロー
START
↓
【Q1】遅延は50ms以下である必要があるか?
├─ YES → Q2へ
└─ NO → Q3へ
【Q2】クライアント間の直接通信(P2P)が必須か?
├─ YES → WebRTC Data Channel を検討
└─ NO → WebSocket or HTTP/3 を検討
【Q3】サーバ管理の手間を最小化したいか?
├─ YES → Agora などマネージド型を検討
└─ NO(自運用OK) → Q4へ
【Q4】ブロードキャスト(一方向)で十分か?
├─ YES → SSE + HTTP/2 を検討
└─ NO(双方向必須) → WebSocket へ
採用実績・参考リソース
WebSocket:Discord, Slack(実装のコア)、Socket.IO(npm 週間DL 1.5M+)
WebRTC:Google Meet, Zoom(ビデオ/音声)、対戦ゲーム各種
SSE:Twitter X(リアルタイムフィード部分)
Agora:ウェビナープラットフォーム、ライブコマース
LiveKit:ビデオチャットプラットフォーム、エンタープライズ向け
まとめ
疑似同期(大規模、一方向)と真同期(小規模、双方向)は設計思想が根本的に違う。
選択基準:
- 遅延要件が50ms以下なら WebSocket か WebRTC Data Channel
- P2P必須なら WebRTC Data Channel
- ブロードキャストだけなら SSE + HTTP/2
- 運用コストを最小化したいなら Agora などマネージド型
- カスタマイズ重視なら WebSocket か LiveKit
複合利用パターンも多い。例えば、YouTube Live風なら「SSE でビデオ配信」「WebSocket でコメント」「サーバ時刻同期で演出タイミング」と、複数技術を組み合わせるのが現実的。
前回の疑似同期記事で「大規模イベント」のスケーリング方法を解説した。今回は「小規模だけど低遅延」の実装パターンをカバーした。自分の要件に合わせて、両者を使い分けられるといい。