Retry Policy — politica de cereri repetate — un set de reguli care determină când și cum o aplicație mobilă repetă automat apelurile de rețea eșuate. La o conexiune instabilă sau erori temporare de server, o politică de repetare bine concepută crește fiabilitatea aplicației fără intervenția utilizatorului. Conform cercetării Google Developer Relations (2025), implementarea corectă a Retry Policy reduce procentul de cereri pierdute cu 40-60% în aplicațiile mobile cu operații frecvente de rețea.
Principalele puncte
Retry Policy — este o strategie software care determină comportamentul clientului la eșecul unei cereri de rețea: ce erori să repete, de câte ori, cu ce întârziere și când să oprească încercările. În aplicațiile mobile, politica de repetare este critică din cauza instabilității rețelelor mobile și a posibilelor defecțiuni temporare de pe partea serverului.
Retry Policy de bază include trei parametri: numărul maxim de repetări (maxRetries), întârzierea inițială (baseDelay) și strategia de creștere a întârzierii (backoff strategy). Suplimentar, poate fi specificată o listă de coduri HTTP la care să se reacționeze prin repetare și un timeout pentru întreruperea tuturor încercărilor.
Conform cărții „Designing Data-Intensive Applications" a lui Martin Kleppmann, 50% dintre defecțiunile din sistemele distribuite sunt temporare și se rezolvă la o nouă încercare. Acest lucru face din Retry Policy una dintre cele mai eficiente și mai ieftine modalități de a crește toleranța la defecțiuni a unei aplicații mobile fără modificări ale arhitecturii serverului.
Erorile temporare (retriable) — singurul tip de defecțiune la care Retry Policy trebuie să reacționeze. Acestea includ timeout-uri de conexiune (SocketTimeoutException), indisponibilitatea temporară a serverului (HTTP 503, 502) și erori DNS. Erorile permanente — HTTP 400, 401, 403, 404 — nu are sens să fie repetate, deoarece indică o problemă în cerere, nu în rețea sau server.
Conform cercetării AWS Architecture Blog, clasificarea corectă a erorilor în retriable și non-retriable este cea mai importantă decizie la proiectarea Retry Policy. Repetarea unei cereri non-idempotente cu HTTP 401 poate duce la blocarea contului, iar repetarea HTTP 400 — la crearea de duplicate de date. Configurați întotdeauna lista de coduri pentru repetare în mod explicit.
Fixed interval — cea mai simplă strategie: fiecare repetare se execută după același interval de timp. De exemplu, cu o întârziere de 2 secunde, aplicația repetă cererea după 2, 2, 2 secunde. Fixed interval este simplu de implementat și previzibil, dar creează o încărcare uniformă pe server la defecțiuni în masă.
Incremental interval — întârzierea crește liniar cu fiecare repetare: prima repetare după 1 secundă, a doua după 2, a treia după 3 și așa mai departe. Această strategie oferă serverului mai mult timp pentru recuperare la defecțiuni repetate, dar rămâne previzibilă pentru mulți clienți care eșuează simultan.
| Strategie | Formula întârzierii | Timp cumulativ (3 încercări) | Aplicare |
|---|---|---|---|
| Fixed | delay = D | 3 × D | Scenario simple, timeout-uri locale |
| Incremental | delay = N × D | 6 × D | Reducerea treptată a încărcării |
| Exponential | delay = D × 2^N | 7 × D | Defecțiuni în masă, servicii cloud |
| Exponential + Jitter | delay = random(0, D × 2^N) | variabil | Încărcare mare, microservicii |
Alegerea strategiei depinde de natura aplicației. Pentru sarcinile de fundal de sincronizare a datelor pe dispozitive mobile, strategia exponential cu jitter este optimă — oferă cea mai mare probabilitate de succes cu încărcare minimă pe server și dispozitivul utilizatorului.
Exponential backoff — strategia în care întârzierea între repetări se dublează cu fiecare încercare. Dacă întârzierea inițială este de 1 secundă, secvența întârzierilor va fi 1, 2, 4, 8, 16 secunde. Aceasta oferă serverului un timp exponențial crescător pentru recuperare.
Jitter — abaterea aleatorie a întârzierii care previne cererile repetate sincrone de la mai mulți clienți (problema thundering herd). Fără jitter, o mie de clienți cu aceeași Retry Policy vor repeta cererile simultan, creând o încărcare de vârf pe server. Jitter dispersează repetările în timp.
Corutinele Kotlin permit implementarea exponential backoff cu jitter fără blocarea firului principal. Funcția retry din kotlinx-coroutines acceptă condiția de repetare și un bloc cu corpul cererii, gestionând automat întârzierile și numărul de încercări.
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!!)
}
Funcția executeWithRetry acceptă o lambda cu un apel de rețea și o execută cu exponential backoff și jitter. Dacă eroarea nu poate fi repetată (non-retriable) sau numărul maxim de încercări a fost depășit, funcția returnează eroarea. Întârzierea este înmulțită cu un coeficient aleator de la 0.5 la 1.5 pentru o distribuție uniformă a repetărilor.
Circuit Breaker — un pattern de proiectare care previne cererile repetate infinite la indisponibilitatea prelungită a serviciului. Când numărul de erori depășește pragul, Circuit Breaker trece în starea OPEN și returnează imediat eroarea fără a executa cererea, oferind serverului timp pentru recuperare.
În aplicațiile mobile, Circuit Breaker este deosebit de util la indisponibilitatea API din cauza întreținerii planificate sau a defecțiunilor rețelei operatorului. Fără el, aplicația va consuma baterie și trafic pentru încercări repetate infinite, înrăutățind experiența utilizatorului și reducând durata de funcționare a dispozitivului pe baterie.
Trei stări Circuit Breaker: CLOSED (funcționare normală, cererile se execută), OPEN (refuz, cererile sunt blocate) și HALF_OPEN (cerere de test pentru verificarea recuperării). După un timeout specificat în starea OPEN, comutatorul trece în HALF_OPEN și execută o cerere — la succes revine în CLOSED, la eșec — în 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
}
}
}
Implementarea Circuit Breaker în Kotlin conține un contor de erori și un timer de recuperare. Metoda protect verifică starea curentă înainte de a executa cererea blocată și actualizează contorul de erori la defecțiuni. După atingerea pragului failureThreshold, toate cererile sunt imediat respinse până la expirarea timeoutMs.
Rețelele mobile au caracteristici care fac Retry Policy deosebit de importantă. Comutarea între Wi-Fi și date mobile, pierderea semnalului în metrou și tuneluri, blocări temporare la nivelul operatorului — toate aceste scenarii duc la eșecuri ale cererilor care sunt gestionate cu succes prin încercări repetate.
Pe Android, biblioteca Retrofit și OkHttp oferă un mecanism încorporat RetryPolicy prin Interceptor. Pe iOS, problema se rezolvă prin URLSessionConfiguration și delegare personalizată. Pentru dezvoltarea cross-platformă, Ktor (KMP) conține suport încorporat pentru retry cu strategii configurabile.
Combine — framework-ul Apple pentru programare reactivă. Operatorul retry din Combine repetă publisher-ul de un număr specificat de ori la eroare, dar nu permite configurarea întârzierii între repetări. Pentru o Retry Policy completă, se folosește o combinație personalizată de catch și flatMap cu întârziere.
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()
}
}
Extensia retryWithBackoff pentru Publisher în Combine implementează exponential backoff printr-un apel recursiv cu decrementarea contorului și dublarea întârzierii. Operatorul delay creează o pauză între repetări, iar catch interceptează eroarea și decide dacă să încerce din nou sau să returneze failure.
Prima eroare — repetarea cererilor fără verificarea idempotenței. Dacă serverul a creat o resursă, dar nu a returnat confirmarea din cauza unei defecțiuni de rețea, cererea repetată va crea un duplicat. Pentru cererile POST, utilizați întotdeauna o cheie idempotentă (Idempotency-Key) în antet sau treceți la retry doar pentru GET, PUT și DELETE.
A doua eroare — repetări infinite (retry forever). Stabiliți întotdeauna un număr maxim de încercări (3-5 pentru aplicații mobile) și un timeout total pentru toate încercările. Repetările infinite consumă bateria și creează o încărcare parazită pe server, în special la migrarea bazelor de date sau modificarea API.
A treia eroare — ignorarea contextului aplicației. Dacă utilizatorul a închis aplicația sau a trecut în modul fundal, Retry Policy active trebuie anulate corect. Utilizați corutine cu SupervisorScope sau Combine cu ciclul de viață UI pentru anularea automată a repetărilor la închiderea ecranului.
A patra eroare — nelogificarea încercărilor repetate. Fără logificare nu veți ști câte cereri au fost repetate, ce erori au apărut și cât de eficientă este Retry Policy dvs. Adăugați metrici: numărul de repetări, succesul după repetare, distribuția întârzierilor. Aceste date vă vor ajuta să ajustați parametrii optimi ai strategiei pentru aplicația specifică.
Întrebări frecvente
Numărul optim de repetări — 3-5 încercări pentru majoritatea scenariilor. Pentru sincronizarea în fundal sunt permise 5-7 încercări, pentru cererile interactive (de exemplu, trimiterea unui formular) — nu mai mult de 3. Un număr mai mare de repetări nu crește probabilitatea de succes, dar consumă bateria și traficul utilizatorului.
Exponential backoff — dublarea întârzierii între încercările repetate: 1 secundă, 2, 4, 8, 16 și așa mai departe. Dacă serverul este supraîncărcat, pauza scurtă între primele repetări îi permite să răspundă rapid, iar pauza crescătoare cu fiecare încercare ulterioară oferă serverului tot mai mult timp pentru recuperare.
Repetați doar erorile temporare: 408 (Request Timeout), 429 (Too Many Requests), 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout). Erorile 4xx (cu excepția 408 și 429) indică probleme ale clientului — repetarea lor nu are sens și poate fi periculoasă pentru datele utilizatorului.
Retry Policy gestionează repetarea unei singure cereri la eșec. Circuit Breaker gestionează starea conexiunii cu serviciul: la acumularea de erori, deschide circuitul (OPEN) și nu permite noi cereri. Retry funcționează la nivelul unui apel individual, Circuit Breaker — la nivelul integrării cu serviciul.
Pentru testarea Retry Policy, utilizați NetworkInterceptor (OkHttp) pe Android și URLProtocol (URLSession) pe iOS pentru simularea defecțiunilor de rețea. Setați parametrii: frecvența erorilor, durata indisponibilității și codurile de răspuns. Testele unitare cu MockWebServer (OkHttp) sau OHHTTPStubs (iOS) verifică logica de repetare fără rețea reală.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și