Saltar a contenido

Zeebe Design Philosophy

En 2017, Camunda decidió que Camunda 7 (basado en DB tradicional) no podía escalar a la era de microservicios. Daniel Meyer pasó ~2 años diseñando Zeebe desde cero, con la decisión arquitectónica decisiva de usar stream processing internamente en vez de una database. Esa elección sola es lo que diferencia Zeebe de todos los otros workflow engines y habilita scalability horizontal, fault tolerance, y integración nativa con microservicios.

Timeline

Año Evento
2013 Camunda 7 lanzado (DB-based, JBoss heritage)
~2015-2017 Reconocimiento de que microservicios cambiarían el landscape
~2017 Daniel Meyer inicia diseño de Zeebe (~2 años de design phase)
2017-2019 Implementación por el equipo de desarrollo (~2 años)
2018 Rework arquitectónico completo: scalability ✓ pero fault tolerance insuficiente
Jul 17, 2019 Production-ready release anunciado
2020+ Camunda 8 lanzado, Zeebe es el core

~4 años desde concepción hasta production-ready — una inversión significativa de R&D que justifica analizar las decisiones cuidadosamente.

El problema que intentaron resolver

Cita literal del blog (julio 2019): "microservices and distributed systems are the forks and knives of that digital transformation".

Específicamente, coordinar "decenas a centenas de acciones implementadas por diferentes microservicios" manteniendo visibilidad y control sobre workflows complejos que cruzan múltiples services.

Los workflow engines tradicionales (incluyendo Camunda 7) no fueron diseñados para esto: - Asumen un único monolith con DB compartida - Persistencia transaccional ACID con la DB del negocio - Engine embebido en cada aplicación - Scaling vertical, no horizontal

La decisión decisiva: stream processing > database

Cita de Daniel Meyer:

"This simple fact [building on stream processing internally instead of a database] is what makes Zeebe unique and completely different from any other workflow engine."

Lo que esto significa

En vez de:

flowchart LR
    App[Application] --> Engine[Workflow Engine] --> DB[(SQL Database transactional state)]

Zeebe hace:

flowchart LR
    Client[Client] --> Gateway[Gateway]
    Gateway --> Broker[Broker]
    Broker --> Log[(Append-only log Raft-replicated)]
    Broker --> SP[Stream processor single-threaded]
    SP --> State[(Local state RocksDB)]

Ver concepts/command-sourcing y concepts/stream-processing para detalles técnicos.

Beneficios habilitados

Beneficio Cómo lo habilita stream processing
Horizontal scalability Particiones independientes, cada una con su propio stream
Fault tolerance Replicación via Raft del log, no de la DB
Native microservices integration Event-driven en su core, no request/response
Deterministic processing Same commands → same events (replay possible)
Atomic state mutations Log write y state change en una transacción
No DB connection pooling issues State es local al broker

Trade-offs aceptados

Trade-off Razón aceptable
Single-thread por partición Predictable, no race conditions, replay determinista
State no consultable externamente Necesita exporters para visibility
Operational complexity Aceptable para workloads críticos a gran escala
Eventual consistency en UIs El delay del export es aceptable para monitoring

Alternativas rechazadas

Implícitamente rechazadas en el design phase:

  1. Refactorizar Camunda 7: insuficiente. La arquitectura DB-centric era el problema fundamental.
  2. Usar Kafka como engine: Kafka es un broker, no un stream processor stateful. Faltaría toda la lógica de BPMN y state management.
  3. Database con horizontal sharding: añade complejidad sin resolver el problema de coordinación cross-service.
  4. Workflow engine embebido: vulnerabilidades de seguridad (extensions malformadas), tight coupling.

El rework de 2018

El blog menciona un rework arquitectónico completo en 2018: - Ya habían logrado scalability excelente - Pero fault tolerance y resilience eran insuficientes - Para un workflow engine mission-critical, fault tolerance es mandatory

Inferencia: este rework probablemente introdujo o refinó: - Raft consensus (vía fork de Atomix) - Snapshot/replay mechanisms - Process banning (rechazar instancias problemáticas para evitar cascadas) - Backpressure (dropping vs buffering)

Ver concepts/raft-consensus y concepts/backpressure.

Validación empírica

El caso de Intuit (ver analysis/intuit-production-benchmarks) validó la decisión:

  • Process creation: 18.75x más rápido que Camunda 7
  • External task latency TP99: 14.4x mejor
  • Sustained throughput: 26x más workflows en 10 horas

Esto cuantifica el valor de los 4 años de R&D y justifica el approach radical.

Lecciones para el MVP

Decisiones a replicar

  1. Stream processing core: command sourcing + event replay
  2. Single-threaded processing per partition: simplicidad > paralelismo
  3. External task pattern: probado a 7,000+ TPS en producción
  4. Local state (no DB compartida): pero con PostgreSQL en vez de RocksDB para MVP

Decisiones a NO replicar (por ahora)

  1. Raft consensus: complejidad alta, no necesario para single-node MVP
  2. Multi-partition: añadirlo cuando se necesite scale horizontal
  3. Atomix fork: fork de protocolos de consenso es costoso de mantener
  4. Custom storage engine: usar PostgreSQL probado y operacional

Decisión arquitectónica fundamental del MVP

El MVP puede mantener la filosofía de stream processing pero usar PostgreSQL como log + state store unificado:

flowchart LR
    Client[Client] --> API[API] --> Proc[Processor single-threaded]
    Proc -->|same transaction| Log[(command_log INSERT)]
    Proc -->|same transaction| State[(process_state INSERT)]

Esto da: - ✓ Command sourcing - ✓ Determinism (con ORDER BY position) - ✓ Atomic log + state - ✗ Replicación (no Raft, depender de PostgreSQL replication) - ✗ Scalability horizontal (single-partition por ahora)

Aceptable para MVP. Escalar después cuando se necesite.

Cita memorable

Del blog: "Events are the new first class citizens."

Esta frase captura la esencia del shift. En engines tradicionales, las entidades (process instance, task) son first-class. En Zeebe, los eventos lo son. Las entidades son derivadas de la suma de eventos.

Esta es la diferencia entre CRUD-centric y event-centric thinking.