El Problema de la Ejecución Duradera: Una Historia Familiar

Imagina un proceso de negocio de múltiples pasos: validar un reclamo, ejecutar verificaciones de seguridad, procesar un pago, enviar notificaciones. A mitad del camino, el servidor se cae. ¿Qué pasa después?

En arquitecturas tradicionales, la respuesta suele ser "depende." Tal vez la operación expire y dispare procesamiento duplicado. Tal vez un estado parcial corrompa lo que sigue. Para workflows que duran minutos, horas o incluso días, las interrupciones son demasiado probables.

Este es el problema de la ejecución duradera: asegurar que un proceso multi-paso se complete de manera confiable, incluso a través de fallos, reinicios y despliegues. La industria ha producido varias soluciones:

  • Motores de orquestación externos (ej: Temporal, AWS Step Functions) — probados en batalla pero requieren infraestructura dedicada e introducen una dependencia externa crítica.
  • Servicios de workflow gestionados en la nube — eliminan la sobrecarga operativa pero introducen vendor lock-in y preocupaciones regulatorias.
  • Sistemas caseros basados en colas — evitan dependencias externas pero las cambian por complejidad propia: cada equipo implementa su propia lógica de reintento, gestión de estado y flujos de compensación.

Airbnb enfrentó todos estos tradeoffs en múltiples equipos. En lugar de que cada servicio reinventara la rueda, construyeron Skipper — un motor de workflow embebido que corre dentro de cada servicio, usando la base de datos que el servicio ya utiliza.

Fuente: Airbnb Engineering Blog

Airbnb Skipper embedded workflow engine architecture diagram showing service and database interaction IT Technology Image

Diseño Central de Skipper: Workflows y Actions

Skipper se centra en dos abstracciones:

  1. Workflows — definen la lógica de orquestación: qué pasa en qué orden y bajo qué condiciones.
  2. Actions — encapsulan operaciones individuales (llamadas a API, actualizaciones de base de datos, notificaciones). Cada action tiene checkpoint automático, para que su resultado sobreviva a crashes y reinicios.

Un Ejemplo Concreto

Aquí hay un workflow duradero para procesar la revisión de fotos de un anuncio:

class ListingPublicationWorkflow : Workflow() {
    private val actions = actions()
    @StateField val photosApproved: Boolean? = false

    @WorkflowMethod
    suspend fun publishListing(submission: ListingSubmission): PublicationResult {
        // Envía fotos para revisión
        val reviewId = actions.submitPhotosForReview(submission.getListingId())

        // Espera a que termine la revisión (manual o automatizada)
        val reviewTimedOut = waitUntil(() -> photosApproved != null,
            Duration.ofHours(24))

        if (reviewTimedOut || !photosApproved) {
            actions.notifyHost(submission.getHostId(), "Las fotos necesitan ajustes")
            return PublicationResult.rejected("La revisión de fotos falló")
        }

        // Publica el anuncio
        actions.activateListing(submission.getListingId())
        actions.notifyHost(submission.getHostId(), "¡Tu anuncio está en línea!")
        return PublicationResult.success(submission.getListingId())
    }

    @SignalMethod
    fun completePhotoReview(approved: Boolean) {
        photosApproved = approved
    }
}

Este código se lee natural: enviar fotos, esperar revisión, publicar. No hay lógica de reintento, gestión de colas o coordinación asíncrona visible en el workflow.

Cómo Funciona la Durabilidad

Cuando un workflow inicia, Skipper hace checkpoint del resultado de cada action en la base de datos. Si el workflow necesita esperar (vía waitUntil), Skipper persiste el estado actual y el workflow hiberna, sin consumir recursos computacionales.

Cuando las condiciones cambian — llega una señal, expira un timer o el servicio se reinicia — Skipper reproduce el método del workflow desde el principio. Las actions ya ejecutadas no se reejecutan; devuelven sus resultados checkpointed instantáneamente. El workflow continúa desde donde se quedó.

Esto es fundamentalmente diferente de los sistemas de orquestación event-sourced. Skipper persiste campos de estado directamente — no hay un log de eventos para reproducir. Esto hace la ejecución más ligera, especialmente para workflows con muchas señales o historias largas, aunque cambia algo de auditabilidad por esa eficiencia.

