Retry Policy w programowaniu mobilnym — istota, strategie i zasada działania

Autor: IT Sectr Opublikowano: 2026-03-11 Czas czytania: 10 min

Retry Policy — polityka ponownych żądań — zestaw reguł określających, kiedy i jak aplikacja mobilna automatycznie powtarza nieudane wywołania sieciowe. Przy niestabilnym połączeniu lub tymczasowych błędach serwera dobrze zaprojektowana polityka powtórzeń zwiększa niezawodność aplikacji bez udziału użytkownika. Według badań Google Developer Relations (2025), prawidłowa implementacja Retry Policy zmniejsza odsetek utraconych żądań o 40-60% w aplikacjach mobilnych z częstymi operacjami sieciowymi.

Najważniejsze

  • Retry Policy — strategia automatycznych powtórzeń żądań przy awariach sieci lub tymczasowych błędach serwera.
  • Exponential backoff — metoda zwiększania opóźnienia między powtórzeniami w celu zmniejszenia obciążenia serwera.
  • Jitter — losowe odchylenie opóźnienia zapobiegające efektowi „stada" (thundering herd).
  • Idempotentność — kluczowy wymóg bezpiecznych powtórzeń: ponowne żądanie nie powinno powodować efektów ubocznych.
  • Circuit Breaker — mechanizm zatrzymania powtórzeń przy długotrwałej niedostępności usługi w celu oszczędzania zasobów.

Czym jest Retry Policy?

Retry Policy — to programowa strategia określająca zachowanie klienta przy awarii żądania sieciowego: które błędy należy powtarzać, ile razy, z jakim opóźnieniem i kiedy przerwać próby. W aplikacjach mobilnych polityka powtórzeń jest krytycznie ważna ze względu na niestabilność sieci mobilnych i możliwe tymczasowe awarie po stronie serwera.

Podstawowa Retry Policy obejmuje trzy parametry: maksymalną liczbę powtórek (maxRetries), opóźnienie początkowe (baseDelay) i strategię zwiększania opóźnienia (backoff strategy). Dodatkowo można określić listę kodów HTTP, na które należy reagować powtórzeniem, oraz limit czasu dla przerwania wszystkich prób.

Według książki „Designing Data-Intensive Applications" Martina Kleppmanna, 50% awarii w systemach rozproszonych ma charakter tymczasowy i jest usuwanych przy ponownej próbie. To sprawia, że Retry Policy jest jednym z najskuteczniejszych i najtańszych sposobów zwiększenia odporności na awarie aplikacji mobilnej bez zmian w architekturze serwera.

Które błędy warto powtarzać

Błędy tymczasowe (retriable) — jedyny typ awarii, na który Retry Policy powinna reagować. Należą do nich przekroczenia limitu czasu połączenia (SocketTimeoutException), tymczasowa niedostępność serwera (HTTP 503, 502) i błędy DNS. Błędy stałe — HTTP 400, 401, 403, 404 — nie ma sensu powtarzać, ponieważ wskazują na problem w żądaniu, a nie w sieci lub serwerze.

Według badań AWS Architecture Blog, prawidłowa klasyfikacja błędów na retriable i non-retriable — to najważniejsza decyzja przy projektowaniu Retry Policy. Powtórzenie nieidempotentnego żądania HTTP 401 może doprowadzić do zablokowania konta, a powtórzenie HTTP 400 — do tworzenia duplikatów danych. Zawsze konfiguruj listę kodów do powtórzenia jawnie.

Główne strategie powtórzeń

Fixed interval — najprostsza strategia: każde powtórzenie wykonywane jest po tym samym czasie. Na przykład przy opóźnieniu 2 sekund aplikacja powtarza żądanie po 2, 2, 2 sekundach. Fixed interval jest prosty w implementacji i przewidywalny, ale tworzy równomierne obciążenie serwera przy masowych awariach.

Incremental interval — opóźnienie zwiększa się liniowo z każdym powtórzeniem: pierwsze po 1 sekundzie, drugie po 2, trzecie po 3 i tak dalej. Ta strategia daje serwerowi więcej czasu na odzyskanie sprawności przy powtarzających się awariach, ale nadal jest przewidywalna dla wielu jednocześnie zawodzących klientów.

StrategiaWzór opóźnieniaCzas skumulowany (3 próby)Zastosowanie
Fixeddelay = D3 × DProste scenariusze, lokalne przekroczenia czasu
Incrementaldelay = N × D6 × DStopniowe zmniejszanie obciążenia
Exponentialdelay = D × 2^N7 × DMasowe awarie, usługi w chmurze
Exponential + Jitterdelay = random(0, D × 2^N)zmiennyDuże obciążenie, mikrousługi

Wybór strategii zależy od charakteru aplikacji. Dla tła zadań synchronizacji danych na urządzeniach mobilnych optymalna jest strategia exponential z jitter — daje największe prawdopodobieństwo sukcesu przy minimalnym obciążeniu serwera i urządzenia użytkownika.

