T-CREATOR

WebRTC 完全メッシュ vs SFU 設計比較:同時接続数と帯域コストを数式で見積もる

WebRTC 完全メッシュ vs SFU 設計比較:同時接続数と帯域コストを数式で見積もる

リアルタイム通信アプリケーションを開発する際、WebRTC の通信方式選択は成功の鍵を握ります。参加者が増えるほど複雑になる帯域コストと接続数の関係を、数式を使って明確に理解しましょう。

適切な設計選択により、コストを最適化しながらユーザー体験を向上させることができます。

背景

WebRTC 通信アーキテクチャの種類

WebRTC では主に 2 つの通信アーキテクチャが利用されています。それぞれの特性を理解することで、プロジェクトに最適な選択が可能になります。

通信方式の全体像を以下の図で確認してみましょう。

mermaidflowchart TD
  webrtc["WebRTC 通信方式"] --> mesh["完全メッシュ型"]
  webrtc --> sfu["SFU 型"]

  mesh --> peer1["P2P 直接通信"]
  mesh --> peer2["各端末間で接続"]
  mesh --> peer3["サーバー不要"]

  sfu --> server["中央サーバー経由"]
  sfu --> single["各端末は1接続のみ"]
  sfu --> scale["サーバーで配信制御"]

この図からわかるように、完全メッシュ型は端末同士が直接通信し、SFU 型は中央サーバーを介して通信を行います。

完全メッシュ型の基本概念

完全メッシュ型(Full Mesh)は、参加者全員が互いに直接接続する方式です。各端末がピア(Peer)として機能し、サーバーを介さずに通信を行います。

mermaidflowchart LR
  A["参加者A"] --- B["参加者B"]
  A --- C["参加者C"]
  A --- D["参加者D"]
  B --- C
  B --- D
  C --- D

この方式では、n 人の参加者がいる場合、合計で n(n-1)​/​2 本の接続が必要になります。各参加者は他の全員と個別に接続を確立し、音声・映像データを直接送受信します。

完全メッシュ型の主な特徴は以下のとおりです。

  • 低遅延: サーバーを経由しないため、通信遅延が最小限
  • 高品質: 直接通信によりデータの劣化が少ない
  • サーバー不要: 中央サーバーのコストが不要

SFU 型の基本概念

SFU(Selective Forwarding Unit)型は、中央サーバーが各参加者からのストリームを受信し、他の参加者に転送する方式です。

mermaidflowchart TD
  A["参加者A"] --> SFU["SFU サーバー"]
  B["参加者B"] --> SFU
  C["参加者C"] --> SFU
  D["参加者D"] --> SFU

  SFU --> A
  SFU --> B
  SFU --> C
  SFU --> D

この方式では、各参加者は SFU サーバーとのみ接続し、サーバーが適切にストリームを配信します。参加者数に関係なく、各クライアントの接続数は一定に保たれます。

SFU 型の主な特徴は以下のとおりです。

  • スケーラブル: 参加者数が増加してもクライアント負荷は一定
  • 帯域効率: サーバーで最適化された配信が可能
  • 管理機能: サーバー側で通信制御や録画機能を実装可能

課題

スケーラビリティの限界

完全メッシュ型では参加者数の増加に伴い、接続数が二次関数的に増加します。この現象により深刻なスケーラビリティの問題が発生します。

接続数の増加パターンを以下の図で確認してみましょう。

mermaidflowchart LR
  users["参加者数"] --> conn["接続数"]

  n2["2人"] --> c1["1接続"]
  n3["3人"] --> c3["3接続"]
  n4["4人"] --> c6["6接続"]
  n5["5人"] --> c10["10接続"]
  n10["10人"] --> c45["45接続"]

この図が示すように、参加者数が倍増しても接続数は 4 倍以上に増加します。これにより以下の問題が発生します。

  • CPU 負荷の急激な増加: 各端末で複数の映像エンコード・デコード処理が必要
  • メモリ使用量の増大: 複数のコネクション状態を同時に管理
  • ネットワーク負荷: 同じデータを複数の相手に個別送信

