システムデザイン完全ガイド ②|ネットワークと通信

システムデザイン

SYSTEM DESIGN GUIDE — PART 2 / 7

ネットワークと通信

IP・TCP・UDP・QUIC からロードバランサ、API 設計、リアルタイム通信まで。データがユーザーに届くまでの経路を、レイヤーごとに分解します。

収録:第 5 章 〜 第 10 章 / 前提:第 1 部のレイテンシの数字とスケールの階段

CHAPTER 05インターネットの基礎(IP / TCP / UDP / QUIC)

🎯 GOAL

「リクエストがサーバーに届くまで」を層ごとに説明できるようになる。

5.1レイヤーの地図

OSI / TCP-IP LAYERS
┌──────────────────────────────────────────┐
│ L7 アプリケーション  HTTP, gRPC, DNS, SMTP  │ ← 何を伝えるか
├──────────────────────────────────────────┤
│ L4 トランスポート    TCP, UDP, QUIC         │ ← 確実に届けるか
├──────────────────────────────────────────┤
│ L3 ネットワーク      IP, ICMP, BGP          │ ← どの経路で運ぶか
├──────────────────────────────────────────┤
│ L2 データリンク      Ethernet, ARP          │ ← 隣の機器へ渡す
├──────────────────────────────────────────┤
│ L1 物理             光ファイバ, 電波        │ ← 物理的な信号
└──────────────────────────────────────────┘
💡 たとえ話

手紙の配達です。

  • L7 = 手紙の中身(日本語で書かれた文章)
  • L4 = 書留にするか普通郵便にするか(届いたか確認するかどうか)
  • L3 = 宛先住所と配送経路
  • L2 = 今この郵便局から隣の郵便局へトラックで運ぶ
  • L1 = トラックそのもの

システムデザインで直接扱うのは主に L4 と L7、そしてルーティングの L3 です。

5.2IP — 住所と経路

  • IP アドレス:v4 は 32 bit(例 93.184.216.34)、v6 は 128 bit
  • IP はベストエフォート。届かないことも、順序が入れ替わることも、重複することもある
  • プライベート IP10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 は社内・VPC 用
  • CIDR 記法10.0.1.0/24 は「上位 24 bit が固定=256 個のアドレス」
🔬 深掘り

アナキャスト (Anycast):同じ IP アドレスを世界中の複数拠点に割り当て、BGP のルーティングで「最も近い」拠点に届ける技術。CDN と DNS(8.8.8.8 など)の基盤技術であり、DDoS 分散にも使われます。Google の Maglev ロードバランサもこの上に構築されています。

5.3TCP — 確実に順番通り届ける

TCP が提供するもの

  1. 信頼性:ACK と再送により、失われたデータを届け直す
  2. 順序保証:シーケンス番号で並べ直す
  3. フロー制御:受信側が処理しきれない量を送らない(受信ウィンドウ)
  4. 輻輳制御:ネットワーク全体が詰まらないよう送信量を調整
3-WAY HANDSHAKE
クライアント          サーバー
    │ ── SYN ────────► │
    │ ◄──── SYN+ACK ── │      ここまでで 1 RTT かかる
    │ ── ACK ────────► │
    │ ── データ ─────► │
📐 数字・公式

接続確立に 1 RTT、TLS を足すとさらに 1〜2 RTT。東京〜米国(RTT 120 ms)だと、データを 1 バイトも送る前に 250〜360 ms 消える。これが「接続を使い回す(Keep-Alive / コネクションプール)」が重要な理由です。

輻輳制御アルゴリズム

名前 特徴
Reno / CUBIC パケットロスを輻輳の合図とする。Linux 既定は CUBIC
BBR(Google 開発) 帯域と RTT を推定して制御。ロスの多い経路で大幅に高速。YouTube で採用
🔬 深掘り