Exponential Backoff i Jitter

Exponential backoff — strategia, w której opóźnienie między powtórzeniami podwaja się z każdą próbą. Jeśli początkowe opóźnienie wynosi 1 sekundę, to sekwencja opóźnień będzie wynosić 1, 2, 4, 8, 16 sekund. To daje serwerowi wykładniczo rosnący czas na odzyskanie sprawności.

Jitter — losowe odchylenie opóźnienia, które zapobiega synchronicznym ponownym żądaniom od wielu klientów (problem thundering herd). Bez jittera tysiąc klientów z tą samą Retry Policy będzie powtarzać żądania jednocześnie, tworząc szczytowe obciążenie serwera. Jitter rozkłada powtórzenia w czasie.

Implementacja w Kotlin z korutynami

Korutyny Kotlin pozwalają zaimplementować exponential backoff z jitter bez blokowania głównego wątku. Funkcja retry z kotlinx-coroutines przyjmuje warunek powtórzenia i blok z treścią żądania, automatycznie zarządzając opóźnieniami i liczbą prób.

kotlin
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!!)
}

Funkcja executeWithRetry przyjmuje lambdę z wywołaniem sieciowym i wykonuje ją z exponential backoff i jitter. Jeśli błąd nie podlega powtórzeniu (nie retriable) lub przekroczono maksymalną liczbę prób, funkcja zwraca błąd. Opóźnienie jest mnożone przez losowy współczynnik od 0.5 do 1.5 dla równomiernego rozkładu powtórzeń.

Circuit Breaker i rezygnacja z powtórzeń

Circuit Breaker — wzorzec projektowy, który zapobiega nieskończonym ponownym żądaniom przy długotrwałej niedostępności usługi. Gdy liczba błędów przekroczy próg, Circuit Breaker przechodzi w stan OPEN i natychmiast zwraca błąd bez wykonywania żądania, dając serwerowi czas na odzyskanie sprawności.

W aplikacjach mobilnych Circuit Breaker jest szczególnie przydatny przy niedostępności API z powodu planowej konserwacji lub awarii sieci operatora. Bez niego aplikacja będzie zużywać baterię i transfer danych na nieskończone ponowne próby, pogarszając doświadczenie użytkownika i skracając czas pracy urządzenia na baterii.

Schemat stanów Circuit Breaker

Trzy stany Circuit Breaker: CLOSED (normalna praca, żądania są wykonywane), OPEN (odmowa, żądania są blokowane) i HALF_OPEN (próbne żądanie do sprawdzenia odzyskania sprawności). Po określonym czasie w stanie OPEN przełącznik przechodzi w HALF_OPEN i wykonuje jedno żądanie — przy sukcesie wraca do CLOSED, przy porażce — do OPEN.

kotlin
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
        }
    }
}

Implementacja Circuit Breaker w Kotlin zawiera licznik błędów i timer odzyskiwania. Metoda protect sprawdza bieżący stan przed wykonaniem blokowanego żądania i aktualizuje licznik błędów przy awariach. Po osiągnięciu progu failureThreshold wszystkie żądania są natychmiast odrzucane do czasu upłynięcia timeoutMs.

Retry Policy w aplikacjach mobilnych

Sieci mobilne mają cechy, które czynią Retry Policy szczególnie ważną. Przełączanie między Wi-Fi a danymi mobilnymi, utrata sygnału w metrze i tunelach, tymczasowe blokady na poziomie operatora — wszystkie te scenariusze prowadzą do awarii żądań, które są skutecznie obsługiwane przez ponowne próby.

Na Android biblioteka Retrofit i OkHttp zapewniają wbudowany mechanizm RetryPolicy przez Interceptor. Na iOS zadanie rozwiązuje się przez URLSessionConfiguration i niestandardowe delegowanie. Do programowania międzyplatformowego Ktor (KMP) zawiera wbudowaną obsługę retry z konfigurowalnymi strategiami.

Implementacja na iOS z Combine

Combine — framework Apple do programowania reaktywnego. Operator retry w Combine powtarza publisher określoną liczbę razy przy błędzie, ale nie pozwala konfigurować opóźnienia między powtórzeniami. Do pełnowartościowej Retry Policy używa się niestandardowej kombinacji catch i flatMap z opóźnieniem.

swift
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()
    }
}

Rozszerzenie retryWithBackoff dla Publisher w Combine implementuje exponential backoff przez rekurencyjne wywołanie z zmniejszaniem licznika i podwajaniem opóźnienia. Operator delay tworzy pauzę między powtórzeniami, a catch przechwytuje błąd i decyduje, czy spróbować ponownie, czy zwrócić failure.

Typowe błędy przy Retry Policy