実際の限界値として、一般的なデスクトップ環境では 6-8 人、モバイル端末では 4-6 人程度が実用的な上限とされています。

帯域幅の課題

完全メッシュ型では、各参加者が他の全員に対して個別にデータを送信する必要があります。これにより帯域使用量が参加者数に対して指数関数的に増加します。

mermaidflowchart TD
  sender["送信者"] --> p1["参加者1"]
  sender --> p2["参加者2"]
  sender --> p3["参加者3"]
  sender --> p4["参加者4"]

  p1 --> sender2["全員が同様に送信"]
  p2 --> sender2
  p3 --> sender2
  p4 --> sender2

例えば、HD 品質(2Mbps)の映像を 5 人で共有する場合:

  • 完全メッシュ: 各参加者が 4 人に送信するため、アップロード帯域 2Mbps × 4 = 8Mbps が必要
  • ダウンロード帯域: 他の 4 人から受信するため 2Mbps × 4 = 8Mbps が必要

この帯域要件は参加者数が増えるほど厳しくなり、一般的な回線では対応が困難になります。

サーバーコストの問題

SFU 型では中央サーバーが必要となり、以下のコストが発生します。

コスト要素説明課題
サーバー費用インスタンス・CPU・メモリ参加者数に応じたスケールアップが必要
帯域費用データ転送量に応じた従量課金大容量通信では高額になる可能性
運用費用監視・保守・バックアップ24 時間体制での運用が必要

特に帯域コストは、参加者数と映像品質に比例して増加するため、事前の見積もりが重要になります。

解決策

完全メッシュ型の数式解析

完全メッシュ型のコストと制約を数式で正確に把握することで、適用可能な範囲を明確に判断できます。

接続数の計算式

n 人の参加者による完全メッシュネットワークでは、接続数は組み合わせの公式で算出されます。

javascript// 完全メッシュの接続数計算
function calculateMeshConnections(participants) {
  // 組み合わせ nC2 = n(n-1)/2
  return (participants * (participants - 1)) / 2;
}

// 使用例
console.log(calculateMeshConnections(5)); // 結果: 10接続
console.log(calculateMeshConnections(10)); // 結果: 45接続

数学的には以下の式で表現されます:

接続数 = C(n,2) = n(n-1)/2

この式から、参加者数が倍増すると接続数は約 4 倍に増加することがわかります。

帯域使用量の計算式

各参加者の帯域使用量は、送信と受信の両方を考慮する必要があります。

javascript// 完全メッシュの帯域計算
function calculateMeshBandwidth(
  participants,
  videoBitrate,
  audioBitrate
) {
  const otherParticipants = participants - 1;

  // 各参加者のアップロード帯域
  const uploadBandwidth =
    otherParticipants * (videoBitrate + audioBitrate);

  // 各参加者のダウンロード帯域
  const downloadBandwidth =
    otherParticipants * (videoBitrate + audioBitrate);

  return {
    perParticipantUpload: uploadBandwidth,
    perParticipantDownload: downloadBandwidth,
    totalBandwidth: participants * uploadBandwidth,
  };
}

// 使用例: 5人、映像2Mbps、音声64kbps
const result = calculateMeshBandwidth(5, 2000, 64);
console.log(
  `各参加者アップロード: ${result.perParticipantUpload}kbps`
);
console.log(`総帯域使用量: ${result.totalBandwidth}kbps`);

数式で表現すると:

各参加者の帯域 = (n-1) × (映像ビットレート + 音声ビットレート) 総帯域使用量 = n × (n-1) × (映像ビットレート + 音声ビットレート)

SFU 型の数式解析

SFU 型では、クライアント側とサーバー側の両方で帯域計算を行う必要があります。

接続数の計算式

SFU 型では各クライアントがサーバーとのみ接続するため、接続数は線形に増加します。

