Retry Policy — política de reintentos — es un conjunto de reglas que determinan cuándo y cómo una aplicación móvil reintenta automáticamente las llamadas de red fallidas. Con conexiones inestables o errores temporales del servidor, una política de reintentos bien diseñada mejora la confiabilidad de la aplicación sin intervención del usuario. Según una investigación de Google Developer Relations (2025), la implementación correcta de Retry Policy reduce el porcentaje de solicitudes perdidas en un 40–60% en aplicaciones móviles con operaciones de red frecuentes.
Ideas clave
Retry Policy es una estrategia de software que define el comportamiento del cliente ante un fallo de solicitud de red: qué errores deben reintentarse, cuántas veces, con qué demora y cuándo dejar de intentarlo. En aplicaciones móviles, la política de reintentos es críticamente importante debido a la inestabilidad de las redes móviles y los posibles fallos temporales del servidor.
Una Retry Policy básica incluye tres parámetros: número máximo de reintentos (maxRetries), demora inicial (baseDelay) y la estrategia de backoff. Adicionalmente, se puede especificar una lista de códigos de estado HTTP que deben provocar un reintento y un tiempo de espera para abortar todos los intentos.
Según el libro “Designing Data-Intensive Applications” de Martin Kleppmann, el 50% de los fallos en sistemas distribuidos son temporales y se resuelven con un reintento. Esto convierte a Retry Policy en una de las formas más efectivas y económicas de mejorar la tolerancia a fallos de una aplicación móvil sin cambios en la arquitectura del servidor.
Los errores temporales (retriable) son el único tipo de fallo al que Retry Policy debe responder. Estos incluyen tiempos de espera de conexión (SocketTimeoutException), indisponibilidad temporal del servidor (HTTP 503, 502) y errores de DNS. Los errores permanentes — HTTP 400, 401, 403, 404 — no deben reintentarse, ya que indican un problema en la solicitud, no en la red o el servidor.
Según el AWS Architecture Blog, la clasificación correcta de errores en retriable y no retriable es la decisión más importante al diseñar una Retry Policy. Reintentar una solicitud no idempotente con HTTP 401 puede llevar al bloqueo de la cuenta, mientras que reintentar HTTP 400 puede crear datos duplicados. Configure siempre explícitamente la lista de códigos para reintentar.
Intervalo fijo es la estrategia más simple: cada reintento se realiza después del mismo intervalo de tiempo. Por ejemplo, con una demora de 2 segundos, la aplicación reintenta la solicitud después de 2, 2, 2 segundos. El intervalo fijo es simple de implementar y predecible, pero crea una carga uniforme en el servidor durante fallos masivos.
Intervalo incremental — la demora aumenta linealmente con cada reintento: primer reintento después de 1 segundo, segundo después de 2, tercero después de 3, y así sucesivamente. Esta estrategia le da al servidor más tiempo para recuperarse ante fallos repetidos, pero sigue siendo predecible para muchos clientes que fallan simultáneamente.
| Estrategia | Fórmula de demora | Tiempo acumulado (3 intentos) | Aplicación |
|---|---|---|---|
| Fijo | delay = D | 3 × D | Escenarios simples, tiempos de espera locales |
| Incremental | delay = N × D | 6 × D | Reducción gradual de carga |
| Exponencial | delay = D × 2^N | 7 × D | Fallos masivos, servicios en la nube |
| Exponencial + Jitter | delay = random(0, D × 2^N) | variable | Alta carga, microservicios |
La elección de la estrategia depende de la naturaleza de la aplicación. Para tareas en segundo plano de sincronización de datos en dispositivos móviles, la estrategia exponencial con jitter es óptima — proporciona la mayor probabilidad de éxito con la mínima carga en el servidor y el dispositivo del usuario.
Exponential backoff es una estrategia en la que la demora entre reintentos se duplica con cada intento. Si la demora inicial es de 1 segundo, la secuencia de demoras será 1, 2, 4, 8, 16 segundos. Esto le da al servidor un tiempo de recuperación exponencialmente creciente.
Jitter es una desviación aleatoria de la demora que evita solicitudes de reintento simultáneas de múltiples clientes (problema de thundering herd). Sin jitter, mil clientes con la misma Retry Policy reintentarían solicitudes simultáneamente, creando una carga máxima en el servidor. Jitter distribuye los reintentos en el tiempo.
Las corutinas de Kotlin permiten implementar exponential backoff con jitter sin bloquear el hilo principal. La función retry de kotlinx-coroutines acepta una condición de reintento y un bloque con el cuerpo de la solicitud, manejando automáticamente las demoras y el número de intentos.
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!!)
}
La función executeWithRetry toma una lambda con una llamada de red y la ejecuta con exponential backoff y jitter. Si el error no es retriable o se excede el número máximo de intentos, la función devuelve un error. La demora se multiplica por un factor aleatorio de 0.5 a 1.5 para una distribución uniforme de los reintentos.
Circuit Breaker es un patrón de diseño que evita solicitudes de reintento infinitas durante una indisponibilidad prolongada del servicio. Cuando el número de errores supera un umbral, Circuit Breaker pasa al estado OPEN y devuelve inmediatamente un error sin ejecutar la solicitud, dando tiempo al servidor para recuperarse.
En aplicaciones móviles, Circuit Breaker es especialmente útil cuando una API no está disponible debido a mantenimiento planificado o fallos de red del operador. Sin él, la aplicación consumiría batería y tráfico en intentos de reintento infinitos, degradando la experiencia del usuario y reduciendo la autonomía del dispositivo.
Circuit Breaker tiene tres estados: CLOSED (funcionamiento normal, las solicitudes se ejecutan), OPEN (fallo, las solicitudes se bloquean) y HALF_OPEN (solicitud de prueba para verificar la recuperación). Después de un tiempo de espera determinado en el estado OPEN, el interruptor pasa a HALF_OPEN y ejecuta una solicitud — si tiene éxito, vuelve a CLOSED; si falla, a 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
}
}
}
La implementación de Circuit Breaker en Kotlin contiene un contador de errores y un temporizador de recuperación. El método protect verifica el estado actual antes de ejecutar la solicitud envuelta y actualiza el contador de errores en caso de fallo. Después de alcanzar el failureThreshold, todas las solicitudes se rechazan inmediatamente hasta que expire timeoutMs.
Las redes móviles tienen características que hacen que Retry Policy sea especialmente importante. El cambio entre Wi-Fi y datos móviles, la pérdida de señal en metros y túneles, los bloqueos temporales a nivel de operador — todos estos escenarios provocan fallos de solicitud que pueden manejarse exitosamente con reintentos.
En Android, la biblioteca Retrofit y OkHttp proporcionan un mecanismo de reintento integrado a través de Interceptor. En iOS, la tarea se resuelve mediante URLSessionConfiguration y delegación personalizada. Para el desarrollo multiplataforma, Ktor (KMP) incluye soporte integrado de reintento con estrategias configurables.
Combine es el framework de Apple para programación reactiva. El operador retry en Combine repite el publisher un número especificado de veces al producirse un error, pero no permite configurar la demora entre reintentos. Para una Retry Policy completa, se utiliza una combinación personalizada de catch y flatMap con demora.
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()
}
}
La extensión retryWithBackoff para Publisher en Combine implementa exponential backoff mediante llamadas recursivas con disminución del contador y duplicación de la demora. El operador delay crea una pausa entre reintentos, mientras que catch intercepta el error y decide si reintentar o devolver un fallo.
El primer error — reintentar solicitudes sin verificar la idempotencia. Si el servidor creó un recurso pero no devolvió confirmación debido a un fallo de red, un reintento creará un duplicado. Para solicitudes POST, utilice siempre una clave de idempotencia (Idempotency-Key) en el encabezado o limite los reintentos solo a GET, PUT y DELETE.
El segundo error — reintentar indefinidamente. Establezca siempre un número máximo de intentos (3–5 para aplicaciones móviles) y un tiempo de espera total para todos los intentos. Los reintentos infinitos agotan la batería y crean una carga parásita en el servidor, especialmente durante migraciones de bases de datos o cambios de API.
El tercer error — ignorar el contexto de la aplicación. Si el usuario cerró la aplicación o pasó a segundo plano, las Retry Policy activas deben cancelarse correctamente. Use corutinas con SupervisorScope o Combine con el ciclo de vida de la UI para la cancelación automática de reintentos al cerrar la pantalla.
El cuarto error — no registrar los intentos de reintento. Sin registros, no sabrá cuántas solicitudes se reintentaron, qué errores ocurrieron y qué tan efectiva es su Retry Policy. Agregue métricas: número de reintentos, éxito después del reintento, distribución de demoras. Estos datos ayudarán a ajustar los parámetros óptimos de la estrategia para su aplicación específica.
Preguntas frecuentes
El número óptimo de reintentos es de 3 a 5 intentos para la mayoría de los escenarios. Para la sincronización en segundo plano, son aceptables de 5 a 7 intentos; para solicitudes interactivas (por ejemplo, envío de formularios), no más de 3. Un número mayor de reintentos no aumenta la probabilidad de éxito, pero consume la batería y el tráfico de datos del usuario.
Exponential backoff es la duplicación de la demora entre intentos de reintento: 1 segundo, 2, 4, 8, 16 y así sucesivamente. Si el servidor está sobrecargado, la pausa corta entre los primeros reintentos le permite responder rápidamente, mientras que la pausa creciente con cada reintento posterior le da al servidor más tiempo para recuperarse.
Reintente solo errores temporales: 408 (Request Timeout), 429 (Too Many Requests), 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout). Los errores 4xx (excepto 408 y 429) indican problemas del cliente — reintentarlos no tiene sentido y puede ser peligroso para los datos del usuario.
Retry Policy gestiona el reintento de una sola solicitud ante un fallo. Circuit Breaker gestiona el estado de la conexión con un servicio: cuando se acumulan errores, abre el circuito (OPEN) y bloquea nuevas solicitudes. Retry funciona a nivel de llamada individual, Circuit Breaker a nivel de integración con el servicio.
Para probar Retry Policy, use NetworkInterceptor (OkHttp) en Android y URLProtocol (URLSession) en iOS para simular fallos de red. Configure parámetros: frecuencia de errores, duración de la indisponibilidad y códigos de respuesta. Las pruebas unitarias con MockWebServer (OkHttp) u OHHTTPStubs (iOS) verifican la lógica de reintentos sin red real.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también