Pierwszy błąd — powtarzanie żądań bez sprawdzenia idempotentności. Jeśli serwer utworzył zasób, ale nie zwrócił potwierdzenia z powodu awarii sieci, ponowne żądanie utworzy duplikat. Dla żądań POST zawsze używaj idempotentnego klucza (Idempotency-Key) w nagłówku lub przechodź na retry tylko dla GET, PUT i DELETE.

Drugi błąd — nieskończone powtórzenia (retry forever). Zawsze ustawiaj maksymalną liczbę prób (3-5 dla aplikacji mobilnych) i całkowity limit czasu na wszystkie próby. Nieskończone powtórzenia rozładowują baterię i tworzą pasożytnicze obciążenie serwera, szczególnie przy migracji baz danych lub zmianie API.

Trzeci błąd — ignorowanie kontekstu aplikacji. Jeśli użytkownik zamknął aplikację lub przeszedł w tryb tła, aktywne Retry Policy powinny być poprawnie anulowane. Używaj korutyn z SupervisorScope lub Combine z cyklem życia UI do automatycznego anulowania powtórzeń przy zamknięciu ekranu.

Czwarty błąd — nielogowanie ponownych prób. Bez logowania nie dowiesz się, ile żądań zostało powtórzonych, jakie błędy występowały i jak skuteczna jest twoja Retry Policy. Dodaj metryki: liczba powtórzeń, skuteczność po powtórzeniu, rozkład opóźnień. Te dane pomogą dostroić optymalne parametry strategii do konkretnej aplikacji.

Często zadawane pytania

Ile razy powtarzać żądanie w aplikacji mobilnej?

Optymalna liczba powtórek — 3-5 prób dla większości scenariuszy. Do synchronizacji w tle dopuszczalne jest 5-7 prób, do żądań interaktywnych (np. wysyłanie formularza) — nie więcej niż 3. Większa liczba powtórzeń nie zwiększa prawdopodobieństwa sukcesu, ale zużywa baterię i transfer danych użytkownika.

Czym jest exponential backoff w prostych słowach?

Exponential backoff — to podwajanie opóźnienia między ponownymi próbami: 1 sekunda, 2, 4, 8, 16 i tak dalej. Jeśli serwer jest przeciążony, krótka pauza między pierwszymi powtórzeniami pozwala mu szybko odpowiedzieć, a rosnąca pauza z każdą kolejną próbą daje serwerowi coraz więcej czasu na odzyskanie sprawności.

Które kody HTTP warto powtarzać?

Powtarzaj tylko tymczasowe błędy: 408 (Request Timeout), 429 (Too Many Requests), 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout). Błędy 4xx (oprócz 408 i 429) wskazują na problemy klienta — powtarzanie ich nie ma sensu i może być niebezpieczne dla danych użytkownika.

Czym różni się Retry Policy od Circuit Breaker?

Retry Policy zarządza powtarzaniem jednego żądania przy awarii. Circuit Breaker zarządza stanem połączenia z usługą: przy nagromadzeniu błędów rozłącza obwód (OPEN) i nie dopuszcza nowych żądań. Retry działa na poziomie pojedynczego wywołania, Circuit Breaker — na poziomie integracji z usługą.

Jak testować Retry Policy na urządzeniach mobilnych?

Do testowania Retry Policy używaj NetworkInterceptor (OkHttp) na Android i URLProtocol (URLSession) na iOS do symulacji awarii sieci. Ustaw parametry: częstotliwość błędów, czas niedostępności i kody odpowiedzi. Testy jednostkowe z MockWebServer (OkHttp) lub OHHTTPStubs (iOS) sprawdzają logikę powtórzeń bez rzeczywistej sieci.

Podsumowanie

  • Retry Policy — strategia automatycznych powtórzeń żądań sieciowych przy tymczasowych awariach z konfigurowalnymi parametrami opóźnienia i liczby prób.
  • Exponential backoff z jitter — podstawowa strategia dla aplikacji mobilnych, zmniejszająca obciążenie serwera przy masowych awariach i zapobiegająca efektowi thundering herd.
  • Idempotentność — obowiązkowy warunek bezpiecznego powtarzania żądań nie-GET: bez niej powtórzenie tworzy duplikaty danych lub niepożądane efekty uboczne.
  • Circuit Breaker uzupełnia Retry Policy, zapobiegając nieskończonym powtórzeniom przy długotrwałej niedostępności usługi i oszczędzając zasoby urządzenia.
  • Klasyfikacja błędów — podział na retriable (503, 502, timeout) i non-retriable (400, 401, 403) jest krytycznie ważny dla poprawnego działania polityki powtórzeń.
  • Maksymalnie 3-5 powtórzeń w scenariuszach interaktywnych i do 7 dla synchronizacji w tle — optymalna wartość dla aplikacji mobilnych według Google Developer Relations.
  • Zalecenie — zaimplementuj Retry Policy z exponential backoff, Circuit Breaker i logowaniem dla wszystkich żądań sieciowych w aplikacji mobilnej.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również