100X Faster: How We Supercharged Netflix Maestro’s Workflow Engine

Netflix Technology Blog
25 min readadvanced
--
View Original

Overview

Netflix redesigned Maestro's internal workflow engine, replacing the legacy Conductor 2.x-based stateless worker model with a custom stateful actor model built on Java 21 virtual threads. This architectural evolution achieved a 100X performance improvement, reducing step launch overhead from ~5 seconds to 50 milliseconds, while maintaining horizontal scalability and adding stronger execution guarantees through generation ID-based ownership, in-memory state management, and flow group partitioning.

What You'll Learn

1

How to replace a stateless polling-based worker model with a stateful actor model for 100X workflow engine performance gains

2

How to use Java 21 virtual threads to implement an actor-based distributed flow engine with minimal code complexity

3

How to design flow group partitioning for horizontal scalability while maintaining in-memory state locality

4

How to implement generation ID-based ownership to provide strong execution guarantees and eliminate race conditions in distributed systems

5

How to plan and execute a zero-downtime migration of 60,000+ active workflows to a new engine architecture

Prerequisites & Requirements

  • Understanding of distributed systems concepts including state machines, race conditions, and eventual consistency
  • Familiarity with workflow orchestration concepts such as DAG execution, step lifecycle management, and job scheduling
  • Understanding of the actor model pattern and message-driven architectures
  • Knowledge of Java concurrency primitives and JVM memory management
  • Experience with large-scale data pipeline orchestration systems(optional)

Key Questions Answered

How did Netflix achieve 100X performance improvement in Maestro's workflow engine?
Netflix replaced the legacy Conductor 2.x-based stateless worker model with a custom stateful actor model that keeps flow and task states in JVM memory. By collocating all tasks of a workflow on the same node, eliminating distributed queue polling overhead, using a write-through caching pattern with the database as source of truth, and implementing Java 21 virtual threads for actors, step launch overhead dropped from ~5 seconds to 50 milliseconds.
Why did Netflix choose to build a custom flow engine instead of using Conductor 4 or Temporal?
Conductor 4 dropped a critical final callback capability that Maestro relied on for database synchronization, and would require porting that functionality. Temporal is optimized for inter-process orchestration requiring external service calls, which introduced unnecessary coupling risk for Maestro's million+ daily tasks. Building a custom engine allowed Netflix to simplify the architecture by eliminating dual database reconciliation and removing features Maestro didn't need.
How does Maestro maintain horizontal scalability with in-memory state?
Maestro introduced a flow group concept that partitions running flows into groups using a stable ID assignment method. Each engine node owns a range of group IDs rather than individual flows, reducing heartbeat maintenance costs. If a node fails or experiences long GC pauses, another node claims the group and rebuilds internal state from the Maestro database through reconciliation, ensuring fault tolerance while maintaining locality benefits.
How does Maestro prevent race conditions in its distributed workflow engine?
Maestro uses a generation ID-based solution where each flow group has an incrementing generation ID. When a node claims a group, it increments the generation ID in the database. All operations verify the database generation ID matches the in-memory ID before proceeding. Combined with single-actor-per-entity execution via virtual threads and a database-backed queue with exactly-once publishing, this eliminates dual-worker execution and stale state race conditions.
How does Netflix use Java virtual threads in Maestro's actor model?
Each workflow instance or step attempt gets a dedicated virtual thread acting as an actor with an in-memory state, thread-safe blocking queue, and state machine. Virtual threads handle state machine transitions efficiently and are lightweight enough to create in large numbers without OOM risks. However, actual step runtime business logic (container launches, external API calls) runs on separate worker thread pools to avoid virtual thread deadlock issues.
How did Netflix migrate 60,000 workflows to the new engine without user disruption?
Netflix established parallel infrastructure for the new engine and used an orchestrator gateway API to hide routing logic. They leveraged Maestro's 10-day workflow timeout to gradually drain traffic: existing executions completed or timed out on the old engine, and upon restart or new triggers, workflows were picked up by the new engine. Traffic was scaled proportionally between stacks to minimize cost increase, with percentage-based cutover once confidence was established.
What is the transactional outbox pattern used in Maestro's queue optimization?
Maestro implements a database-backed in-memory queue where a row is inserted into the maestro_queue table in the same transaction that updates Maestro tables. Upon transaction commit, the job is immediately pushed to a queue worker on the same node, eliminating polling latency. After successful processing, the worker deletes the row. A periodic sweeper re-enqueues rows with expired timeouts, providing exactly-once publishing and at-least-once delivery guarantees.
What challenges did Netflix encounter during the Maestro engine migration?
About 50 workflows with defunct ownership information entered stuck states, some creating race conditions where new instances perpetually kept workflows on the old engine. Configuration discrepancies between parallel infrastructure stacks caused a partner team's Python migration process to silently skip feature flags, resulting in incorrect Python version configurations for ~40 workflows. These highlighted the importance of meticulous parallel infrastructure management and testing runtime configuration from external APIs.

