0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【バックエンド】APIのポーリング、WebSocket、SSEの違いを理解する

0
Posted at

はじめに

現代のWebアプリケーションでは、チャット機能や株価情報の更新、通知機能など、リアルタイム性が求められる場面が増えています。サーバーからクライアントへ最新情報を届ける方法として、主に3つの通信方式があります。

  • ポーリング(Polling): クライアントが定期的にサーバーへ問い合わせる
  • WebSocket: クライアントとサーバー間で双方向の永続的な接続を確立する
  • SSE(Server-Sent Events): サーバーからクライアントへ一方向にデータを送信する

それぞれに特徴があり、用途によって使い分ける必要があります。この記事では、各通信方式の仕組みと、どのような場面で使うべきかを解説します。

ポーリング(Polling)

ポーリングとは

ポーリングは、クライアントが一定間隔でサーバーに対してHTTPリクエストを送り、新しいデータがあるか確認する方式です。最もシンプルで理解しやすい仕組みですね。

仕組みと動作フロー

クライアントは設定した間隔(例えば5秒ごと)でサーバーにリクエストを送り、新しいデータの有無を確認します。サーバーは毎回HTTPレスポンスを返し、接続は都度切断されます。

メリットとデメリット

メリット

  • 実装がシンプルで、既存のHTTP通信の知識で対応できる
  • サーバー側の実装が容易で、特別なプロトコルやライブラリが不要
  • ファイアウォールやプロキシの影響を受けにくい

デメリット

  • 更新がない場合も無駄なリクエストが発生し、サーバーとネットワークに負荷がかかる
  • ポーリング間隔によってはリアルタイム性が低い(5秒間隔なら最大5秒の遅延)
  • 同時接続数が多い場合、サーバーリソースを圧迫する

適している場面と実装例

適している場面

  • リアルタイム性がそれほど求められない場合(数秒〜数十秒の遅延が許容される)
  • 更新頻度が低いデータ(ダッシュボードの定期更新など)
  • シンプルな実装で済ませたい小規模なアプリケーション

基本的な実装例

// クライアント側(JavaScript)
async function pollData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    updateUI(data);
  } catch (error) {
    console.error('ポーリングエラー:', error);
  }
}

// 5秒ごとにポーリング
setInterval(pollData, 5000);
// サーバー側(Node.js/Express)
app.get('/api/data', (req, res) => {
  const latestData = getLatestData(); // 最新データを取得
  res.json(latestData);
});

WebSocket

WebSocketとは

WebSocketは、クライアントとサーバー間で双方向の永続的な接続を確立するプロトコルです。一度接続すれば、どちらからでも自由にデータを送信できます。

仕組みと動作フロー

最初にHTTPプロトコルで接続を開始し、その後WebSocketプロトコルにアップグレードします。接続が確立されると、クライアントとサーバーは対等な関係となり、いつでもメッセージを送信できます。

メリットとデメリット

メリット

  • 双方向のリアルタイム通信が可能で、遅延がほとんどない
  • 接続を維持するため、リクエストのオーバーヘッドがなく効率的
  • サーバーから能動的にデータをプッシュできる

デメリット

  • 実装が複雑で、接続管理や再接続処理が必要
  • サーバー側で接続を維持するため、多数のクライアントがいる場合はリソース消費が大きい
  • 一部のプロキシやファイアウォールで接続が遮断される可能性がある
  • スケーリングが難しい(ロードバランサーの設定など)

適している場面と実装例

適している場面

  • チャットアプリケーションやオンラインゲームなど、リアルタイム性が重要
  • 双方向通信が必要な場合(クライアントからも頻繁にデータを送る)
  • データの更新頻度が高く、常に最新の状態を保ちたい

基本的な実装例

// クライアント側(JavaScript)
const socket = new WebSocket('ws://localhost:3000');

// 接続が開いたとき
socket.addEventListener('open', (event) => {
  console.log('WebSocket接続が確立しました');
  socket.send('Hello Server!');
});

// メッセージを受信したとき
socket.addEventListener('message', (event) => {
  console.log('サーバーからのメッセージ:', event.data);
  updateUI(event.data);
});

// エラー発生時
socket.addEventListener('error', (error) => {
  console.error('WebSocketエラー:', error);
});
// サーバー側(Node.js/ws)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });

wss.on('connection', (ws) => {
  console.log('クライアントが接続しました');
  
  // クライアントからメッセージを受信
  ws.on('message', (message) => {
    console.log('受信:', message);
    // 全クライアントにブロードキャスト
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });
  
  // 定期的にデータを送信
  const interval = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ timestamp: Date.now() }));
    }
  }, 1000);
  
  ws.on('close', () => {
    clearInterval(interval);
    console.log('クライアントが切断しました');
  });
});

SSE(Server-Sent Events)

SSEとは

SSE(Server-Sent Events)は、サーバーからクライアントへ一方向にデータをストリーミングする技術です。HTTPプロトコルを使用しつつ、サーバーからのプッシュ通知を実現します。

仕組みと動作フロー

SSEは通常のHTTP接続を使用しますが、レスポンスを閉じずにストリームとして保持します。サーバーは必要に応じてイベントデータを送信し、クライアントはそれを受信し続けます。