Cloud infrastructure diagram illustrating durable execution with Skipper across multiple services System Abstract Visual

El Camino Feliz: Salir del Camino

La mayoría de los motores de workflow imponen overhead en cada ejecución, incluso cuando nada sale mal. Los motores de orquestación externos requieren round-trips de red a un clúster central para cada invocación de actividad.

Skipper toma un enfoque diferente. Cuando un workflow inicia, dos cosas pasan a nivel de base de datos:

  1. La instancia del workflow se crea
  2. Una tarea de timeout retrasada se agenda como garantía de durabilidad

Luego el workflow ejecuta enteramente in-process. Las actions corren como llamadas a métodos normales en una cola de ejecución en memoria, los checkpoints se agrupan, y el workflow puede completarse sin ninguna coordinación adicional.

La tarea retrasada actúa como una red de seguridad: si el proceso se cae a media ejecución, el scheduler persistente retoma el workflow después de que el período de lease expire y lo reproduce. Si el workflow se completa normalmente, la tarea de timeout se descarta inofensivamente.

Resultado: En el caso feliz (sin crashes), Skipper agrega muy poco overhead — solo unas pocas escrituras en la base de datos. El workflow ejecuta casi como si no hubiera motor de workflow alguno.

Tradeoffs Principales

Lo Que GanasLo Que Cambias
Sin infraestructura que gestionar (sin clúster separado)Los métodos de workflow deben ser determinísticos (sin efectos secundarios, aleatoriedad o lógica dependiente del tiempo)
Usa base de datos existente (MySQL, DynamoDB)Ejecución al menos una vez — las actions pueden ejecutarse más de una vez en casos extremos; las actions deben ser idempotentes
Modelo de programación simple (clases Java/Kotlin)Complejidad de evolución — cambiar la estructura de un workflow puede romper workflows en curso; se necesitan estrategias de versionado
Escalabilidad independiente por servicioNo adecuado para orquestación cross-language o cross-service

Limitaciones y Cuidados

  • El determinismo es difícil. Desarrolladores nuevos en el patrón a menudo introducen no-determinismo accidentalmente (ej: llamar a System.currentTimeMillis() dentro de un método de workflow). Esto rompe la reproducción.
  • La idempotencia es obligatoria. Como las actions pueden ejecutarse más de una vez, cada action debe ser segura para ejecutarse múltiples veces.
  • El versionado de workflows es doloroso. No hay herramientas de migración integradas. Los equipos necesitan crear nuevas versiones de métodos, migrar tráfico y depreciar versiones antiguas.
  • Debugging es más difícil. Los timestamps de log y las secuencias de llamadas reflejan reproducciones, no la ejecución original. Mejores herramientas de observabilidad (visualización de reproducción) ayudarían.

Próximos Pasos para Aprender

Si este patrón te interesó, considera explorar:

  • Temporal — el motor de orquestación externo más popular para ejecución duradera
  • AWS Step Functions — una alternativa gestionada en la nube
  • Apache Airflow — para orquestación de pipelines de datos

Para una inmersión práctica en sistemas distribuidos en AWS, checa nuestra guía sobre escalando Python con Ray en clúster en la nube.

Developer writing Java Kotlin workflow code for durable execution with Skipper Programming Illustration

Conclusión: Cuándo Usar un Motor de Workflow Embebido

La ejecución duradera de workflows es una capacidad fundamental para sistemas distribuidos confiables. Skipper representa un punto específico en el espacio de diseño: un motor embebido que cambia orquestación centralizada por simplicidad operativa.

Este enfoque no sirve para todas las situaciones. Pero para servicios que buscan ejecución duradera sin overhead de infraestructura — particularmente donde minimizar dependencias es primordial — el modelo embebido ofrece ventajas convincentes.

La idea central se generaliza más allá de la implementación de Airbnb: la ejecución basada en reproducción con actions checkpointed puede proporcionar durabilidad sin servicios de coordinación.

Lectura Adicional

Este contenido fue redactado con la asistencia de herramientas de IA, basándose en fuentes confiables, y fue revisado por nuestro equipo editorial antes de su publicación. No reemplaza el asesoramiento de un profesional especializado.