Del tema de la revista a la práctica del proyecto
Páginas de servicios y técnicas relacionadas
Una llamada REST es en teoría sencilla: enviar solicitud, recibir respuesta, listo. En la práctica, sin embargo, las integraciones productivas rara vez fallan por «falsa URL», sino por casos límite en el funcionamiento: timeouts esporádicos, problemas transitorios de DNS o TLS, sistemas downstream sobrecargados, o 429 (Too Many Requests) porque un API-Gateway aplica limitación. Justo aquí se separa un prototipo de demostración de una integración que pueda operarse de forma continuada.
Esta entrada muestra cómo, con el RESTClient en Delphi, establecer rutas de comunicación robustas: definiciones claras de tiempos de espera, reintentos dirigidos solo donde son seguros desde el punto de vista funcional y técnico, y un comportamiento de backoff que respeta los límites de tasa en lugar de agravarlos. El foco no está en el «código bonito», sino en el comportamiento bajo carga, la capacidad de depuración, la clasificación precisa de errores y la cuestión de cuándo merece realmente la pena el esfuerzo adicional.
Por qué timeouts, reintentos y 429 aparecen juntos en entornos reales
En redes empresariales las llamadas REST rara vez van «directamente a Internet». Lo habitual son cadenas de proxies, terminación TLS, API-Gateways, WAFs (Firewall de Aplicaciones Web) y varios saltos internos. Cada eslabón puede tener sus propios tiempos de espera y límites. Un timeout en el lado del cliente puede significar:
- El servidor no respondió (sobrecarga, bloqueo, el servicio downstream está colgado).
- La respuesta llegó, pero demasiado tarde (ruta deficiente, pérdida de paquetes, congestión).
- Te has bloqueado a ti mismo: tiempos de espera demasiado cortos o un hilo de UI/hilo principal bloqueante.
Paralelamente, los reintentos «ingenuos» suelen provocar más problemas: si un servidor ya está en el límite, los reintentos aumentan la carga y convierten un cuello de botella menor en una incidencia. Con los 429 es aún más obvio: un rate-limit es una indicación explícita de enviar menos o volver más tarde. Un cliente sin mecanismo de backoff actúa como un generador de DoS, aunque de forma involuntaria.
La robustez, por tanto, no se logra con «reintentar en todas partes», sino con un modelo de decisión consistente: qué errores son transitorios (temporales), cuáles son permanentes, qué solicitudes son reintentables (idempotentes) y cómo controlas los tiempos de espera para que tu sistema se mantenga estable.
Configurar los tiempos de espera correctamente: ¿qué significa exactamente «Timeout» en el RESTClient en Delphi?
Un tropiezo frecuente: «Timeout» no es siempre lo mismo. Según la pila existen distintas fases. Aunque las componentes Delphi-REST abstraen mucho, conviene tener el modelo en la cabeza:
- Connect-Timeout: tiempo hasta que se establece la conexión TCP (incluido DNS/TLS según la implementación).
- Read/Response-Timeout: tiempo hasta que empiezan a llegar bytes del servidor o hasta que la respuesta está completa.
- Timeout total: límite superior para toda la llamada, incluidos los reintentos.
En la práctica, un timeout demasiado corto es al menos tan peligroso como uno demasiado largo: generas errores artificiales que luego se reintentan y así aumentan la carga. Al contrario, un timeout demasiado largo bloquea hilos de trabajo, slots de cola o la capacidad de respuesta de la UI. Para operación y administración es importante que los tiempos de espera sean configurables (p. ej., por endpoint) y que se registren en el log.
Recomendación práctica: dos niveles en lugar de un único valor
Para llamadas REST en software empresarial se han demostrado útiles dos niveles:
- Call-Timeout (por solicitud): un límite superior realista que se ajuste al caso de uso.
- Tiempo máximo de ejecución del job (a nivel general): si tienes un procesamiento por lotes o un trabajo de sincronización, limita la duración total y finaliza de forma controlada.
Así evitas que una única respuesta de la API espere indefinidamente y, al mismo tiempo, que un trabajo nocturno siga ejecutándose „hasta el mediodía“ por culpa de numerosos reintentos.
Decidir correctamente los reintentos: no técnico, sino desde el punto de vista funcional
Que un reintento esté permitido no es una cuestión puramente técnica. El concepto clave es la idempotencia: una petición es idempotente si ejecutarla varias veces produce el mismo efecto que hacerlo una sola vez. Ejemplos típicos: GET es idempotente, PUT a menudo también (si estableces completamente el objeto destino), DELETE habitualmente también. POST suele no ser idempotente (p. ej., „crear un nuevo pedido“).
¿Por qué es determinante? Un timeout puede significar que el servidor procesó la petición, pero la respuesta no llegó al cliente. Si entonces repites ciegamente un POST, generas duplicados. En producción es un error clásico de „fantasma“: en la aplicación aparece „Timeout“, pero en el backend hay registros duplicados.
La base segura: reintentos solo para operaciones claramente reintentables
Una regla robusta, probada en integraciones:
- GET: reintentable ante errores transitorios.
- PUT/DELETE: reintentable si tu API lo define claramente desde el punto de vista funcional (p. ej., el ID del recurso es estable) y el servidor implementa correctamente la idempotencia.
- POST: reintentable solo si dispones de una estrategia de Idempotency-Key (ID de petición funcionalmente único que el servidor usa para evitar duplicados) o si el POST es semánticamente idempotente (raro, pero posible).
Si no controlas la API, aquí es donde, como responsable técnico, debes tomar una decisión: o aceptas „sin reintento en POST“ (y construyes mejores mensajes de error/mecanismos de resincro), o negocias con el proveedor de la API un Idempotency-Key o un modelo deduplicable.
429 Too Many Requests: respetar los rate-limits en lugar de „reintentar sin control“
HTTP 429 no es un „mensaje de error molesto“, sino un mecanismo de control. En entornos empresariales, 429 suele provenir de:
- API-Gateway con límites Token-Bucket/Leaky-Bucket (Rate Limiting).
- APIs en la nube con límites por inquilino por minuto/hora.
- Servicios internos que se protegen frente a picos de carga.
Para el cliente eso significa: reintentos sí, pero controlados. Hay dos cosas importantes:
- Evaluar el encabezado Retry-After, cuando exista (segundos o fecha HTTP).
- Usar Backoff si no hay Retry-After o si además aplicas jitter.
La trampa más habitual: tratar 429 como un 500 („error de servidor, reintentar de inmediato“). Con eso solo refuerzas la limitación. Mejor: interpretar 429 como una señal para esperar activamente y, si procede, reducir la paralelidad.
Backoff con jitter: por qué sin aleatoriedad todo colapsa de forma sincronizada
Backoff exponencial significa que aumentas el tiempo de espera después de cada fallo (p. ej., 200 ms, 400 ms, 800 ms …). Jitter es un componente aleatorio que impide que muchos clientes vuelvan a tocar a la vez. Sin jitter suele ocurrir lo siguiente en la práctica: se aplica un límite, 50 clientes reciben 429, todos esperan exactamente 1 segundo y vuelven a enviar simultáneamente. Resultado: otra vez 429, y tienes un problema de “Thundering Herd”.
Un enfoque práctico es “Full Jitter” o “Equal Jitter”: calculas una ventana de backoff y seleccionas un tiempo de espera aleatorio dentro de esa ventana. Puede parecer un detalle, pero en producción marca la diferencia entre una recuperación estable y un ruido continuo.
Un patrón limpio: encapsular llamadas a REST en lugar de dispersar bucles de reintento en todas partes
Si insertas reintentos/backoff “ad hoc” en cada punto de llamada, pronto surge un comportamiento inconsistente: un Endpoint reintenta agresivamente, otro no lo hace, el Logging es fragmentario, y los administradores sólo ven “errores esporádicos”. Se vuelve robusto cuando defines una ruta de llamada centralizada:
- Un wrapper alrededor de RESTClient/RESTRequest que aplique una política (timeout, reintento, backoff).
- Un objeto de resultado uniforme: código de estado, duración, contador de intentos, y si procede, la última Exception.
- Standardisiertes Logging (Request-ID/Correlation-ID, Endpoint, HTTP-Methode, relevante Header).
Este es el punto en el que el código adicional realmente merece la pena: obtienes un comportamiento reproducible, mejores logs, y puedes configurar políticas por sistema objetivo sin reestructurar la aplicación.
Matriz de decisión de políticas (breve y práctica)
Para la mayoría de integraciones basta con una matriz simple que implementes en el wrapper:
- Reintento ante: errores de red/cortes de conexión, 408, 429, 502, 503, 504 (según el contrato de la API).
- No reintentar en: 400/401/403/404 (habitualmente errores de configuración/autenticación/solicitud), 409/422 (conflictos lógicos/validación), así como en POST sin Idempotency-Key.
- Máx. intentos: mantenerlo bajo (a menudo 2–4 intentos son suficientes), y mejorar el monitoreo.
- Máx. backoff: limitarlo (p. ej., unos segundos hasta un minuto), de lo contrario bloquearás demasiados workers.
Importante: estas reglas no son universales. Un 404 puede ser transitorio en casos de “eventual consistency”; un 409 puede ser transitorio con estrategias de locking. La diferencia es: entonces será una desviación consciente, no un comportamiento aleatorio.
Caso límite concreto: timeout tras un POST – ¿se almacenó o no?
Este es el clásico que rara vez se reproduce de forma limpia en el depurador: envías un POST (p. ej., «crear ticket»), tu cliente recibe un Read-Timeout y el usuario hace clic en «otra vez». En el backend, sin embargo, el ticket ya existe. Sin medidas compensatorias se generan duplicados o inconsistencias.
Sólo se vuelve robusto con una de tres estrategias:
- Idempotency-Key: Generas por cada operación de negocio una identificador único de solicitud (p. ej. GUID), lo envías como cabecera y el servidor garantiza el procesamiento deduplicado.
- Deduplificación del lado del cliente: Guardas solicitudes pendientes con una ID propia localmente y, tras un timeout, realizas una comprobación de estado (p. ej. un GET por la clave de negocio). Esto es más laborioso y no siempre posible.
- Sin reintentos: Informas de forma clara de que el estado es desconocido y construyes un proceso de resíncronización manual/automático (p. ej. conciliación posterior).
Si construyes integraciones para operaciones, «estado desconocido» es una categoría válida. No intentes codificar la incertidumbre. Regístrala en los logs, hazla visible y provee una ruta de conciliación.
Diseño de Backoff en la práctica: umbrales, paralelismo y cancelación
Un Backoff no es solo «sleep». Debes situarlo en el contexto de tu aplicación:
- Paralelismo: Si tienes 20 hilos y todos esperan, 20 hilos quedan bloqueados. Para servicios suele estar bien; para aplicaciones de escritorio, no tanto.
- Cancelación: Un usuario cancela, el servicio se detiene, un job finaliza. La espera del backoff debe ser abortable, si no los procesos de parada/shutdown se quedan colgados.
- Equidad: Varios endpoints no deberían asfixiarse entre sí. Los rate-limits suelen ser por token o por endpoint; tu wrapper debería poder controlar esto por sistema objetivo.
Un enfoque correcto es: implementar el Backoff en una función que espere en intervalos pequeños comprobando una bandera de cancelación (p. ej. Event/Token). Esto no es un lujo: precisamente ese punto decide si un Windows- y Linux-servicios se detienen correctamente o se quedan «colgados» en la consola del Service Control Manager.
Duración máxima y «presupuesto» por llamada
Una implementación robusta de reintentos no trabaja solo con «max tries», sino también con un presupuesto de tiempo. Ejemplo: permites como máximo 10 segundos de tiempo total para la llamada, incluidos los reintentos. De ese modo, un intento individual no puede bloquear 30 segundos de repente solo porque un timeout esté mal configurado. Para administradores y operaciones esto vale oro, porque limita picos de latencia y estabiliza las colas.
Depuración y diagnóstico operativo: sin buenos logs, los reintentos son amplificadores de errores invisibles
Los reintentos sin registro son peligrosos, porque al final solo escuchas «a veces tarda». Si quieres ser robusto, necesitas logs que no solo muestren excepciones, sino que aporten contexto:
- Correlation-ID: una ID de solicitud que generas por llamada y conservas en cada retry.
- Número de intento y Delay (backoff).
- Estado HTTP y cabeceras seleccionadas (en particular Retry-After, cabeceras de RateLimit si existen).
- Duración por intento y tiempo total.
- Endpoint (host + ruta), pero sin datos sensibles en el log (tokens, datos personales).
Para responsables técnicos es además la palanca para ajustar umbrales: ves si los timeouts «siempre a los 3 segundos» ocurren (probablemente demasiado cortos) o si los 429 llegan en oleadas (paralelismo demasiado alto, backoff insuficiente o ausencia de limitación de tasa en el cliente).
Trampas típicas en el logging
- Payload excesivo: registrar cuerpos JSON completos parece útil, pero explota con archivos/adjuntos y genera problemas de protección de datos. Mejor: hash/tamaño, Content-Type y, si es necesario, logging de depuración dirigido mediante feature-flag.
- No distinguir timeout de cancelación: una llamada cancelada no es un error del mismo tipo que un timeout. Sepáralos, si no los administradores perseguirán errores fantasma.
- El retry oculta la causa inicial: si el intento 1 tiene un error TLS y el intento 2 es exitoso, aún quieres saber que hubo un fallo TLS intermitente. Eso es una señal temprana.
Limitación de tasa en el cliente: cuando debes controlar la carga tú mismo
El 429 es la respuesta del servidor. En muchos escenarios, no obstante, conviene ya en el cliente reducir la tasa antes de generar 429. Esto es especialmente relevante si:
- Tienes jobs por lotes (por ejemplo, reconciliación de datos nocturna) y la API solo permite X peticiones por minuto.
- Usas varios workers/hilos y lanzas las peticiones en paralelo.
- Tienes varias instancias de proceso en ejecución (por ejemplo, servidores de terminal o varios servicios).
En la práctica significa: implementas un pequeño limitador de tasa (por ejemplo, Token-Bucket) por sistema destino o por API-Key. Esto reduce los 429, estabiliza el throughput y hace que los tiempos de ejecución sean más previsibles. Para operaciones y planificación de capacidad suele ser más valioso que „otro reintento“.
Importante: limitador de tasa y backoff se complementan
El limitador de tasa te mantiene en operación normal por debajo del límite. El backoff es la reacción cuando aun así recibes 429 o sobrecarga temporal. Quien solo tiene backoff conduce permanentemente „contra el muro“ y luego se frena. Quien solo tiene limitador de tasa reacciona mal ante límites inesperados o cuotas compartidas (por ejemplo, cuando varios sistemas usan la misma API-Key).
Seguridad y cumplimiento: los reintentos no deben ocultar problemas de autenticación
En las empresas, autenticación y autorización suelen ser el „fallo“ más frecuente tras un despliegue: tokens caducados, credenciales de cliente mal configuradas, excepciones de proxy. Los reintentos no solucionan esto e incluso pueden ser perjudiciales, porque llenan los logs y disparan mecanismos de bloqueo (por ejemplo, bloqueo de cuentas, límites en endpoints de autenticación).
Regla práctica: nunca reintentar 401/403 (a menos que tengas un manejo explícito de renovación de tokens). Si implementas renovación de tokens, sepárala claramente del mecanismo de retry: primero renovar el token, luego enviar de nuevo una vez. Y registra explícitamente que se realizó una renovación.
Cuándo merece la pena el esfuerzo — y cuándo no
Los reintentos robustos y el backoff no son un fin en sí mismos. Merecen la pena especialmente cuando se cumple al menos uno de los siguientes puntos:
- La integración es crítica para el negocio (p. ej., registro de pedidos, envío, facturación).
- La API es externa o se opera internamente en modo ‚best effort‘ y no tienes control total.
- Tienes picos de carga (p. ej., ventanas de tareas, cierre mensual) y quieres atravesarlos de forma estable.
- Lo ejecutas como servicio/daemon y debe poder detenerse de forma controlada y limpia.
Tiene menos sentido cuando solo realizas GETs de confirmación en la UI y el usuario de todos modos vuelve a hacer clic, o cuando trabajas en un entorno interno muy estable sin cuotas y los errores son visibles de inmediato. Aun así, los timeouts y el logging bien definidos casi siempre son recomendables.
Lista de verificación pragmática para la operación productiva del cliente Delphi-REST
- Timeouts: configurable por endpoint, elegidos de forma realista, presupuesto total definido.
- Política de reintentos: dependiente del método HTTP y de la idempotencia, no de forma genérica.
- Manejo de 429: evaluar Retry-After, backoff con jitter, vigilar la concurrencia.
- Ruta de cancelación: la espera durante el backoff debe ser abortable (detención del servicio, cancelación por parte del usuario).
- Registro: Correlation-ID, intento, retardo, duración, estado/encabezados – sin secretos.
- Opcional: limitador de tasa del lado del cliente para operación por lotes/paralela.
Conclusión: La robustez es un comportamiento, no un bloque catch-all de excepciones
Con el RESTClient en Delphi obtendrás llamadas REST que funcionan rápidamente. Sin embargo, será robusto en producción solo cuando definas conscientemente los timeouts, asegures los reintentos desde el punto de vista funcional (¡idempotencia!) y respetes los límites de tasa 429 con backoff y jitter. El código para ello no es complicado, pero debe estar centralizado, ser configurable y claramente observable. Es entonces cuando el esfuerzo compensa: menos incidencias ‚esporádicas‘, mejor diagnóstico en operación e integraciones que no fallan incluso bajo carga.
Si quieres introducir de forma ordenada una política de retry/backoff para aplicaciones existentes Delphi o dimensionarla adecuadamente para una nueva integración: ponte en contacto.
Para este tema son también importantes Delphi timeout del Restclient y estrategia de retry Delphi. El artículo sitúa estos aspectos de forma comprensible y muestra en qué se debe incidir en la práctica.
Discutir proyecto o iniciativa de modernización con Net-Base.
siguiente paso
Cuando un tema se convierte en un proyecto real, arquitectura, entorno existente y operación deben considerarse conjuntamente desde el inicio.
No solo apoyamos en consultas puntuales, sino también cuando, a partir de fragmentos de código fuente, temas heredados o ideas de portales, debe consolidarse un proyecto empresarial robusto.
- La situación actual, el estado objetivo y los riesgos técnicos se evalúan conjuntamente.
- REST, el acceso a datos, los portales y el despliegue no se relegan a fases posteriores.
- Usted detecta con antelación qué camino es viable, tanto económica como operativamente.