メリットとデメリット

メリット

  • 実装がWebSocketより簡単で、通常のHTTPを使用する
  • 自動的に再接続する機能が標準で備わっている
  • サーバーからのプッシュ通知に特化しており、用途がシンプル
  • プロキシやファイアウォールとの互換性が高い

デメリット

  • サーバーからクライアントへの一方向通信のみ(クライアントからはHTTPリクエストが必要)
  • Internet Explorerではサポートされていない
  • 同時接続数の制限がある(ブラウザごとに6〜8接続程度)

適している場面と実装例

適している場面

  • サーバーからのプッシュ通知が主な用途(ニュースフィード、株価更新など)
  • クライアントからの送信は少なく、主にサーバーからの情報を受け取る
  • 実装をシンプルに保ちたいが、ポーリングより効率的にしたい

基本的な実装例

// クライアント側(JavaScript)
const eventSource = new EventSource('/api/events');

// メッセージを受信
eventSource.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  console.log('受信データ:', data);
  updateUI(data);
});

// カスタムイベントを受信
eventSource.addEventListener('update', (event) => {
  const data = JSON.parse(event.data);
  console.log('更新イベント:', data);
});

// エラー処理
eventSource.addEventListener('error', (error) => {
  console.error('SSEエラー:', error);
  // 自動的に再接続を試みる
});

// 接続を閉じる
// eventSource.close();
// サーバー側(Node.js/Express)
app.get('/api/events', (req, res) => {
  // SSE用のヘッダー設定
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  // 定期的にデータを送信
  const interval = setInterval(() => {
    const data = {
      timestamp: Date.now(),
      message: 'サーバーからの更新'
    };
    
    // デフォルトのmessageイベント
    res.write(`data: ${JSON.stringify(data)}\n\n`);
    
    // カスタムイベント
    res.write(`event: update\n`);
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  }, 2000);
  
  // クライアントが切断したときの処理
  req.on('close', () => {
    clearInterval(interval);
    console.log('クライアントが切断しました');
  });
});

3つの通信方式の比較

比較表

項目 ポーリング WebSocket SSE
通信方向 リクエスト/レスポンス 双方向 サーバー→クライアント
プロトコル HTTP WebSocket HTTP
接続 都度確立・切断 永続的 永続的
リアルタイム性 低〜中
実装の複雑さ 簡単 やや複雑 簡単
サーバー負荷 高い(頻繁なリクエスト) 中(接続維持) 中(接続維持)
ブラウザ対応 すべて ほぼすべて IE以外
再接続 不要(毎回新規接続) 手動実装が必要 自動
ファイアウォール 問題なし 問題が起こる可能性 問題なし

選択基準とユースケース

各通信方式を選択する基準を、具体的なユースケースとともに見ていきましょう。

ポーリングを選ぶ場合

  • ダッシュボードの定期更新(1分ごとの売上データ更新など)
  • メールの新着確認
  • ジョブの実行状態確認
  • シンプルな通知機能

WebSocketを選ぶ場合

  • チャットアプリケーション
  • オンラインゲーム
  • 共同編集ツール(Google Docsのようなリアルタイム編集)
  • リアルタイムコラボレーションツール
  • トレーディングプラットフォーム(双方向の注文処理)

SSEを選ぶ場合

  • ニュースフィードやSNSのタイムライン更新
  • 株価や為替レートの配信
  • サーバーログのストリーミング表示
  • 進捗状況の通知(ファイルアップロードの進行状況など)
  • 通知センター

パフォーマンスとコストの観点

サーバー負荷の観点

  • ポーリング: クライアント数 × ポーリング頻度 分のリクエスト処理が必要
  • WebSocket: 接続数分のメモリとCPUリソースを常時消費
  • SSE: WebSocketと同様だが、一方向のため若干軽量

開発コストの観点

  • ポーリング: 最も低い(既存のHTTP APIの延長)
  • SSE: 低い(標準APIが使いやすい)
  • WebSocket: 高い(接続管理、再接続、エラーハンドリングなど)

運用コストの観点

  • ポーリング: スケールアウトが容易、ロードバランサーの設定が不要
  • SSE: スケールアウトが比較的容易
  • WebSocket: Sticky Sessionの設定やRedisなどの共有ストレージが必要になる場合がある

まとめ

この記事では、バックエンドAPIの3つの通信方式について解説しました。

それぞれの特徴

  • ポーリング: シンプルで実装が容易だが、無駄なリクエストが発生する
  • WebSocket: 双方向のリアルタイム通信が可能だが、実装とスケーリングが複雑
  • SSE: サーバーからのプッシュに特化し、実装がシンプルで自動再接続機能を持つ

実際のプロジェクトでの選び方

  1. まず、リアルタイム性がどの程度必要かを見極める
  2. 通信が一方向か双方向かを確認する
  3. 開発リソースと運用コストを考慮する
  4. ブラウザの対応状況を確認する

多くの場合、最初はシンプルなポーリングから始めて、必要に応じてSSEやWebSocketに移行するアプローチが現実的です。過度に複雑な実装は避け、要件に合った最適な方式を選択しましょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?