モバイルのバッテリーに優しいAPI設計:通信の多さを減らす
モバイルのバッテリーを節約するAPI設計:バッチ処理、キャッシュヘッダー、ペイロード削減でラジオの起動を減らし、画面の表示を速くして消費を抑える方法を学ぶ。

なぜチャッティなAPIはモバイルのバッテリーを消費するのか
「チャッティ」なAPIは、1つの画面を表示するために多数の小さなリクエストを発行させます。個々のリクエストは紙の上では安いように見えますが、携帯ではすぐに積み重なります。
最大のバッテリー負荷は多くの場合ネットワークリアの起動から来ます。セルラーやWi‑Fiのチップはデータ送受信のために高消費状態に切り替わります。アプリが短時間に多くのリクエストを投げると、ラジオは何度も起動し長く動作し続けます。各レスポンスが小さくても、繰り返しの起動は実際のエネルギーを消費します。
CPUの作業もあります。各リクエストはヘッダーの構築、TLS処理、JSONの解析、キャッシュ更新、結果のマージを行うアプリコードの実行を伴います。接続が切れれば、セットアップの作業が繰り返されます。
チャッティさはUIを遅く感じさせることもあります。予測可能な1つの読み込みではなく、呼び出しの連鎖を待つことになり、スピナーが長くなったり、部分的なレンダリングがジャンプしたり、ネットワークが弱いときにタイムアウトが増えます。バックグラウンド更新も同様に悪化します:リトライが増え、ラジオが何度も起き、バッテリー消費が増えます。
「モバイルのバッテリーのためのAPI設計」を実践的に考えると、単純です:同じUIをより少ない往復、より少ないバイト、より少ないバックグラウンド作業で表示すること。
効果は以下の具体的な変化で計測できます:
- 画面読み込みあたりのAPI呼び出しが減る
- 画面あたりのダウンロードバイト数が減る
- セルラーでの中央値と最悪ケースのインタラクティブまでの時間が改善する
- 同じ結果を得るためのバックグラウンドフェッチとリトライが減る
これらが改善されれば、応答性とバッテリー持続時間は通常一緒に良くなります。
何かを変える前に測るべきこと
リクエストをバッチしたりキャッシュを調整する前に、まずアプリが今何をしているかを明確に把握してください。最速で効く改善は通常いくつかの繰り返し原因を修正することですが、それを見つけるには実際の画面やフロー(単一のハッピーパスのテストではなく)を計測する必要があります。
高トラフィックの画面数個について、基本的なログを取りましょう:読み込みあたり何回リクエストが発生するか、どのエンドポイントが呼ばれるか、送受信バイト数(ヘッダーとボディを含む)、リトライとタイムアウト率、UIが使えると感じるまでの時間。可能ならネットワーク種別(Wi‑Fi vs セルラー)ごとにエラー率を分けてください。その差で安定した接続では見落とす問題が明らかになることがよくあります。
フォアグラウンドのトラフィックとバックグラウンドのトラフィックを分けてください。ある画面は静かに見えても、電話はバックグラウンドリフレッシュ、プッシュでの同期、分析アップロード、あるいは“念のため”のプリフェッチで忙しいかもしれません。別々に追跡して、誤った対象を最適化しないようにします。
ホットスポットは通常いくつかの瞬間に集中します:アプリ起動、ホーム/フィード画面、詳細画面(多くの呼び出しに広がる)、プル・トゥ・リフレッシュ。これらのうち1つを選んでエンドツーエンドで測定してください。
チームが「良い」を合意できるベースライン予算を設定しましょう。例:「注文追跡画面のコールドロードは、ステータス表示前に6リクエスト以下で、ダウンロードは250KB以下にする」など。
シンプルなKPIの組み合わせを始めに使うなら、(1) 画面ロードあたりのリクエスト数 と (2) リトライ率 を使ってください。リトライの削減は思ったよりバッテリーを節約することが多いです。繰り返しのリトライがラジオを長く起動させるからです。
ステップバイステップ:バッチでリクエストを減らす
チャッティなAPIは偶然に生まれやすいです。各ウィジェットが「自分の」データを読み込み、1画面で十数の小さな呼び出しが発生します。修正は多くの場合単純です:いつも一緒にロードされるものを特定し、少ない呼び出しで返すようにします。
まずは高トラフィックの1つの画面(ホーム、受信箱、注文一覧)をマッピングします。ファーストビューに何が表示されるかを書き出し、各UI要素がどのリクエストで賄われているかを追跡します。重複(同じユーザープロフィールを二度取得しているなど)や常に一緒に来る呼び出し(プロフィールと権限と未読カウントなど)をよく見つけます。
次に、確実に一緒に発生する呼び出しをグループ化します。一般に2つの選択肢があります:
- その画面専用の目的別エンドポイントを作る(多くの場合一番安定する)
- 小さく予測可能なリソースのリストを受け取るバッチエンドポイントを追加する
バッチは画面単位で境界を決め、キャッシュ、監視、デバッグがしやすいようにしてください。
バッチが厄介にならないためのルールがいくつかあります。今表示する必要のあるものだけを返し、「念のため」の完全オブジェクトは避けてください。いくつかの部分がオプションならそれを明示し、UIが重要な部分を素早くレンダリングできるようにします。部分的な失敗で全体の再試行が必要にならないようにレスポンスを設計してください。失敗した部分だけを再試行する方がずっと安価です。
バッテリーを節約するキャッシュヘッダー(帯域幅だけでない)
キャッシュはバッテリー機能です。各リクエストはラジオを起動し、CPUを使い、パースやアプリロジックを引き起こします。適切なキャッシュヘッダーは多くのリフレッシュを軽量なチェックに変えます。
最大の勝ち分は条件付きリクエストです。データを再ダウンロードする代わりに「変わったか?」と尋ね、変わっていなければサーバーは小さな 304 Not Modified を返します。
リソースのバージョンを表すレスポンスには ETag を使い、次回フェッチ時にクライアントが If-None-Match を送るようにしてください。ETag の生成が難しい場合は、単純な「更新日時」リソースには Last-Modified と If-Modified-Since がよく効きます。
滅多に変わらないデータはキャッシュ方針を明確にしてください。Cache-Control は実際の更新頻度に合わせてください。ユーザープロフィールなら短めの max-age が妥当かもしれませんが、アプリ設定や参照リスト、機能フラグは長めにできます。
アプリ側では 304 を高速パスとして扱ってください。JSONを解析しない(存在しない)、モデルを再構築しない、UIをちらつかせない。画面にあるものを保持し、インジケータだけを更新してください。
例:注文追跡画面が15秒ごとにステータスをポーリングする場合、レスポンスに ETag が含まれていれば多くのチェックは 304 で帰ります。アプリは最後のステータスを保持し、ステータスが変わったときだけ実際の処理を行います(例:「梱包済み」から「発送済み」へ)。
機密データについては意図的に扱ってください。キャッシュが自動的に危険というわけではありませんが、明確なルールが必要です。個人情報やトークンを含むレスポンスはプロダクト要件が許す場合以外キャッシュしないでください。ユーザー固有データには短い寿命を設定し、共有キャッシュに個人レスポンスが蓄積されないように(必要なら Cache-Control: private を使う)注意してください。
賢いペイロード:送る量を減らし、解析を減らす
追加の1バイトはモバイルでは帯域幅以上のコストを生みます。ラジオが長く起き、アプリがJSONをデコードしモデルを更新するCPU時間を消費します。APIの冗長な通信を減らす ことを狙うなら、ペイロードの削減は最速の勝ち筋です。
まず画面ごとにペイロードを監査してください。1つの画面(例えばホームフィード)を選び、レスポンスサイズをログに取り、フィールドごとに:クライアントはこれをレンダリングしているか、表示を決めるために使っているか?もし違うなら、そのエンドポイントから外してください。
リストでは小さな「サマリー」形状が通常十分です。リストの行は多くの場合 id、タイトル、ステータス、タイムスタンプだけで良く、完全な説明や長いノート、深くネストしたオブジェクトは不要です。詳細はユーザーが項目を開いたときだけ取得します。
いくつかの変更でペイロードはすぐに小さくなります:
- 繰り返される長い文字列の代わりに id や短い列挙コードを使う
- クライアントが安全に想定できる明白なデフォルトは再送しない
- 同じネストオブジェクトを各リスト項目で繰り返さない
- フィールド名と型を安定させ、クライアントの分岐や再パースを減らす
圧縮は特に遅いネットワークで役立ちますが、実際のデバイスでCPUトレードオフをテストしてください。GzipやBrotliは転送サイズを大きく下げることが多いですが、古い端末では非常に大きなレスポンスの解凍に目に見える時間がかかるかもしれません。最良の勝ち分はやはり最初から送るデータを減らすことです。
レスポンス形状を契約と考えてください。フィールド名と型が一貫していれば、クライアントはフォールバックを少なくでき、防御的なコードも減り、UIの滑らかさとバッテリー消費の低下に寄与します。
悪いネットワークとリトライを少なくする設計
モバイルアプリはリクエストを送信する時だけバッテリーを消費するわけではありません。失敗してリトライし、ラジオを再度起こし、作業を繰り返す時もバッテリーを消費します。APIが完璧なWi‑Fiを前提にしていると、実際のユーザーは不安定な4Gで代償を払います。
より少ないデータを要求しやすくしてください。クライアント側で「全部ダウンロードしてからフィルタする」より、サーバー側のフィルタやページングを優先します。画面が最後の20件のイベントだけを必要とするなら、そのクエリをサポートして、クライアントが何百行も取得して大半を捨てることがないようにします。
デルタ同期をサポートすると、アプリは「最後に見た後で何が変わった?」と尋ねられます。これは更新時刻や増分カウンタを返し、クライアントがその時点以降の更新と削除だけを要求するようにできます。
リトライは避けられませんが、更新を冪等にしてください。リトライで二重請求や二重登録、重複が起きないようにします。作成操作に対する冪等キーや、状態を設定する更新セマンティクス(「加える」ではなく「設定する」)が非常に有効です。
リトライが発生したときはリトライストームを避けてください。指数バックオフにジッタを使い、何千ものデバイスが同時にサーバーに押し寄せないようにし、電話が毎秒起きるのを防ぎます。
明確なエラーコードを返して、クライアントが何をすべきか判断できるようにしてください。401 は再認証、404 は通常リトライ停止、409 は状態の更新が必要、429 や 503 はバックオフが適切、などです。
クライアント側の振る舞いでAPIコストが増えるケース
良いAPI設計があっても、クライアント側が静かにネットワーク作業を増やすことがあります。モバイルでは追加のラジオ起動と稼働時間が、実際のバイト数よりもバッテリーに大きな影響を与えることが多いです。
滅多に変わらないデータはキャッシュしてください。プロフィール写真、機能フラグ、参照データ(国一覧、ステータス、カテゴリ)は毎回取得するべきではありません。妥当な寿命を与え、ディスクに保存し、必要時だけ更新してください。APIが検証(ETag や Last-Modified)をサポートするなら、素早い再チェックは完全なダウンロードよりずっと安価です。
別の一般的な問題は同時進行の重複リクエストです。アプリの2つの部分が同じリソースを同時に要求する(例:ヘッダーと設定画面が両方プロフィールを要求する)と、コアレスしていれば2回の呼び出しが発生し、2回解析し2回状態を更新します。ネットワークリクエストを単一の真実の源と扱い、複数の利用者はそれを待てるようにしてください。
バックグラウンドリフレッシュは意図的に行ってください。ユーザーがすぐに見るものを変えない、あるいは通知をトリガーしないなら、多くの場合待たせられます。毎回のアプリ再開でリフレッシュロジックを走らせないでください。短いクールダウンを入れ、データが最後に更新された時間を確認してください。
AppMasterでバックエンドを作る場合、画面に合わせたエンドポイント設計や一貫したスキーマ、制御されたキャッシュヘッダーにより、クライアントが多くの時間静かにしていられるようにサポートしやすくなります。
バッチ、キャッシュ、ペイロードでのよくある失敗
目標はラジオの起動を減らし、CPU作業を減らし、リトライを減らすことです。いくつかのパターンは節約を台無しにして、画面を遅く感じさせることさえあります。
バッチが逆効果になるとき
バッチは便利ですが、バッチが「全部ちょうだい」呼び出しになると問題です。サーバーが多くのテーブルを結合し、重い権限チェックを行い巨大なレスポンスを組み立てるなら、その単一リクエストは小さな複数リクエストより遅くなることがあります。モバイルでは1つの遅いリクエストがアプリを待たせ、ネットワークをアクティブに保ち、タイムアウトリスクを増やします。
健全なバッチは画面形状に合わせ、1つのビューが必要とするものだけ、明確な上限を設けます。レスポンスを1文で説明できないなら、そのエンドポイントはおそらく広すぎます。
ステールな画面を生むキャッシュ
明確な無効化戦略なしのキャッシュは古いデータ表示の悪循環を生みます:ユーザーは古いデータを見てプル・トゥ・リフレッシュをし、アプリは余分なフルリロードを行います。Cache-Control を使うなら、そのデータが何で更新されるか(作成/更新アクション、サーバーイベント、短い新鮮度ウィンドウなど)を計画して、クライアントがキャッシュレスポンスを信頼できるようにしてください。
ポーリングもバッテリーの罠です。厳しい5秒タイマーは何も変わらないときでもデバイスを忙しくします。可能ならサーバー駆動の更新に切り替えるか、積極的にバックオフしてください(変更がないときは間隔を伸ばす、バックグラウンドでは停止する)。
巨大なペイロードは静かなコストです。便利さのために巨大なネストオブジェクトを返すと、より多くのバイト、より多くのJSONパース、より多くのメモリ churn を生みます。画面に必要なものだけを送り、詳細はオンデマンドで取得してください。
最後に、バッチ内の部分的失敗を無視しないでください。サブ結果の1つが失敗してバッチ全体を再試行するとトラフィックが重複します。クライアントが失敗した部分だけを再試行できるように設計してください。
簡単なチェックリスト:バッチは境界化し、キャッシュの新鮮度を最初に定義し、厳しいポーリングを避け、ペイロードを削り、部分成功をサポートする。
リリース前の簡単チェックリスト
出荷前にネットワーク動作だけに集中した一回の見直しを行ってください。多くのバッテリー改善は驚きを取り除くことで得られます:起動を減らす、パースを減らす、バックグラウンドでのリトライを減らす。
上位3画面でこれを実行してください:
- コールドロードは小さく予測可能なリクエスト数で終わる(各アイテムのルックアップなどの隠れた追従呼び出しに注意する)
- レスポンスには明確なキャッシュルールが含まれる(適所に
ETagやLast-Modified)と、何も変わっていないときに304 Not Modifiedを返す - リストエンドポイントは境界化されている:安定したソート、ページング、デフォルトで不要フィールドを返さない
- リトライロジックはジッタ付きのバックオフを行い、自己修復しないエラーでは停止する;総リトライ時間に上限がある
- バックグラウンド更新は正当化されている;ユーザーが見る内容を明確に変えない限り常時ポーリングは避ける
簡単な現実チェック:ある画面を1回読み込み、機内モードにしてから再度開いてみてください。キャッシュされたコンテンツ、最後に知られている状態、フレンドリーなプレースホルダが表示されるなら、不必要な呼び出しを減らし、体感速度を改善している可能性が高いです。
例:注文追跡画面を安く読み込ませる
顧客がモバイルデータでバッテリー残量20%の状態で注文追跡アプリを開きます。彼らが欲しいのはひとつ:「今どこに荷物があるか?」画面は単純に見えますが、その背後のAPIトラフィックは思いのほか高コストです。
以前はアプリが画面を読み込むと一連のリクエストが飛び、UIはラジオの起動を待ち、いくつかの呼び出しは弱い接続でタイムアウトしていました。
典型的な「以前」のパターンは次のとおりです:
GET /orders/{id}で注文のサマリを取得GET /orders/{id}/itemsで行アイテムを取得GET /orders/{id}/historyでステータスイベントを取得GET /meでユーザープロフィールと設定を取得GET /settingsで表示ルール(通貨、日付形式)を取得
ここでUIを変えずに3つの変更を適用します。
まず、画面が必要とするものだけを1往復で返す単一のスクリーンエンドポイントを追加します:注文サマリ、最新ステータス、最近の履歴、アイテムのタイトル。次にプロフィールをキャッシュします:GET /me は ETag と Cache-Control: private, max-age=86400 を返し、多くの開封は高速な 304 になる(キャッシュが新鮮ならそもそもリクエストが発生しない場合もある)。三つ目にペイロードを細くします:アイテムリストは id, name, qty, thumbnail_url のみを送信し、製品説明や未使用のメタデータは含めません。
結果はより少ない往復、より少ないバイト、そしてネットワークが不安定なときのリトライが減ることです。電話のラジオはより長く眠っていられるようになり、そこにバッテリー節約の大部分が現れます。
ユーザーにとって見た目は何も変わりません。画面は同じですが、読み込みが速く感じ、応答性が良く、接続が不安定でも動作し続けます。
次のステップ:実践的な展開計画(とAppMasterが助けられる点)
早い勝ちを狙うなら、小さく始めて影響を検証してください。バッテリー節約は通常、ラジオ起動とパース作業を減らすことから来ており、大規模な書き換えが必要になることは稀です。
安全で測定可能、かつ戻しやすい3つの変更:
- 1つの画面をエンドツーエンドで計測する(リクエスト数、合計バイト、インタラクティブまでの時間、エラーとリトライ率)
- その画面のリクエストを1〜2回の呼び出しにバッチする(互換性のために古いエンドポイントは残す)
- トラフィックの多いGETエンドポイントに
ETagを追加して、クライアントがIf-None-Matchを使い304 Not Modifiedを受け取れるようにする
使用頻度が安定した機能(注文一覧やメッセージ受信箱など)を選んでください。可能ならサーバーサイドのフラグの背後に出荷し、数日間にわたって古い経路と新しい経路のKPIを比較してください。セッションあたりのリクエスト数減少、リトライ減少、アクティブユーザーあたりのダウンロードバイト減少を探します。
古いクライアントを壊さないようAPIとアプリのリリースを調整してください。実用的なルール:新しい振る舞いを先に追加し、クライアントを移行し、最後に古い振る舞いを削除する。キャッシュ動作を変える際は個人化データに注意し、共有キャッシュでユーザーが混ざらないようにしてください。
バックエンド変更を素早く試作して出荷したいなら、AppMaster(appmaster.io)は視覚的にデータをモデル化し、ドラッグ&ドロップでビジネスロジックを作り、要件が変わるたびに本番向けのソースコードを再生成するのに役立ちます。
まずは高トラフィック画面1つに対してバッチエンドポイント1つと主要GETに ETag を追加してみてください。数値が改善すれば、どこにさらに工数を投資するかが明確になります。
よくある質問
良い出発点は画面ごとに予算を決めて、実際のセッションを計測することです。多くのチームはコールドロード時にセルラーで4~8リクエスト程度を目標に始め、最悪の要因を潰したあとで厳しくしていきます。正しい数は、タイム・トゥ・インタラクティブ目標を確実に満たし、リトライや長時間のラジオ起動を引き起こさないものです。
複数の呼び出しが常に一緒に発生するならバッチは役に立ちますが、バッチが遅く大きくなってしまうと逆効果です。バッチレスポンスは画面単位で境界を決め、1つのリクエストが単一点の障害とならないようにしてください。定期的にタイムアウトしたり未使用データが多いなら、小さな焦点を絞った呼び出しに戻す方が良いです。
最初に試すべきは ETag と If-None-Match を使った条件付きリクエストです。多くのリフレッシュを小さな 304 Not Modified レスポンスに変えられます。加えて、Cache-Control をデータの実際の更新頻度に合わせて設定すると、不要なネットワークワークを避けられます。ETag が難しい場合は、更新時刻ベースのリソースには Last-Modified と If-Modified-Since が実用的な代替です。
ETag はリソースの「バージョン」チェックとして信頼性が高く、内容の変更がタイムスタンプにきれいに対応しない場合に向きます。サーバーに明確な更新時刻があり、タイムスタンプ精度で十分なら Last-Modified でも良いです。どちらか一つだけ実装できるなら、無駄なダウンロードを避ける精度の高い ETag をおすすめします。
APIを変える前に画面やセッション単位で計測してください。エンドポイント単位だけでなく、リクエスト数、送受信バイト数(ヘッダーとボディを含む)、リトライ、タイムアウト、インタラクティブまでの時間をログに取り、フォアグラウンドとバックグラウンドを分けて計測します。多くの場合、数画面やフローが繰り返しの起動を生んでいます。
バッチレスポンスは各サブ結果が独立して成功/失敗できるように設計し、クライアントが失敗した部分だけを再試行できるように十分なエラー詳細を含めてください。1つが壊れたからといってバッチ全体を再要求するのは避け、二重のトラフィックや余分なラジオ起動を防ぎます。
画面が今レンダリングするものだけにレスポンスを絞り、リストはサマリー形状を使うのが最も早く効果が出ます。重いフィールドや滅多に使わないものは詳細用エンドポイントに移し、ユーザーが項目を開いたときだけ取得します。これにより転送バイトとJSONのパース量、モデル更新が減り、CPUとバッテリーの節約になります。
指数バックオフにジッタを混ぜてリトライの総時間を上限し、電話が数秒ごとに目を覚ますのを防ぎます。書き込み操作は冪等にして、リトライで重複や二重課金が起きないようにします。さらに、サーバーが明確なステータスコードを返すとクライアントはいつリトライを止めるべきか判断できます。
短いポーリング間隔は、何も変わっていなくてもラジオとCPUを忙しくします。もしポーリングが必要なら、変更がない場合は間隔を延ばす、バックグラウンドでは一時停止するなどを行ってください。可能ならイベント駆動(サーバー通知)に切り替え、何か新しいものがあるときだけアプリを起動させます。
AppMasterでは画面志向のエンドポイントを作り、レスポンススキーマの一貫性を保ちやすくできます。これによりバッチやペイロードの整形が容易になり、条件付きリクエストをサポートするヘッダーを返すことでクライアントが短い「変化なし」レスポンスを得られるようになります。高トラフィック画面1つから始めて、1つのバッチエンドポイントと主要GETに ETag を追加して、リクエストやリトライの減少を計測するのが実用的なアプローチです。