Key Statistics & Figures

Performance improvement
100X
Overall engine overhead reduction from seconds to milliseconds
Step launch overhead (before)
~5 seconds
Time for step launch in the original flow engine
Step launch overhead (after)
50 milliseconds
Time for step launch in the new flow engine
Workflow start overhead (before)
200 milliseconds
Incurred once per workflow execution in old engine
Workflow start overhead (after)
50 milliseconds
Incurred once per workflow execution in new engine
Daily step executions
Over 1 million
Daily data processing tasks managed by Maestro
Cumulative overhead saved per day
~57 days
Aggregated flow engine overhead savings across million+ daily step executions
Active workflows migrated
60,000+
Workflows migrated to new engine with almost no user involvement
Obsolete database storage deleted
~40TB
Obsolete tables related to the previous stateless flow engine
Database query traffic reduction
90%
Reduction in internal database query traffic after migration
Stuck workflows during migration
~50
Workflows with defunct ownership that entered stuck state during rollout
Workflows affected by config discrepancy
~40
Workflows with incorrect Python version due to missing feature flag

Technologies & Tools

Some links below are affiliate links. We may earn a commission if you make a purchase.

Programming Language
Java 21
Virtual threads for implementing the actor-based flow engine with minimal dependencies
Workflow Orchestrator
Netflix Maestro
The horizontally scalable workflow orchestrator for managing Data/ML workflows at Netflix
Workflow Engine
Netflix Conductor 2.x
The previous internal flow engine layer (deprecated since April 2021) that was replaced
Workflow Engine
Temporal
Evaluated as an alternative internal flow engine option but rejected due to external service coupling concerns
Compute Engine
Spark
One of the Netflix compute engines integrated with Maestro
Compute Engine
Trino
One of the Netflix compute engines integrated with Maestro
Cloud Storage
S3
Storage for workflow YAML definitions, subworkflow IDs, and cached test data

Key Actionable Insights

