Durable Delays: gestionando el tiempo como un estado distribuido
SchemaBridge Team · 2026-01-10 · Time, Scheduling, Orchestration
Cómo gestionar periodos de espera de 30 días sin fugas de memoria. Por qué `sleep()` es un antipatrón de los sistemas distribuidos.
La mentira de sleep(): por qué el tiempo es la primitiva distribuida más difícil
En todo lenguaje de programación existe un comando para esperar. En Python es time.sleep(). En Node.js es setTimeout(). En Java es Thread.sleep(). Estos comandos son simples, intuitivos y, para cualquier cosa más compleja que unos pocos segundos, totalmente inútiles en un sistema distribuido de producción.
El humilde sleep() es una mentira porque asume que el entorno es estable. Asume que la máquina que ejecuta el código seguirá viva, que el proceso no será eliminado por un balanceador de carga y que el sistema operativo no reclamará la memoria. En un entorno cloud moderno, estas suposiciones son falsas. Si haces sleep(24 60 60) (un día) en un pod estándar de Kubernetes, hay un 99% de probabilidades de que ese pod se rote, se escale a la baja o se redepliegue antes de que termine el día.
Cuando el proceso muere, tu "sleep" muere con él. Tu lógica de negocio se pierde en el vacío. Así es como nace el "software olvidadizo": sistemas que pierden el rastro de las pruebas de clientes, se saltan renovaciones de suscripción y no envían correos de seguimiento críticos.
La jerarquía del tiempo: de los segundos a los meses
Para gestionar el tiempo correctamente, primero debemos categorizarlo. No todos los retrasos son iguales:
1. Retrasos transitorios (milisegundos a segundos): normalmente son backoffs de red o tiempos de espera para un bloqueo rápido de base de datos. Aquí sleep() a veces es aceptable, porque el riesgo de un fallo en una ventana de 50ms es bajo.
2. Retrasos cortos (minutos): aquí es donde sleep() empieza a fallar. Estás bloqueando un hilo de worker o los recursos de un contenedor durante minutos sin hacer nada. Es un desperdicio de dinero y un riesgo para el pool de conexiones.
3. Durable Delays (horas a meses): este es el terreno de los ciclos de vida de negocio. Una prueba gratuita de 14 días, un plazo de pago de 30 días o un calendario de mantenimiento de 6 meses. Estos no pueden vivir en el código; deben vivir en la infraestructura.
La arquitectura de los workflows "dormidos"
En SchemaBridge tratamos el tiempo como estado duradero. Cuando tu workflow llega a un "vértice de Delay", no bloquea un hilo. Se serializa a disco y deja de existir en memoria.
El modelo de sondeo en BD (baja precisión, alta durabilidad)
Almacenas la "hora de despertar" en una tabla de base de datos. Un worker en segundo plano (el "Poller") consulta la tabla cada pocos segundos: SELECT * FROM timers WHERE wake_up < NOW(). Esto es increíblemente duradero, porque un registro de BD puede sobrevivir años.
El enfoque de SchemaBridge
SchemaBridge utiliza un motor de planificación persistente respaldado por DynamoDB. Persistimos explícitamente la intención de despertar en nuestro ScheduledTaskRepository. Esto nos da la durabilidad de mil años propia de un registro de base de datos.
Señales e interrupciones: cambiando el futuro
Un Durable Delay es inútil si no se puede cancelar o modificar. Si un usuario está en un vértice de "esperar 3 días por el pago" y paga a las 2 horas, necesitas despertar el workflow y avanzar de inmediato.
SchemaBridge soporta señales externas. Una señal es un evento que se envía HACIA un workflow en ejecución.
- La espera: el workflow está en un vértice
DelayhastaT + 3 díasO hasta que llegue una señalPayment_Received. - La interrupción: cuando la señal llega al motor, este busca la instancia específica, rehidrata el estado y transiciona el vértice de inmediato.
Proporcionamos APIs explícitas para cancelar o interrumpir estas tareas programadas (por ejemplo, detener un cron job mediante el comando nuke o interrumpir un retraso específico mediante una señal de webhook). Como tanto el retraso como la señal son gestionados por el motor, el sistema es inmune a las condiciones de carrera.
Desfase de reloj y precisión en una malla global
En un sistema distribuido repartido entre múltiples regiones de AWS, los relojes nunca están perfectamente sincronizados. Es el fenómeno del desfase de reloj (clock drift). Si el reloj de la Región A va 50ms por delante del de la Región B, un "esperar 1 segundo" puede dar lugar a comportamientos distintos según dónde se recoja la tarea.
SchemaBridge resuelve esto utilizando una firma de reloj lógico derivada de nuestra capa de metadatos centralizada. Aunque cada worker usa su reloj local para la ejecución, la "intención del tiempo" está sincronizada globalmente y queda registrada en el historial inmutable. No nos importa si el reloj del worker está ligeramente desviado; nos importa que la duración de la ejecución coincida con la intención de negocio.
Caso de estudio: automatizando una campaña drip de 30 días con 0% de fallos
Trabajamos con una plataforma de automatización de marketing que tenía problemas con su lógica de "recorrido de bienvenida".
El reto
Un recorrido incluía:
- Enviar el correo 1 (día 1).
- Esperar 3 días.
- Si el usuario no ha hecho clic, enviar el correo 2.
- Esperar 7 días.
- Si el usuario no ha mejorado su plan, enviar un código promocional.
Utilizaban una solución construida a medida con cron jobs y una tabla de Postgres. Cada semana, durante su ventana de mantenimiento de base de datos o un despliegue, alrededor del 5% de los usuarios quedaba "atascado" en un estado de espera y nunca recibía el siguiente correo. Perdían miles de dólares en ingresos de conversión potenciales cada mes.
El camino de SchemaBridge
Trasladaron los recorridos de usuario a workflows de SchemaBridge.
1. Delays visuales: literalmente arrastraron un vértice de "Delay" al grafo y lo configuraron a 3d y 7d.
2. Reanudación duradera: durante los despliegues, los workflows simplemente se pausaban en la base de datos. Cuando el motor volvía a estar en línea, veía los temporizadores que habían expirado durante la caída y los reanudaba de inmediato en el orden correcto.
3. Integración de señales: usaron nuestro Webhook Gateway para enviar una señal de "clic". Si el usuario hacía clic en el correo, el workflow se despertaba y transicionaba de inmediato a la ruta de "éxito", saltándose el resto del retraso.
El resultado
- Fiabilidad: pasaron de una tasa de fallo del 5% a cero recorridos perdidos.
- Felicidad para los desarrolladores: la base de código de los recorridos se redujo un 70%. Eliminaron su compleja lógica de cron y su servicio "Job Poller" a medida.
- Impacto de negocio: las tasas de conversión mejoraron un 12%, porque los usuarios por fin recibían sus correos exactamente cuando debían.
Comparativa: estrategias de planificación existentes
| Característica | setTimeout() | Cron Jobs / Quartz | Durable Delays de SchemaBridge |
| :--- | :--- | :--- | :--- |
| Persistencia | Ninguna (volátil) | Manual (respaldada por BD) | Nativa (historial duradero) |
| Escalabilidad | Baja (ligada a la RAM) | Media (cuello de botella en BD) | Alta (DDB particionada) |
| Cancelación | Compleja (gestión manual) | Limpieza manual en BD | Señales/interrupciones visuales |
| Visibilidad | Ninguna | Consultas SQL | Dashboard visual de progreso |
| Resolución | Milisegundos | Segundos/minutos | Sondeo gestionado |
Lista de verificación experta para lógica de negocio de larga duración
Si estás diseñando un sistema que espera más de 5 minutos, sigue estas heurísticas:
1. Detén el hilo: nunca bloquees un worker mientras esperas. Tu worker debería ser stateless y estar listo para morir en cualquier momento.
2. Externaliza el reloj: usa un motor central para gestionar el paso del tiempo, no el reloj del sistema de la máquina local.
3. Diseña para la interrupción: asume siempre que el usuario podría realizar la acción que esperas antes de que expire el retraso. Usa señales para que tus esperas sean "interrumpibles".
4. Audita la sala de espera: usa tu dashboard para ver cuántos miles (o millones) de workflows están actualmente en estado "dormido". Es un indicador importante de la salud del negocio.
5. Aprovecha los offsets lógicos: no uses una marca de tiempo fija para los despertares (por ejemplo, "15 de enero"). Usa un offset lógico (por ejemplo, +3d) para garantizar que, incluso si el primer paso del workflow se retrasa, el intervalo relativo se mantenga consistente.
Conclusión: el tiempo es un problema de infraestructura
El software que olvida es software que falla. Al tratar el tiempo como un estado distribuido duradero y de primera clase, cerramos la brecha entre los eventos en tiempo real y el comportamiento humano de alta latencia. Con SchemaBridge puedes construir recorridos que abarcan meses con la misma confianza con la que construyes lógica que abarca milisegundos.
En la era de la "economía de la experiencia", la capacidad de gestionar a la perfección el timing de tus interacciones es tu mayor ventaja competitiva. Nosotros ponemos el reloj; tú pones el recorrido.
En la Parte 8 exploraremos la "brecha de observabilidad" y veremos cómo depurar visualmente estas cadenas distribuidas complejas de varios días sin perder la cabeza entre los logs.