javascript// SFU の接続数計算
function calculateSFUConnections(participants) {
  // 各参加者がSFUサーバーと1接続
  return participants;
}

// サーバー側の接続数
function calculateSFUServerConnections(participants) {
  // サーバーは全参加者と接続
  return participants;
}

クライアント接続数 = 1(常に一定) サーバー接続数 = n(参加者数に比例)

帯域使用量の計算式

SFU 型では、クライアントとサーバーで異なる帯域計算が必要です。

javascript// SFU の帯域計算
function calculateSFUBandwidth(
  participants,
  videoBitrate,
  audioBitrate
) {
  const streamBitrate = videoBitrate + audioBitrate;

  // 各クライアントの帯域使用量
  const clientUpload = streamBitrate; // 自分のストリーム送信
  const clientDownload = (participants - 1) * streamBitrate; // 他者のストリーム受信

  // サーバーの帯域使用量
  const serverDownload = participants * streamBitrate; // 全員のストリーム受信
  const serverUpload =
    participants * (participants - 1) * streamBitrate; // 配信

  return {
    clientUpload,
    clientDownload,
    serverDownload,
    serverUpload,
    totalServerBandwidth: serverDownload + serverUpload,
  };
}

数式で表現すると:

クライアントアップロード = 映像ビットレート + 音声ビットレート クライアントダウンロード = (n-1) × (映像ビットレート + 音声ビットレート) サーバー帯域 = n × (映像ビットレート + 音声ビットレート) + n × (n-1) × (映像ビットレート + 音声ビットレート)

コスト比較の数式

両方式のコストを定量的に比較するための計算式を作成しましょう。

javascript// コスト比較計算
function compareArchitectureCosts(
  participants,
  videoBitrate,
  audioBitrate,
  serverCostPerGb
) {
  const streamBitrate = videoBitrate + audioBitrate;

  // 完全メッシュのクライアント負荷コスト
  const meshClientBandwidth =
    participants * (participants - 1) * streamBitrate;

  // SFUのコスト計算
  const sfuServerBandwidth =
    participants * streamBitrate * participants;
  const sfuServerCost =
    (sfuServerBandwidth / 1000 / 1000) * serverCostPerGb; // GB変換

  return {
    meshClientLoad: meshClientBandwidth,
    sfuServerCost,
    sfuClientBandwidth:
      participants *
      ((participants - 1) * streamBitrate + streamBitrate),
  };
}

// 損益分岐点の計算
function findBreakEvenPoint(
  videoBitrate,
  audioBitrate,
  serverCostPerGb,
  clientCostThreshold
) {
  let participants = 2;

  while (participants <= 100) {
    const costs = compareArchitectureCosts(
      participants,
      videoBitrate,
      audioBitrate,
      serverCostPerGb
    );

    if (
      costs.sfuServerCost <
      costs.meshClientLoad * clientCostThreshold
    ) {
      return participants;
    }
    participants++;
  }

  return null; // 損益分岐点が見つからない場合
}

損益分岐点は以下の条件で決定されます:

SFU サーバーコスト < 完全メッシュクライアント負荷コスト

この比較により、プロジェクトの規模と要件に応じた最適な選択が可能になります。

具体例

参加者数別の比較シミュレーション

実際の数値を使って、両方式の特性を比較してみましょう。以下の条件でシミュレーションを実行します。

項目
映像ビットレート2Mbps(HD 品質)
音声ビットレート64kbps
サーバー費用$0.1/GB
javascript// シミュレーション実行
function runBandwidthSimulation() {
  const videoBitrate = 2000; // kbps
  const audioBitrate = 64; // kbps
  const participantCounts = [2, 3, 5, 8, 10, 15, 20];

  console.log('参加者数別帯域使用量比較(kbps)');
  console.log(
    '参加者数 | 完全メッシュ(総計) | SFU(クライアント) | SFU(サーバー)'
  );

  participantCounts.forEach((n) => {
    const mesh = calculateMeshBandwidth(
      n,
      videoBitrate,
      audioBitrate
    );
    const sfu = calculateSFUBandwidth(
      n,
      videoBitrate,
      audioBitrate
    );

    console.log(
      `${n}人 | ${mesh.totalBandwidth} | ${
        sfu.clientUpload + sfu.clientDownload
      } | ${sfu.totalServerBandwidth}`
    );
  });
}

