モバイルネットワーキングと接続管理
通信の遅さや電池消費の多くは接続の張り方で決まる。接続プール・多重化・リトライ・ピンニングの原理を押さえれば、無線環境でも速く安全なアプリを設計できる。
- URLSessionとOkHttpはどちらも接続プールとHTTP/2多重化を前提とし、1本のTLSコネクション上で複数リクエストを並行させることで確立コストと無線ウェイクアップを削減する。
- リトライは冪等メソッドに限り、指数バックオフとジッターを併用する。証明書ピンニングはリーフではなく中間CAの公開鍵をSPKIハッシュで固定し、バックアップピンを必ず併記する。
- 無線は送信後に高電力状態をタイマーで維持するため、通信をまとめて短時間に集約する設計が電力効率とレイテンシの両面で効く。
接続は「張り直す」たびに高くつく
モバイルアプリのネットワーク層で最も見落とされがちなコストは、リクエスト本体の転送ではなく接続の確立です。新規のHTTPS接続を1本張るには、DNS解決、TCPの3ウェイハンドシェイク、そしてTLSハンドシェイク(TLS 1.2で2往復、TLS 1.3で1往復)が必要で、RTTが往復100〜300msに達するモバイル回線では、データが1バイトも流れないうちに数百ミリ秒を消費します。さらに無線モジュールは通信のたびに低電力状態から高電力状態へ遷移するため、接続確立の多発は電池も直接削ります。
このコストを償却する仕組みが接続プールです。一度確立したTCP/TLSコネクションを閉じずに保持し、後続のリクエストで再利用することで、2回目以降はハンドシェイクを丸ごと省けます。iOSのURLSessionとAndroidで事実上の標準となっているOkHttpは、いずれもこのプールを内部に持ち、アプリ開発者が明示的に管理しなくても接続を再利用します。ポイントは、この再利用を最大化するために同じ設定のクライアントインスタンスを使い回すことです。
OkHttpClientやURLSessionをリクエストのたびにnewすると、そのインスタンスが持つ接続プールも毎回別物になり、確立済みコネクションを再利用できません。OkHttpのOkHttpClientはアプリ全体で1つ(またはごく少数)を共有し、newBuilder()で設定を差分変更した派生インスタンスはプールとスレッドプールを親と共有します。URLSessionもURLSession.sharedか、用途ごとに1つ生成したセッションを保持して使い回すのが原則です。
HTTP/2多重化と接続プールの相互作用
HTTP/1.1では1本のコネクション上で1つのリクエスト・レスポンスを完結させてから次を送る必要があり(キープアライブでも直列)、並行性を得るにはコネクションを複数本張るしかありませんでした。ブラウザやモバイルクライアントは同一ホストへ最大6本前後を張って並列化していましたが、これは無線ウェイクアップとハンドシェイクを本数分だけ増やす、モバイルには不利な手法です。
HTTP/2はこれをストリーム多重化で解決します。1本のTCP/TLSコネクション上に複数の論理ストリームを流し、リクエストとレスポンスをフレーム単位でインターリーブして並行送受信します。結果として、同一ホストへの接続は原則1本に集約され、その1本の中で数十のリクエストが同時進行できます。
| 観点 | HTTP/1.1 | HTTP/2 |
|---|---|---|
| 同一ホストへの接続 | 並列化のため複数本(例: 6本) | 原則1本に集約 |
| 並行リクエスト | コネクション本数が上限 | 1本の中でストリーム多重化 |
| ヘッダ | 毎回テキストで冗長に送信 | HPACKで圧縮し差分のみ |
| モバイルでの利点 | 接続数分の無線起床が必要 | ウェイクアップ・ハンドシェイクを最小化 |
ただし多重化には固有の弱点があります。HTTP/2はTCP上で動くため、TCPレベルのパケットロスが起きると、その1パケットの再送を待つ間、同じコネクション上の全ストリームが停止します(TCP HoL、Head-of-Line ブロッキング)。パケットロスが起きやすいモバイル回線ではこの影響が無視できず、UDP上に再構築して各ストリームを独立させたHTTP/3(QUIC)が、まさにこの問題を解消する目的で設計されています。URLSessionは対応環境でHTTP/3を自動ネゴシエートし、OkHttpも設定によりQUICを利用できます。
リトライとバックオフの正しい設計
不安定な回線では失敗は常態であり、リトライは必須です。しかし無条件のリトライは二重処理とサーバー過負荷を招くため、何を・いつ・どう再試行するかを厳密に決める必要があります。
第一の原則は冪等性です。同じリクエストが二度処理されても結果が変わらないメソッド(GET・PUT・DELETE・HEAD)は安全に再送できますが、POSTは一般に非冪等で、決済や投稿の二重実行を招きます。OkHttpの自動リトライ(retryOnConnectionFailure)も、接続確立自体に失敗したケースなど、リクエストがサーバーに到達していないと判断できる場合に限定されます。到達後に応答がタイムアウトした場合は「処理済みか不明」なため、POSTを安易に再送してはいけません。冪等キー(Idempotency-Key ヘッダ等)をサーバーと取り決め、重複を受信側で排除する設計が定石です。
第二の原則は指数バックオフとジッターです。失敗のたびに待機時間を倍々に伸ばし、さらにランダムな揺らぎを加えます。
待機 = min(base * 2^attempt, cap) にランダムなジッターを加える
# 例: base=0.5s, cap=30s
attempt 0 → 約0.5s
attempt 1 → 約1s
attempt 2 → 約2s
...
attempt 6 → 約30s(capで頭打ち)
ジッターが欠かせないのは、障害から復旧した瞬間に多数の端末が同時刻に再送を始めると、負荷が同期して集中するサンダリングハード問題が起きるためです。待機時間に乱数を混ぜることで再送のタイミングを分散させます。加えて、サーバーがRetry-Afterヘッダや429(Too Many Requests)を返した場合は、クライアント側の計算よりサーバーの指示を優先するのが正しい挙動です。
「タイムアウトしたPOSTを再送してよいか」は頻出の論点です。答えは、リクエストがサーバーに届いたか不明である以上そのまま再送は危険で、冪等キーによる重複排除を前提にして初めて安全になる、というものです。あわせて、リトライ総数の上限・累積タイムアウト(デッドライン)を設けないと、失敗し続けるリクエストが無線を起こし続けて電池を消耗させる点も押さえます。
証明書ピンニングの原理と運用
TLSは通常、OSに組み込まれた信頼された認証局(CA)が署名した証明書であれば正当と見なします。しかしこれは、正規のCAが誤発行した証明書や、端末に不正に追加されたルートCAを使った中間者攻撃を防げません。証明書ピンニングは、アプリが「この接続先はこの鍵であるべき」という情報を自前で保持し、OSの信頼判断に加えて追加検証する手法です。
実務でピンで固定するのは証明書そのものではなく、公開鍵情報(SPKI)のSHA-256ハッシュです。証明書はやがて更新されますが、鍵ペアを引き継げばSPKIハッシュは変わらないため、証明書更新のたびにアプリを再配布せずに済みます。
サーバーのリーフ(末端)証明書のSPKIだけをピンすると、証明書更新で鍵が変わった瞬間に全ユーザーが接続不能になります。実務では、より寿命が長く安定した中間CAの公開鍵をピンし、さらに別系統の鍵をバックアップピンとして最低1つ併記します。バックアップがないと、鍵の緊急ローテーションが必要になったときにアプリ更新が全端末へ行き渡るまでサービスが止まります。ピン一式には必ず有効期限を設け、期限切れ時の挙動(フェイルオープンかクローズか)も決めておきます。
Androidではres/xmlのNetwork Security Configにピンを宣言的に記述でき(<pin-set>に複数SPKIとexpirationを指定)、OkHttpではCertificatePinnerをビルダーに渡して実装します。iOSではURLSessionDelegateのurlSession(_:didReceive:completionHandler:)でサーバートラストを受け取り、証明書チェーンからSPKIハッシュを算出して期待値と照合します。いずれの実装でも、照合失敗時は接続を確立させずエラーとして扱うのが原則です。
無線状態の遷移と省電力下の挙動
モバイルの電力設計を理解するうえで核心なのが、無線は送信直後にすぐスリープしないという事実です。セルラーの無線(RRC状態機械)は、少量のデータを送っただけでも高電力状態へ遷移し、その後一定のタイマー(テールタイム)が満了するまで高電力を維持してからスリープに戻ります。このテールは数秒に及ぶこともあり、「1KBを1回送るだけ」でも、その後の待機で無視できない電力を消費します。
横にスクロール
ここから導かれる設計指針は明確です。通信は細切れに散発させず、短時間にまとめて集約するほど、無線が高電力状態にいる総時間が短くなり電力効率が上がります。プリフェッチやログのバッチ送信、複数APIコールの束ね(HTTP/2多重化はこれを1接続で実現します)が有効なのはこのためです。逆に、数秒おきのポーリングは毎回テールタイムを踏むため最悪のパターンになります。
OSは低電力モード(iOS Low Power Mode/Android バッテリーセーバー)やアプリがバックグラウンドにあるとき、裁量的な通信を遅延・停止させます。iOSのURLSessionにはisDiscretionaryやallowsExpensiveNetworkAccess/allowsConstrainedNetworkAccessといった制御があり、緊急でない転送はOSが電力とネットワーク状況を見て好機にまとめて実行します。バックグラウンドでの大容量転送はフォアグラウンドのセッションではなくバックグラウンドセッション(iOS)やWorkManager(Android)に委ねるのが原則で、これらの背景制約は/mobile-development/mobile-background-execution-limits/で扱うOSのバックグラウンド実行制限と地続きです。
さらに、回線種別(Wi-Fi/セルラー)や帯域は接続中に切り替わります。エレベーターやトンネルの出入りでインターフェースが遷移すると、既存のTCPコネクションは無効化されます。これに追従する仕組みがコネクションマイグレーションで、HTTP/3(QUIC)は接続をIPアドレスではなくコネクションIDで識別するため、Wi-Fiからセルラーへ切り替わってもハンドシェイクをやり直さずに通信を継続できます。TCPベースのHTTP/2ではこの遷移で張り直しが発生するため、モバイルではQUIC対応の価値が特に大きくなります。
まとめ
モバイルネットワーキングの要諦は、接続を「使い捨てにしない」ことに尽きます。接続プールで確立コストを償却し、HTTP/2多重化で1本のコネクションに並行リクエストを集約し、リトライは冪等なものに限って指数バックオフとジッターで安全に再送します。セキュリティ面では、中間CAの公開鍵をSPKIハッシュでピンしバックアップピンを併記することで、中間者攻撃への耐性と運用の継続性を両立させます。そしてこれらの技術判断は、無線のテールタイムや省電力制御という電力側の制約と常に一体で、通信の集約は/mobile-development/mobile-battery-power-profiling/で扱う電力効率に直結します。トリガー型のプッシュとプル型同期を組み合わせる設計は/mobile-development/mobile-push-notification-architecture/や/mobile-development/mobile-offline-first-sync/と合わせて理解すると、通信層全体の見通しが立ちます。
モバイル開発の記事ガイド
モバイルネットワーキングと接続管理を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
モバイル開発
比較で見る軸
難易度: advanced / カテゴリ: モバイル開発 / タグ数: 6
導入後に効く点
リトライは冪等メソッドに限り、指数バックオフとジッターを併用する。証明書ピンニングはリーフではなく中間CAの公開鍵をSPKIハッシュで固定し、バックアップピンを必ず併記する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- モバイル開発
- タグ数
- 6
判断チェックリスト
- 自社の用途が「モバイル開発 / ネットワーク」に近いか確認する。
- 強みである「URLSessionとOkHttpはどちらも接続プールとHTTP/2多重化を前提とし、1本のTLSコネクション上で複数リクエストを並行させることで確立コストと無線ウェイクアップを削減する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。