スロースタート:接続直後は少しずつしか送れません(初期輻輳ウィンドウ 10 パケット ≒ 14 KB)。つまり新しい接続では最初の 14 KB だけが 1 RTT で届く。「重要な CSS は 14 KB 以内に」という Web 最適化の格言はここから来ています。

ヘッドオブラインブロッキング(TCP レベル):パケット 3 が失われると、パケット 4, 5 が届いていてもアプリケーションには渡されません。順序保証の代償です。

5.4UDP — 速いが保証しない

  • 送りっぱなし。順序も到達も保証しない。ハンドシェイクなし(0 RTT で送れる)
  • ヘッダが小さい(8 B、TCP は 20 B〜)

UDP が適する場面

  • DNS(1 往復で終わる、失敗したら再送すればいい)
  • 音声・ビデオ通話(古いフレームが届いても意味がない。落とした方が良い)
  • ゲーム(最新の位置情報だけが重要)
  • メトリクス送信(StatsD など。多少落ちても統計的に問題ない)

5.5QUIC / HTTP/3 — TCP の欠点を修正する

QUIC は UDP の上に、TCP 相当の信頼性 + TLS 1.3 を再実装したものです。

課題 TCP + TLS QUIC
接続確立 TCP 1 RTT + TLS の版・再開方式に依存 通常 1 RTT、再接続や 0-RTT は条件とリプレイリスクを確認
HOL ブロッキング ストリーム間で発生 ストリームごとに独立して解消
接続の識別 4 タプル(IP が変わると切れる) Connection ID(Wi-Fi ↔ 4G 切替でも継続)
実装場所 OS カーネル(更新が遅い) ユーザー空間(アプリ更新で改善できる)
🎤 面接

SCRIPT
「モバイルユーザーが多く、ネットワーク切替が頻繁なので HTTP/3 (QUIC) を選びます。
 Connection ID により Wi-Fi と LTE の切替で接続が切れず、パケットロスの多い環境でも
 ストリーム単位の独立により他のリソース取得がブロックされません。」

第 5 章 一問一答

TCP 接続確立に何 RTT かかるか。
1 RTT(3 ウェイハンドシェイク)。TLS 1.3 を足すと計 2 RTT。
UDP を選ぶ典型例を 3 つ。
DNS、リアルタイム音声・映像、メトリクス。
QUIC の最大の利点は。
ストリーム単位での HOL ブロッキング解消、接続確立の高速化、接続 ID による移動耐性。

CHAPTER 06HTTP と TLS

🎯 GOAL

HTTP のバージョン差、キャッシュ制御、TLS ハンドシェイクを説明できる。

6.1HTTP の基本

REQUEST / RESPONSE
リクエスト                         レスポンス
GET /users/42 HTTP/1.1             HTTP/1.1 200 OK
Host: api.example.com              Content-Type: application/json
Authorization: Bearer xxx          Cache-Control: max-age=60
Accept: application/json           ETag: "a1b2c3"

                                   {"id":42,"name":"Kota"}

メソッドの性質(面接で頻出)