1
Consider replacing stateless polling-based architectures with stateful actor models when workflow engine overhead becomes a bottleneck. The key technique is collocating all tasks of a workflow on a single node with in-memory state, using the database only as source of truth with a write-through caching pattern. This eliminates the overhead of distributed queue polling, state loading, and multi-database reconciliation.
Netflix reduced step launch overhead from ~5 seconds to 50 milliseconds by making this architectural shift, saving approximately 57 days of cumulative overhead per day across a million daily step executions.
2
Use Java 21 virtual threads for state machine transitions and actor implementations, but separate them from actual business logic execution. Virtual threads are ideal for lightweight coordination tasks but can deadlock when executing user-provided or complex external logic. Create a dedicated worker thread pool for heavy execution tasks and have virtual thread actors wait on futures from that pool.
This separation allowed Netflix to benefit from virtual threads' lightweight nature and scalability while avoiding deadlock risks from external library calls or service interactions within the actor model.
3
Implement generation ID-based ownership to provide strong execution guarantees in distributed systems. When a node claims ownership of a resource group, increment a generation ID in the database. All subsequent operations must verify their generation ID matches the database before proceeding, automatically invalidating stale actors from failed or partitioned nodes.
This pattern eliminates race conditions where two workers execute the same task simultaneously, which in Netflix's case caused stuck jobs, lost executions, and conflicting workflow states requiring manual intervention.
4
When migrating large-scale distributed systems, use a parallel infrastructure approach with an API gateway to transparently route traffic between old and new systems. Scale up the new infrastructure proportionally as you scale down the old to keep costs negligible. Leverage natural system timeouts to drain traffic rather than forcing migrations.
Netflix used Maestro's 10-day workflow timeout to naturally drain traffic from the old engine. Existing executions completed or timed out, and upon restart or new triggers, workflows were automatically picked up by the new engine with no user involvement.
5
Use the transactional outbox pattern for internal job queues to achieve exactly-once publishing and at-least-once delivery without external distributed queue dependencies. Insert queue messages in the same database transaction as state updates, push to an in-memory worker immediately on commit, and use a periodic sweeper for timeout-based retry.
This approach eliminated the need for external distributed job queues that could only provide at-least-once guarantees, which previously caused tasks to be dispatched to multiple workers and created race conditions.
6
When operating parallel infrastructure during migrations, maintain meticulous parity of all configuration including alerts, system flags, feature flags, and partner team integrations. Configuration discrepancies between stacks can cause silent failures that are hard to detect and may affect downstream systems in unexpected ways.
Netflix discovered that missing feature flags in the new engine stack caused a partner team's Python migration process to silently skip configuration, resulting in incorrect Python versions for ~40 workflows and requiring user-impacting restarts.

Common Pitfalls

1
Using distributed job queues with only at-least-once delivery guarantees for workflow task scheduling. Tasks can be dequeued and dispatched to multiple workers simultaneously, and workers may reset states in race conditions or load stale states and make incorrect decisions. After GC pauses or network hiccups, two workers can pick up the same task, causing one to mark it completed while another resets it to running.
Netflix solved this by replacing external distributed queues with a database-backed in-memory queue implementing exactly-once publishing and at-least-once delivery via the transactional outbox pattern.
2
Maintaining dual database state across separate systems (e.g., Conductor tables and application tables) that must be synchronized. This creates race conditions, orphaned job statuses, and requires complex reconciliation logic that adds operational overhead and reliability issues.
Netflix eliminated this by making the Maestro engine database the single source of truth and using in-memory state with write-through caching, removing the need for multi-database synchronization.
3
Running user-provided logic or complex external library calls on Java virtual threads, which can lead to deadlocks. Virtual threads are well-suited for lightweight state machine transitions but not for executing business logic that depends on external services or libraries outside your control.
Netflix addressed this by separating flow engine execution (virtual threads) from task execution logic (dedicated worker thread pool), allowing actors to wait on futures without performing actual execution.
4
Failing to maintain configuration parity across parallel infrastructure during migrations. Alerts, system flags, feature flags, and partner integrations configured for one stack but not the other can cause silent failures in downstream systems that are difficult to detect.
Netflix discovered that a missing feature flag in the new engine stack caused a partner team's Python migration to be silently skipped, affecting ~40 workflows with incorrect configurations.
5
Underestimating edge cases when draining traffic from old to new infrastructure during migrations. Workflows with defunct ownership or queued instance backlogs can create race conditions that perpetually keep workflows on the old engine, preventing natural migration through timeout-based draining.
Netflix encountered ~50 stuck workflows that required proactive user contact to negotiate manual stop-and-restart times to force them onto the new engine.

Related Concepts

Actor Model Pattern
Write-through Caching
Transactional Outbox Pattern
Java Virtual Threads
Dag Execution Engines
Workflow Orchestration
Distributed Systems Consistency
Flow Partitioning And Group Ownership
Generation Id-based Concurrency Control
Event-driven Architecture
Horizontal Scalability
State Machine Design
Blue-green Deployment Migration
Exactly-once Semantics
Data/ML Pipeline Orchestration