After studying this material, you should be able to:
Modern applications at scale are built from microservices: small, independent services that each do one thing. But business processes require many steps in sequence or parallel.
Example: Publishing a Netflix title requires:
Without orchestration, coordinating these steps means:
Conductor acts as a central coordinator that:
[Service A] → [Service B] → [Service C]
↑ ↑ ↑
└───────[Conductor]────────────┘
(tracks state,
retries failures,
provides visibility)
Key Definition: A workflow is a declared graph of tasks. A task is one unit of work performed by a microservice.
| Metric | Then | Now |
|---|---|---|
| Workflow definitions | Hundreds | 200,000 |
| Monthly executions | ~1 billion/year | ~420 million/month |
| Max tasks per workflow | 2,500 | 30,000 |
┌─────────────────────────────────┐
│ Conductor Engine │
├─────────────────────────────────┤
│ DynoQueues │ Dynomite (Redis)│ ← Queueing + Storage
│ Elasticsearch │ ← Indexing
│ Cassandra (partial) │ ← Some execution data
└─────────────────────────────────┘
Problem 1: Dynomite is in-memory
Problem 2: DynoQueues had uneven scheduling
Problem 3: Synchronous evaluation with race conditions
Before: Dynomite (in-memory, limited scale)
After: Cassandra (disk-based, horizontally scalable)
Why Cassandra?
Additional change: Large task inputs/outputs moved to S3, keeping bulky data off the primary database.
Before: DynoQueues (built on Dynomite, uneven)
After: Timestone (high-throughput, low-latency)
Timestone added two critical capabilities:
Before: Indexing on the critical path (slows every operation)
After: Kafka buffer → independent indexer → Elasticsearch
This means indexing failures no longer block workflow execution.
┌─────────────────────────────────────┐
│ Conductor 3.0 Stack │
├─────────────────────────────────────┤
│ Timestone │ Queue │
│ Cassandra │ Execution │
│ Kafka → ES │ Indexing │
│ Iceberg │ Long-term │
│ Multi-cluster │ Isolation │
└─────────────────────────────────────┘
This is the most technically deep section. Take it in parts.
Old approach:
Evaluate workflow → Load ENTIRE workflow state into memory
→ Decide next step
→ Write back
For a workflow with 10,000 tasks, this means loading all 10,000 tasks just to advance one step.
New approach:
Evaluate workflow → Load lightweight "blueprint"
→ Load ONLY the specific task data needed
→ Decide next step
Result: 40% reduction in p99 evaluation latency, and support for 30,000 tasks (up from 2,500).
The scenario:
Worker sends: IN_PROGRESS → IN_PROGRESS → COMPLETED
↑
Network reorders: late IN_PROGRESS arrives AFTER COMPLETED
↓
Result: Task reverts to IN_PROGRESS ← BUG!
First attempt: Cassandra Lightweight Transactions (LWT)
Final solution: Two-partition model
┌─────────────────┐ ┌──────────────────┐
│ Pending Partition│ │Terminal Partition │
│ (in-flight tasks)│ │(completed/failed) │
└─────────────────┘ └──────────────────┘
Reconciliation rule (simple and elegant):
Race scenario with new model:
COMPLETED → written to Terminal Partition ✓
Late IN_PROGRESS → written to Pending Partition
On read: Terminal wins → task stays COMPLETED ✓
Old flow:
Task completes → Request evaluates workflow inline
→ Must acquire lock on workflow
→ Other updates WAIT for lock
→ Under load: lock contention → minutes of delay
New flow (Async Evaluation):
Task completes → Enqueue workflow onto Timestone exclusive queue
→ Central async evaluator processes in order
→ Only ONE worker per workflow at a time
→ Lock contention: essentially zero
Key insight: The queue is the lock. Because it's exclusive, ordering is guaranteed without explicit locking.
Imagine two workflow instances both processing the same movie simultaneously:
Instance A: reads movie data → modifies → writes
Instance B: reads movie data → modifies → writes ← overwrites A!
Teams had to build custom locking logic themselves — fragile and complex.
@RunStrategy(type = SEQUENTIAL, sequenceKey = "movieId")
@WorkflowMethod(name = "ingest_movie")
default void ingestMovie(MovieInput input) { ... }
Two strategies:
| Strategy | Behavior |
|---|---|
| Parallel (default) | No restrictions, all run simultaneously |
| Sequential | One at a time per sequence key, others queue up |
The sequence key is critical:
Movie A (id=101): Run 1 → Run 2 → Run 3 (serial)
Movie B (id=202): Run 1 → Run 2 → Run 3 (serial)
↕
Movies A and B run CONCURRENTLY with each other
Your Application Conductor
┌──────────────┐ ┌──────────┐
│ Worker Fleet │ ←── polls ──────→ │ Server │
│ (you operate)│ ──── result ────→ │ │
└──────────────┘ └──────────┘
You must: deploy, scale, and maintain the worker fleet.
Conductor drives the compute directly:
TITUS Task (containers):
Workflow step → Conductor launches Docker container on Titus
→ Monitors job
→ exit code 0 → COMPLETED
→ exit code ≠ 0 → FAILED
→ timeout → FAILED
STRATUM Task (serverless media functions):
Workflow step → Conductor invokes Stratum function
→ Waits for result
→ COMPLETED / FAILED / TIMED_OUT
Mixing all three in one workflow:
[Polled Worker] [Titus Container] [Stratum Function]
(steady volume) → (heavy batch step) → (media processing)
Task Queue A: ████████████████ (1000 items backlogged)
Task Queue B: ░░░░░░░░░░░░░░░░ (empty)
Thread allocation (fixed):
Queue A: 10 threads ← not enough!
Queue B: 10 threads ← wasted!
Result: CPU flat, queues backing up
Each polling interval:
Step 1: Poll Timestone for current depth of ALL queues
Step 2: Split thread pool into two parts:
┌─────────────────────────────────────────┐
│ Minimum Allocation: every queue gets │
│ a baseline (prevents starvation) │
├─────────────────────────────────────────┤
│ Floating Pool: remaining threads │
│ distributed proportional to queue depth │
└─────────────────────────────────────────┘
Step 3: Apply multiplier ceiling
(max growth per cycle = configurable factor)
→ prevents sudden flood of downstream services
Result:
Type safety issue:
// JSON declares: "movieId": "${task.output.movieId}"
// Worker reads:
long movieId = (Integer) inputData.get("movieId"); // ClassCastException at runtime!
long movieId = (Integer) inputData.get("movieid"); // NullPointerException! (typo)
Verbosity issue:
Two core building blocks:
| Annotation | Purpose | Executed at runtime? |
|---|---|---|
@TaskMethod | Defines a task with real types | ✅ Yes |
@WorkflowMethod | Defines workflow structure | ❌ No (parsed at build time) |
How it works:
You write Java → Gradle plugin → JSON definition → Conductor server
(build time) (same as before)
The server never sees Java. It gets the same JSON it always understood.
Type-safe example:
// Before (JSON + unsafe worker):
long movieId = (Integer) inputData.get("movieId"); // fingers crossed
// After (SDK):
@TaskMethod
public Movie getMovie(long movieId) { /* compiler checks types */ }
// Workflow wiring:
Movie movie = getMovie(input.getMovieId()); // compiler verified ✓
return wrapMovie(movie); // compiler verified ✓
Loops (always do-while in Conductor):
do {
status = checkStatus(jobId);
} while (status.getState() == "PENDING");
Parallel execution:
Fork<Movie> details = ParallelExecution.fork(() -> getMovie(id));
Fork<Artwork> art = ParallelExecution.fork(() -> getArtwork(id));
ParallelExecution.allOf(details, art); // FORK + JOIN
Sub-workflows: Just call another @WorkflowMethod like a regular method.
Here is how all the pieces connect:
┌─────────────────────────────────────────────────────┐
│ DEVELOPER EXPERIENCE │
│ Workflow SDK (type-safe Java DSL) │
│ IDE plugins, testing framework │
└──────────────────────┬──────────────────────────────┘
│ generates JSON
┌──────────────────────▼──────────────────────────────┐
│ EXECUTION CONTROL │
│ Run Strategies (Sequential / Parallel) │
│ Managed Tasks (Titus / Stratum / Polled Workers) │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────┐
│ EVALUATION ENGINE │
│ Async evaluation (no lock contention) │
│ Blueprint-based (low memory footprint) │
│ Two-partition task state (no race conditions) │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────┐
│ DATA PLANE │
│ Timestone (queuing + async eval queue) │
│ Cassandra (execution data) │
│ Kafka → Elasticsearch (indexing) │
│ S3 (large payloads) + Iceberg (long-term) │
└─────────────────────────────────────────────────────┘
| Concept | One-Line Summary |
|---|---|
| Conductor | Orchestrates microservices into reliable workflows |
| Timestone | High-throughput priority queue; backbone of async eval |
| Cassandra | Durable, horizontally scalable execution datastore |
| Async Evaluation | Decouples triggers from evaluation; eliminates lock contention |
| Two-partition model | Terminal state always wins; eliminates task state races |
| Run Strategies | Declare concurrency policy (parallel vs sequential) on workflow |
| Managed Tasks | Conductor drives compute directly (Titus/Stratum) |
| Dynamic Threads | Rebalances worker threads based on real queue depth |
| Workflow SDK | Type-safe Java DSL compiled to JSON at build time |
| Sequence Key | Groups executions that must run serially |