リトライポリシー — 再試行ポリシー — は、モバイルアプリケーションが失敗したネットワークコールを自動的にいつどのように再試行するかを決定する一連のルールです。不安定な接続や一時的なサーバーエラーが発生した場合、適切に設計された再試行ポリシーは、ユーザーの介入なしにアプリケーションの信頼性を向上させます。Google Developer Relations(2025)の調査によると、リトライポリシーを適切に実装すると、ネットワーク操作が頻繁なモバイルアプリケーションにおいて、失われるリクエストの割合が40~60%減少します。
重要なポイント
リトライポリシー — ネットワークリクエストが失敗した際のクライアントの動作を定義するソフトウェア戦略です。どのエラーを再試行すべきか、何回、どのような遅延で、そしていつ試行を停止するかを決定します。モバイルアプリケーションでは、モバイルネットワークの不安定性やサーバー側の一時的な障害の可能性があるため、再試行ポリシーは非常に重要です。
基本的なリトライポリシーには、再試行の最大回数(maxRetries)、初期遅延(baseDelay)、およびバックオフ戦略の3つのパラメーターが含まれます。さらに、再試行をトリガーするHTTPステータスコードのリストと、すべての試行を中止するためのタイムアウトを指定できます。
マーティン・クレップマンの著書「Designing Data-Intensive Applications」によると、分散システムにおける障害の50%は一時的なものであり、再試行によって解決されます。これにより、リトライポリシーは、サーバーアーキテクチャを変更することなくモバイルアプリケーションの耐障害性を向上させる、最も効果的で低コストな方法の1つとなっています。
一時的なエラー(再試行可能)は、リトライポリシーが対応すべき唯一の障害タイプです。これには、接続タイムアウト(SocketTimeoutException)、サーバーの一時的な利用不可(HTTP 503、502)、DNSエラーが含まれます。永続的なエラー(HTTP 400、401、403、404)は、ネットワークやサーバーではなくリクエスト自体に問題があることを示すため、再試行しても意味がありません。
AWS Architecture Blogの調査によると、エラーを再試行可能と再試行不可能に正しく分類することは、リトライポリシーを設計する上で最も重要な決定です。HTTP 401で冪等でないリクエストを再試行するとアカウントがロックされる可能性があり、HTTP 400を再試行するとデータが重複する可能性があります。再試行するコードのリストは常に明示的に設定してください。
固定間隔 — 最も単純な戦略で、各再試行は同じ時間間隔で行われます。たとえば、遅延が2秒の場合、アプリケーションは2、2、2秒後にリクエストを再試行します。固定間隔は実装が簡単で予測可能ですが、大量の障害が発生した場合にサーバーに均一な負荷がかかります。
増分間隔 — 遅延が再試行ごとに線形に増加します:1回目の再試行は1秒後、2回目は2秒後、3回目は3秒後、という具合です。この戦略は、繰り返し障害が発生した場合にサーバーにより多くの回復時間を与えますが、同時に障害が発生する多くのクライアントにとっては依然として予測可能です。
| 戦略 | 遅延の計算式 | 累積時間(3回の試行) | 適用 |
|---|---|---|---|
| 固定 | delay = D | 3 × D | 単純なシナリオ、ローカルタイムアウト |
| 増分 | delay = N × D | 6 × D | 段階的な負荷軽減 |
| 指数 | delay = D × 2^N | 7 × D | 大量障害、クラウドサービス |
| 指数 + ジッター | delay = random(0, D × 2^N) | 変動 | 高負荷、マイクロサービス |
戦略の選択はアプリケーションの性質によって異なります。モバイルデバイスでのデータ同期のバックグラウンドタスクには、ジッターを伴う指数戦略が最適です。サーバーとユーザーデバイスへの負荷を最小限に抑えながら、成功確率を最大化します。
エクスポネンシャルバックオフ — 再試行間の遅延が試行ごとに倍になる戦略です。初期遅延が1秒の場合、遅延の順序は1、2、4、8、16秒となります。これにより、サーバーは指数関数的に増加する回復時間を得られます。
ジッター — 複数のクライアントからの同期した再試行リクエスト(サンダリングハード問題)を防ぐランダムな遅延変動です。ジッターがない場合、同じリトライポリシーを持つ何千ものクライアントが同時にリクエストを再試行し、サーバーにピーク負荷が発生します。ジッターは再試行を時間的に分散させます。
Kotlinのコルーチンを使用すると、メインスレッドをブロックせずにジッターを伴うエクスポネンシャルバックオフを実装できます。kotlinx-coroutinesのretry関数は、再試行条件とリクエスト本体を含むブロックを受け取り、遅延と試行回数を自動的に管理します。
suspend fun RetryPolicy.executeWithRetry(
block: suspend () -> Result<T>
): Result<T> {
var lastError: Throwable? = null
repeat(maxRetries + 1) { attempt ->
try {
return block()
} catch (e: Exception) {
if (!isRetriable(e) || attempt == maxRetries) {
return Result.failure(e)
}
val delay = (baseDelayMs * (1 shl attempt))
.toLong()
val jitteredDelay = (delay * (0.5 + Random.nextDouble())).toLong()
delay(jitteredDelay)
lastError = e
}
}
return Result.failure(lastError!!)
}
executeWithRetry関数は、ネットワークコールを含むラムダを受け取り、エクスポネンシャルバックオフとジッターで実行します。エラーが再試行可能でない場合、または最大試行回数を超えた場合、関数はエラーを返します。遅延は0.5から1.5のランダムな係数で乗算され、再試行が均一に分散されます。
サーキットブレーカー — サービスが長期間利用できない場合に、無制限の再試行リクエストを防ぐデザインパターンです。エラー数がしきい値を超えると、サーキットブレーカーはOPEN状態に移行し、リクエストを実行せずに即座にエラーを返し、サーバーに回復時間を与えます。
モバイルアプリケーションでは、計画メンテナンスやキャリアネットワーク障害のためにAPIが利用できない場合に、サーキットブレーカーが特に役立ちます。サーキットブレーカーがないと、アプリケーションは無制限の再試行でバッテリーとトラフィックを消費し、ユーザーエクスペリエンスを低下させ、デバイスのバッテリー寿命を縮めます。
サーキットブレーカーには3つの状態があります:CLOSED(通常動作、リクエストが実行される)、OPEN(障害、リクエストがブロックされる)、HALF_OPEN(回復確認のためのテストリクエスト)。OPEN状態で指定されたタイムアウト後、ブレーカーはHALF_OPENに移行し、1つのリクエストを実行します。成功した場合はCLOSEDに、失敗した場合はOPENに戻ります。
class CircuitBreaker(
private val failureThreshold: Int = 3,
private val timeoutMs: Long = 30000
) {
private var state = State.CLOSED
private var failureCount = 0
private var lastFailureTime: Long = 0
suspend fun T.protect(block: suspend () -> T): T {
checkState()
return try {
val result = block()
onSuccess()
result
} catch (e: Exception) {
onFailure()
throw e
}
}
}
Kotlinでのサーキットブレーカーの実装には、エラーカウンターと回復タイマーが含まれます。protectメソッドは、ラップされたリクエストを実行する前に現在の状態を確認し、障害発生時にエラーカウンターを更新します。failureThresholdに達すると、timeoutMsが経過するまで、すべてのリクエストは即座に拒否されます。
モバイルネットワークには、リトライポリシーを特に重要にする特性があります。Wi-Fiとモバイルデータの切り替え、地下鉄やトンネルでの信号喪失、キャリアレベルでの一時的なブロック — これらすべてのシナリオはリクエスト障害を引き起こしますが、再試行によって正常に処理できます。
Androidでは、RetrofitライブラリとOkHttpがInterceptorを介して組み込みの再試行メカニズムを提供します。iOSでは、URLSessionConfigurationとカスタムデリゲーションによってタスクが解決されます。クロスプラットフォーム開発の場合、Ktor(KMP)には設定可能な戦略による組み込みの再試行サポートが含まれています。
Combine — Appleのリアクティブプログラミングフレームワークです。Combineのretryオペレーターは、エラー時にパブリッシャーを指定された回数繰り返しますが、再試行間の遅延を設定することはできません。完全なリトライポリシーには、遅延を伴うcatchとflatMapのカスタム組み合わせが使用されます。
extension Publisher {
func retryWithBackoff(
retries: Int = 3,
baseDelay: TimeInterval = 1.0
) -> AnyPublisher<Output, Failure> {
return self.catch { error -> AnyPublisher in
guard retries > 0 else {
return Fail(error).eraseToAnyPublisher()
}
return Just(())
.delay(for: .seconds(baseDelay), scheduler: DispatchQueue.main)
.flatMap { self.retryWithBackoff(
retries: retries - 1,
baseDelay: baseDelay * 2
) }
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
CombineのPublisherのためのretryWithBackoff拡張は、カウンターを減らしながら遅延を倍にする再帰呼び出しによってエクスポネンシャルバックオフを実装します。delayオペレーターは再試行間に一時停止を作成し、catchはエラーをインターセプトして、再試行するか失敗を返すかを決定します。
最初の誤り — 冪等性を確認せずにリクエストを再試行すること。サーバーがリソースを作成したが、ネットワーク障害のために確認応答を返さなかった場合、再試行によって重複が作成されます。POSTリクエストの場合は、ヘッダーに冪等性キー(Idempotency-Key)を常に使用するか、GET、PUT、DELETEのみを再試行するようにしてください。
2番目の誤り — 無限に再試行すること。常に最大試行回数(モバイルアプリケーションの場合は3~5回)と、すべての試行の合計タイムアウトを設定してください。無限の再試行はバッテリーを消耗させ、特にデータベース移行やAPI変更時にサーバーに寄生負荷をかけます。
3番目の誤り — アプリケーションのコンテキストを無視すること。ユーザーがアプリケーションを閉じたりバックグラウンドに移動した場合、アクティブなリトライポリシーは適切にキャンセルされるべきです。画面が閉じられたときに再試行を自動キャンセルするには、SupervisorScopeを使用したコルーチン、またはUIライフサイクルを使用したCombineを使用してください。
4番目の誤り — 再試行をログに記録しないこと。ロギングがなければ、いくつのリクエストが再試行されたか、どのようなエラーが発生したか、リトライポリシーがどの程度効果的かを知ることはできません。メトリクスを追加してください:再試行回数、再試行後の成功率、遅延の分布。これらのデータは、特定のアプリケーションに最適な戦略パラメーターを調整するのに役立ちます。
よくある質問
ほとんどのシナリオでは、再試行の最適な回数は3~5回です。バックグラウンド同期の場合は5~7回が許容されます。インタラクティブなリクエスト(フォーム送信など)の場合は、3回以下にしてください。再試行回数を増やしても成功確率は上がりませんが、ユーザーのバッテリーとデータトラフィックを消費します。
エクスポネンシャルバックオフ — 再試行間の遅延が倍になることです:1秒、2、4、8、16... サーバーが過負荷の場合、最初の数回の再試行間の短い一時停止によりサーバーは迅速に応答でき、その後の再試行ごとに増加する一時停止によりサーバーはより多くの回復時間を得られます。
一時的なエラーのみを再試行してください:408(Request Timeout)、429(Too Many Requests)、502(Bad Gateway)、503(Service Unavailable)、504(Gateway Timeout)。4xxエラー(408と429を除く)はクライアントの問題を示しており、再試行しても意味がなく、ユーザーデータにとって危険な場合があります。
リトライポリシーは、障害発生時の単一リクエストの再試行を管理します。サーキットブレーカーは、サービスとの接続状態を管理します:エラーが蓄積されると、回路を開いて(OPEN)新しいリクエストをブロックします。リトライは個別のコールレベルで機能し、サーキットブレーカーはサービス統合レベルで機能します。
テストするには、AndroidではNetworkInterceptor(OkHttp)、iOSではURLProtocol(URLSession)を使用してネットワーク障害をシミュレートします。エラー頻度、利用不可の期間、応答コードを設定します。MockWebServer(OkHttp)やOHHTTPStubs(iOS)を使用したユニットテストは、実際のネットワークなしで再試行ロジックを検証します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。