シミュレーション結果の可視化:

mermaidflowchart TD
  sim["シミュレーション結果"] --> mesh_result["完全メッシュ結果"]
  sim --> sfu_result["SFU結果"]

  mesh_result --> m2["2人: 4Mbps"]
  mesh_result --> m5["5人: 40Mbps"]
  mesh_result --> m10["10人: 180Mbps"]

  sfu_result --> s2["2人: クライアント8Mbps<br/>サーバー8Mbps"]
  sfu_result --> s5["5人: クライアント10Mbps<br/>サーバー100Mbps"]
  sfu_result --> s10["10人: クライアント20Mbps<br/>サーバー1800Mbps"]

この結果から、参加者数が 5 人を超えると完全メッシュの総帯域使用量が急激に増加し、実用性が低下することがわかります。

実際のユースケース別検証

代表的なユースケースごとに、最適な方式を検証してみましょう。

javascript// ユースケース別最適化
const useCases = [
  {
    name: '1対1ビデオ通話',
    participants: 2,
    quality: 'HD',
    recommendation: '完全メッシュ',
  },
  {
    name: '家族会議',
    participants: 4,
    quality: 'HD',
    recommendation: '完全メッシュ',
  },
  {
    name: 'チーム会議',
    participants: 8,
    quality: 'HD',
    recommendation: 'SFU',
  },
  {
    name: 'ウェビナー',
    participants: 50,
    quality: 'HD',
    recommendation: 'SFU',
  },
];

function analyzeUseCase(useCase) {
  const videoBitrate =
    useCase.quality === 'HD' ? 2000 : 1000;
  const audioBitrate = 64;

  const mesh = calculateMeshBandwidth(
    useCase.participants,
    videoBitrate,
    audioBitrate
  );
  const sfu = calculateSFUBandwidth(
    useCase.participants,
    videoBitrate,
    audioBitrate
  );

  return {
    useCase: useCase.name,
    meshTotal: mesh.totalBandwidth,
    sfuClient: sfu.clientUpload + sfu.clientDownload,
    sfuServer: sfu.totalServerBandwidth,
    recommendation: useCase.recommendation,
  };
}

各ユースケースの分析結果:

ユースケース完全メッシュ総計SFU クライアントSFU サーバー推奨方式
1 対 1 ビデオ通話4Mbps4Mbps8Mbps完全メッシュ
家族会議(4 人)24Mbps8Mbps48Mbps完全メッシュ
チーム会議(8 人)112Mbps16Mbps448MbpsSFU
ウェビナー(50 人)5000Mbps102Mbps10200MbpsSFU

この分析から、参加者数が 6-8 人を境界として、推奨方式が変わることがわかります。小規模な通話では完全メッシュの低遅延性が有効ですが、大規模な会議では SFU のスケーラビリティが必要になります。

まとめ

WebRTC の通信方式選択は、数式による定量的な分析により最適化できます。完全メッシュ型は小規模(2-6 人)での低遅延通信に適しており、SFU 型は大規模(7 人以上)でのスケーラブルな通信に適しています。

重要な判断基準をまとめると以下のとおりです。

完全メッシュ型を選ぶべき場合

  • 参加者数が 6 人以下
  • 低遅延が最重要要件
  • サーバーコストを抑制したい
  • 高品質な音声・映像が必要

SFU 型を選ぶべき場合

  • 参加者数が 7 人以上
  • スケーラビリティが重要
  • クライアントデバイスの性能制約がある
  • 録画や配信制御機能が必要

数式を活用した事前設計により、コストとパフォーマンスの最適なバランスを実現し、ユーザー体験の向上とビジネス成功の両立が可能になります。

関連リンク