メソッド 安全 (safe) 冪等 (idempotent) 意味
GET 取得。副作用なし
HEAD ヘッダのみ取得
PUT 完全置換(何回やっても同じ状態)
DELETE 削除(2 回目は既に無い=同じ状態)
POST 作成・任意の処理(2 回送ると 2 個できる
PATCH ❌※ 部分更新(設計次第で冪等にできる)
📐 数字・公式

冪等 (idempotent):同じ操作を何回実行しても、結果の状態が 1 回のときと同じであること。分散システムではリトライが避けられないので、冪等性は最重要概念です(第 18 章で詳述)。

ステータスコード(覚えるべきもの)

コード 意味 使いどころ
200 / 201 / 204 OK / 作成した / 中身なし
301 / 302 / 304 恒久リダイレクト / 一時リダイレクト / 未更新 304 はキャッシュ検証で必須
400 / 401 / 403 / 404 不正な要求 / 未認証 / 権限なし / 存在しない 401 と 403 の違いは頻出
409 / 412 / 422 競合 / 事前条件失敗 / 内容が不正 楽観ロックで 409, 412 を使う
429 レート制限超過 Retry-After ヘッダを付ける
500 / 502 / 503 / 504 サーバー内部エラー / 上流不正 / 過負荷・停止中 / 上流タイムアウト 503 と 504 の区別は運用で重要
⚠️ 落とし穴

リトライ可否はステータスコードだけでは決まりません。HTTP メソッドの冪等性、冪等性キーの有無、サーバーが処理を受け付けたか不明な状態、Retry-After、リトライ予算を合わせて判断します。500 でも一時障害なら再試行でき、502/503/504 でも非冪等な副作用を無条件に再送してはいけません。400 番台も 429 のように時間を置けば成功するものがあります。

6.2HTTP のバージョン

HTTP/1.1 HTTP/2 HTTP/3
下位層 TCP TCP QUIC (UDP)
多重化 △(通常は keep-alive で順番に処理。パイプラインは普及しなかった) ✅ 1 接続で多重化
ヘッダ圧縮 ❌(テキスト) ✅ HPACK ✅ QPACK
サーバープッシュ ✅(ただし現在は非推奨)
HOL ブロッキング アプリ層で発生 TCP 層で発生 解消
バイナリ テキスト バイナリ バイナリ
🎤 面接

gRPC は HTTP/2 の上に構築されており、多重化・双方向ストリーミングをそのまま活かします。

6.3HTTP キャッシュ(設計で必ず使う)

HEADERS
Cache-Control: public, max-age=3600, stale-while-revalidate=86400
ETag: "v3-a1b2c3"
Last-Modified: Wed, 21 Aug 2026 07:28:00 GMT
ディレクティブ 意味
public / private CDN でキャッシュ可 / ブラウザのみ可(ユーザー固有データは必ず private)
max-age=N N 秒間は再検証せず使ってよい
s-maxage=N CDN 用の max-age(ブラウザとは別に設定できる)
no-cache キャッシュはするが毎回サーバーに検証する(「キャッシュしない」ではない!)
no-store 一切保存しない(本当の意味でのキャッシュ禁止)
immutable 絶対に変わらない(ハッシュ付きファイル名に付ける)
stale-while-revalidate=N 期限切れでも N 秒はそれを返しつつ裏で更新

条件付きリクエスト(帯域を大幅に削減)

CONDITIONAL REQUEST
クライアント: GET /a.js   If-None-Match: "v3-a1b2c3"
サーバー:     304 Not Modified (ボディを送らない = 数十バイトで済む)
📐 数字・公式

静的アセットの定石:ファイル名にコンテンツハッシュを埋め込み(app.9f2c1a.js)、Cache-Control: max-age=31536000, immutable を付ける。更新時はファイル名が変わるので、キャッシュ無効化が不要になります。

6.4TLS — 通信の暗号化

TLS が保証する 3 つのこと

  1. 機密性:盗聴されない(対称鍵暗号 AES-GCM / ChaCha20)
  2. 完全性:改竄されない(AEAD / MAC)
  3. 認証:相手が本物である(証明書チェーン)
TLS 1.3 HANDSHAKE — 1 RTT
Client                             Server
  │─ ClientHello (鍵共有材料, 対応暗号) ──►│
  │◄─ ServerHello + 証明書 + Finished ────│   ← ここで鍵が確定
  │─ Finished ─────────────────────────►│
  │═══ 暗号化された HTTP ══════════════►│

TLS 1.2 は 2 RTT。TLS 1.3 は 1 RTT で、再接続時は 0-RTT も可能(ただし 0-RTT データはリプレイ攻撃の余地があるため、冪等な GET のみに使う)。

証明書の仕組み

CERTIFICATE CHAIN
ルート CA(ブラウザに埋め込み済み)
  └ 中間 CA
      └ サーバー証明書(example.com、公開鍵、有効期限、署名)

サーバーは秘密鍵を持ち、証明書の公開鍵と対応することを署名で示します。

🔬 深掘り

知っておくと差がつく用語

  • SNI (Server Name Indication):1 つの IP で複数ドメインの TLS を扱うための拡張。ハンドシェイクの平文部分にホスト名が乗るため、ECH(Encrypted Client Hello)で隠す動きがある
  • mTLS(相互 TLS):クライアントも証明書を出す。サービス間通信の認証(ゼロトラスト)で標準的
  • 証明書ピンニング:モバイルアプリで特定の証明書のみ信頼する。中間者攻撃対策だが運用が硬直する
  • TLS 終端:ロードバランサで復号し、内部は平文 or 内部 mTLS。CPU 負荷の集約が目的
🎤 面接

SCRIPT
「外部からの TLS は L7 ロードバランサで終端し、
 内部のサービス間通信は mTLS で相互認証します。証明書はサービスメッシュ(Envoy 等)が
 自動で配布・ローテーションする構成にします。」

第 6 章 一問一答

POST と PUT の最大の違いは。
PUT は冪等、POST は冪等でない。
no-cache の正しい意味は。
キャッシュはするが、使用前に必ずサーバーへ再検証する。
リトライ判断で見るものは。
ステータスコードだけでなく、冪等性、処理済みか不明か、Retry-After、リトライ予算、接続・タイムアウトの種類を組み合わせる。
TLS 1.3 のハンドシェイクは何 RTT か。
1 RTT(再接続時 0-RTT も可)。

CHAPTER 07DNS

🎯 GOAL

ドメイン名が IP に変わる過程と、DNS を設計に活用する方法を理解する。

7.1名前解決の流れ

RESOLUTION FLOW
ブラウザ
  │ ① ブラウザキャッシュ → OS キャッシュ → hosts ファイル
  ▼
リゾルバ(ISP or 8.8.8.8 / 1.1.1.1)
  │ ② キャッシュになければ再帰的に問い合わせ
  ├──► ルートサーバー (.)        「.com は c.gtld-servers.net に聞け」
  ├──► TLD サーバー (.com)       「example.com は ns1.example.com に聞け」
  └──► 権威サーバー (example.com) 「A レコードは 93.184.216.34 だ」
  ▼
IP アドレスが返る(TTL 付き)
📐 数字・公式

キャッシュがあれば 0〜数 ms、なければ 20〜200 ms。初回接続の体感速度に効くので、dns-prefetch や接続の事前確立(preconnect)が使われます。

7.2レコードの種類

種類 内容 用途
A / AAAA IPv4 / IPv6 アドレス 基本
CNAME 別名(wwwexample.com ルートドメインには使えない(ALIAS/ANAME で代替)
NS 権威サーバーの指定 委譲
MX メールサーバー メール
TXT 任意文字列 SPF/DKIM、ドメイン所有証明
SRV サービスのホストとポート 内部サービス検出
CAA 証明書を発行できる CA の制限 セキュリティ

7.3DNS をシステム設計に使う

1. グローバル負荷分散 (GSLB)

  • 地理ベース:日本からのクエリには東京の IP、欧州からは frankfurt の IP を返す
  • レイテンシベース:実測 RTT が最小のリージョンを返す
  • 加重ラウンドロビン:新バージョンに 5% だけトラフィックを流す(カナリア)
  • ヘルスチェック連動フェイルオーバー:落ちたリージョンの IP を返さなくする

2. サービスディスカバリ

Kubernetes 内部では service-name.namespace.svc.cluster.local で解決します。

⚠️ 落とし穴

DNS フェイルオーバーの限界。TTL を 60 秒にしても、TTL を無視するリゾルバやアプリがある(Java の旧 JVM は無期限キャッシュがデフォルトだった)。そのため「DNS だけで即座に切り替わる」ことは期待してはいけません。秒単位の切替が必要なら Anycast + ロードバランサを使います。

7.4TTL の設計

TTL 用途
30〜60 秒 フェイルオーバー対象、頻繁に変わるもの(ただし DNS 負荷とレイテンシが増える)
300〜3600 秒 一般的なサービス
86400 秒 ほぼ変わらないもの(MX, NS)
🎤 面接

移行時の定石:切替の 24〜48 時間前に TTL を 60 秒に下げておき、切替後に戻す。

第 7 章 一問一答

ルートドメインに CNAME を設定できない理由は。
RFC 上 CNAME は他のレコードと共存できず、ルートには SOA/NS が必須のため。
DNS でフェイルオーバーする際の注意点は。
TTL を無視するクライアントがあり、切替が即時に完了しない。
地理的に近いサーバーへ振り分ける方法を 2 つ。
GeoDNS(DNS で近い IP を返す)と Anycast(同一 IP を経路で最寄りへ)。

CHAPTER 08ロードバランサとプロキシ

🎯 GOAL

L4/L7 の違い、分散アルゴリズム、ヘルスチェックの設計を説明できる。

8.1なぜロードバランサが要るのか

  1. 負荷分散:複数台に均等に配る
  2. 可用性:落ちたサーバーを外す
  3. 抽象化:クライアントは 1 つの IP/ホスト名だけ知っていればよい
  4. 付加機能:TLS 終端、圧縮、レート制限、WAF、リトライ、認証

8.2L4 と L7

L4(トランスポート) L7(アプリケーション)
見るもの IP アドレスとポート HTTP ヘッダ、パス、Cookie、メソッド
できること TCP/UDP を素通しで転送 パスによるルーティング、TLS 終端、リトライ、キャッシュ、圧縮
速度 非常に速い(数百万 pps) 遅い(数万〜数十万 rps)
AWS NLB, LVS, Google Maglev AWS ALB, Nginx, Envoy, HAProxy, Google GFE
💡 たとえ話

L4 は「封筒の宛先だけ見て仕分ける郵便局」、L7 は「封を開けて中身を読み、内容に応じて適切な部署へ回す受付」。

🔬 深掘り

Google の実際の構成。外部からのトラフィックは Maglev(L4, ソフトウェアで ECMP + コンシステントハッシュ)GFE(L7, TLS 終端) → バックエンドサービス、という 2 段構成になっています。

8.3分散アルゴリズム

アルゴリズム 説明 向き・不向き
ラウンドロビン 順番に配る 単純。処理時間がバラバラだと偏る
加重ラウンドロビン 性能比で配分 異なるスペックの混在時
最小コネクション数 今一番暇な所へ 処理時間がばらつく場合に有効
最小応答時間 実測が速い所へ 良いが、指標が不安定になりがち
IP ハッシュ / コンシステントハッシュ 同じキーは同じサーバーへ キャッシュのヒット率を上げたいとき必須
Power of Two Choices ランダムに 2 台選び、空いている方へ 少ない観測で偏りを減らせる。負荷指標とワークロードに依存
🔬 深掘り

Power of Two Choices (P2C) は覚えておく価値が非常に高いです。完全ランダムだと最大負荷は O(log n / log log n) ですが、2 台から選ぶだけで O(log log n) に落ちます。実装も簡単で状態共有が不要。Nginx, Envoy, HAProxy などが採用しています。

8.4ヘルスチェック

種類 説明
Liveness(生存) プロセスが生きているか。失敗 → 再起動
Readiness(受付可能) トラフィックを受けられるか。失敗 → LB から外す(再起動はしない)
Startup(起動) 起動中の猶予。JVM のような遅い起動に必要
⚠️ 落とし穴 1

Readiness の中で DB への疎通確認をすると、DB が一瞬詰まった瞬間に全サーバーが同時に LB から外れ、サービス全断になります。→ 依存先のチェックは慎重に。「自分自身が動くか」だけを見るのが基本。

⚠️ 落とし穴 2

ヘルスチェックが軽すぎる(return 200 するだけ)と、実際は壊れているサーバーにトラフィックが流れ続けます。→ ディープヘルスチェックは監視・アラート用、シャローは LB 用と役割を分ける。

8.5その他のプロキシ

種類 位置 目的
リバースプロキシ サーバー側 負荷分散、TLS 終端、キャッシュ、隠蔽
フォワードプロキシ クライアント側 社内からの外向き通信の制御・監査
API ゲートウェイ 入口 認証、レート制限、ルーティング、集約、変換
サイドカープロキシ 各 Pod の隣 サービスメッシュ(mTLS、リトライ、可観測性)。例: Envoy
🎤 面接

SCRIPT
「入口は L4 LB で TCP を分散し、その後段の L7 プロキシで
 TLS 終端・パスベースルーティング・レート制限を行います。
 L7 の状態を持たせないことで、L7 レイヤ自体も水平にスケールできます。」

第 8 章 一問一答

L4 と L7 のどちらが速いか、なぜか。
L4。パケットを解析せず転送するだけだから。
Readiness と Liveness の違いは。
Readiness 失敗は LB から外す、Liveness 失敗は再起動する。
キャッシュサーバー群への振り分けに適したアルゴリズムは。
コンシステントハッシュ(同じキーが同じノードに行きヒット率が上がる)。
Power of Two Choices の利点は。
全体状態を知らずに、ランダム 2 台比較だけで負荷の偏りを劇的に減らせる。

CHAPTER 09API 設計

🎯 GOAL

面接で 3 分で API を定義でき、その設計判断を説明できる。

9.13 つのスタイル

REST RPC / gRPC GraphQL
発想 リソース(名詞)を URL で表す 手続き(動詞)を呼ぶ クライアントが必要な形を問い合わせる
形式 JSON over HTTP Protobuf over HTTP/2 クエリ言語
得意 公開 API、キャッシュしやすい サービス間通信(速い・型安全・ストリーミング) 画面ごとに必要データが違うクライアント
苦手 過剰取得/不足取得(over/under-fetching) ブラウザから直接呼びにくい(gRPC-Web が必要) キャッシュ困難、N+1、複雑なクエリのコスト制御
🎤 面接

定番の答え

SCRIPT
「クライアント(モバイル・Web)向けの公開 API は REST + JSON にします。HTTP キャッシュと
 CDN が効き、デバッグしやすいためです。内部のサービス間通信は gRPC にします。
 Protobuf によるスキーマ定義でバージョン互換を担保でき、HTTP/2 の多重化で
 コネクション効率が良く、ペイロードも小さいためです。」

9.2REST の設計原則

ENDPOINTS
GET    /v1/users              一覧
POST   /v1/users              作成
GET    /v1/users/{id}         取得
PATCH  /v1/users/{id}         部分更新
DELETE /v1/users/{id}         削除
GET    /v1/users/{id}/posts   サブリソース
POST   /v1/videos/{id}:publish  ← 動詞が必要な場合はカスタムメソッド(Google API 設計ガイド流)

原則

  • URL は名詞・複数形、階層は 2 段まで
  • 動詞は HTTP メソッドで表す
  • バージョンを URL かヘッダに入れる(/v1/, Accept: application/vnd.x.v1+json
  • フィルタ・ソート・ページングはクエリパラメータ

9.3ページネーション(面接頻出)

❌ オフセット方式

GET /posts?offset=100000&limit=20
  • 問題 1:DB は 100,020 行読んで 100,000 行捨てる → 深いページほど激遅
  • 問題 2:読んでいる間に新規投稿が入ると重複・欠落する

✅ カーソル(キーセット)方式

GET /posts?after=eyJpZCI6MTIzLCJ0cyI6MTY5OTk5fQ&limit=20
→ WHERE (created_at, id) < (:ts, :id) ORDER BY created_at DESC, id DESC LIMIT 20
  • インデックスで一発シーク → 深いページでも概ね安定したコスト
  • created_atid のような、安定して一意な複合ソートキーを使う
  • 新規挿入、更新、削除をまたぐ完全なスナップショットが必要なら、スナップショット境界もカーソルに含める
  • カーソルは署名付きの不透明トークンにし、クエリ条件・テナント・有効期限と結び付ける。Base64 は暗号化でも安全性でもない
📐 返却形式

JSON
{
  "items": [ ... ],
  "next_cursor": "eyJpZCI6...",   // null なら終端
  "has_more": true
}
⚠️ 落とし穴

総件数(total)は、データベース、インデックス、スナップショット、事前集計の有無でコストが変わります。厳密な COUNT が高い場合は、概算、非同期集計、あるいは返さない設計を選びます。

9.4冪等性キー(Idempotency Key)

決済のような「二重実行が致命的」な POST に必須のパターン。

REQUEST
POST /v1/payments
Idempotency-Key: 8f14e45f-ea...   ← クライアントが生成する UUID
{"amount": 1000, "currency": "JPY"}

サーバー側の処理

SERVER LOGIC
1. キー、リクエストハッシュ、状態(in_progress / succeeded / failed)、レスポンスを保存
2. 既存キーが succeeded なら、同じリクエストか検証して保存済みレスポンスを返す
3. in_progress なら同時実行を抑止し、ポーリングまたは再試行可能な応答を返す
4. 新規キーは一意制約とトランザクションで claim し、副作用と結果保存を安全に結び付ける
5. タイムアウトやクラッシュ後は、lease の期限切れ、照合、再実行・照会の方針を定義する
6. 保持期間は業務上の再送期間と、プロバイダの仕様に合わせて決める
⚠️ 落とし穴

リクエスト内容のハッシュも保存し、同じキーで異なる内容が来たら 422 を返すのが正しい実装です。

9.5その他の必須トピック

エラー形式(統一する)

JSON
{
  "error": {
    "code": "RESOURCE_EXHAUSTED",
    "message": "Rate limit exceeded. Retry after 30s.",
    "details": [{"@type": "RetryInfo", "retry_delay": "30s"}],
    "request_id": "req_01H..."
  }
}
📐 数字・公式

request_id を必ず返すこと。ユーザーからの問い合わせで、ログを一発で引けます。

バージョニングと互換性

  • 後方互換な変更(OK):フィールドの追加、任意パラメータの追加
  • 破壊的変更(NG):フィールドの削除・改名、型変更、必須化、意味の変更
  • Protobuf ではフィールド番号を絶対に再利用しないreserved を使う)

バルク / バッチ API

POST /v1/users:batchGet で N+1 を潰す。部分成功を許すなら、各要素にステータスを持たせる。

ロングランニング操作

POST /v1/videos:transcode → 202 Accepted, {"operation_id": "op_123"}
GET  /v1/operations/op_123 → {"done": false, "progress": 0.4}

第 9 章 一問一答

オフセットページネーションの 2 つの問題は。
深いページで性能劣化、データ挿入時の重複・欠落。
内部サービス間通信に gRPC を選ぶ理由を 3 つ。
Protobuf による型安全とスキーマ進化、HTTP/2 多重化、ペイロードが小さく高速。
決済 API を二重実行から守る方法は。
クライアント生成の冪等性キー、リクエストハッシュ、状態機械、同時実行 claim、結果照会、失敗・タイムアウト後の再試行方針を組み合わせる。

CHAPTER 10リアルタイム通信

🎯 GOAL

「サーバーからクライアントへ push したい」ときの選択肢を比較できる。

10.14 つの方式

方式 仕組み 遅延 サーバー負荷 使いどころ
ショートポーリング 5 秒ごとに GET 最大 5 秒 高(無駄なリクエスト多数) 更新頻度が低く実装を単純にしたい場合
ロングポーリング リクエストを保持し、更新時に返す 中(接続保持) WebSocket が使えない環境の代替
SSE (Server-Sent Events) HTTP で一方向のストリーム 通知、株価、進捗、LLM のトークン出力
WebSocket 双方向の永続接続 最低 中〜高 チャット、ゲーム、共同編集
💡 たとえ話

  • ショートポーリング = 「もう届いた?」と 5 秒おきに郵便受けを見に行く
  • ロングポーリング = 郵便受けの前で届くまで待ち、届いたら家に戻ってまた出てくる
  • SSE = 郵便配達員が家まで持ってきてくれる(一方通行)
  • WebSocket = 配達員と直通の電話回線を引く(双方向)

10.2選択の基準

DECISION TREE
サーバー → クライアントの一方向で十分?
  ├─ YES → SSE を選ぶ(HTTP なので LB・プロキシと相性が良く、自動再接続も仕様にある)
  └─ NO(双方向・低遅延が必要)→ WebSocket
       └─ 極端な低遅延・多少の欠損を許容(ゲーム)→ WebRTC / QUIC datagram
⚠️ 落とし穴

SSE の制約。HTTP/1.1 ではブラウザの同一ドメイン接続数(6)を消費します。HTTP/2 以降なら多重化されるので問題ありません。

10.3WebSocket を大規模に運用する設計

これは面接(チャットシステム設計)で必ず問われます。

ARCHITECTURE
              ┌── 接続サーバー #1 (10 万接続) ──┐
クライアント ──│                                  │
              ├── 接続サーバー #2 (10 万接続) ──┤
              └── 接続サーバー #3 (10 万接続) ──┘
                          │
                  「誰がどのサーバーに繋がっているか」
                          ▼
              ┌────────────────────────┐
              │ セッションレジストリ    │ Redis: user_id → server_id
              └────────────────────────┘
                          │
              ┌────────────────────────┐
              │ Pub/Sub(Redis/Kafka)  │ サーバー間でメッセージを中継
              └────────────────────────┘

設計上の論点

  1. 接続数の上限:1 台で 10 万〜100 万接続(C10M)。制約はファイルディスクリプタ数、メモリ(接続あたり数 KB〜数十 KB)、エフェメラルポート(サーバー側は宛先ポート固定なので上限は FD 側)
  2. ルーティング:ユーザー A → B に送るには、B がどのサーバーにいるか知る必要がある → レジストリ(Redis)or 全サーバーへの Pub/Sub ブロードキャスト
  3. ハートビート:NAT やロードバランサのアイドルタイムアウト(典型 60 秒)で接続が黙って切れる → 30 秒ごとの ping/pong 必須
  4. 再接続:指数バックオフ + ジッタ。再接続時は「最後に受け取った ID 以降」を取得して欠落を埋める(=オフライン中のメッセージ配信)
  5. デプロイ:全サーバーを同時に再起動すると全クライアントが同時に再接続(サンダリングハード) → 段階的にドレインし、再接続にジッタを入れる
🎤 面接

加点ポイント

SCRIPT
「WebSocket の接続は状態を持つので、接続サーバー層とビジネスロジック層を分離します。
 接続サーバーは『接続の保持とメッセージの中継』だけに責任を限定し、
 ユーザー ID → 接続サーバーのマッピングを Redis に持ちます。
 これにより、ロジック側を自由にデプロイしても接続が切れません。」

第 10 章 一問一答

通知配信に SSE を選ぶ理由は。
一方向で十分、HTTP なので既存インフラと相性が良く、自動再接続が仕様に含まれる。
WebSocket でハートビートが必要な理由は。
NAT/LB のアイドルタイムアウトによる無通知切断を検知・防止するため。
数千万接続をどう捌くか。
接続サーバー層を水平分割し、ユーザー→サーバーのマッピングを外部レジストリに持ち、Pub/Sub で中継する。