intermediate

Direct Runner vs Dataflow Runner

7 min readLast updated: 2026-07-01

1. Introduction

Apache Beam is designed around a "Write Once, Run Anywhere" philosophy. This abstraction is made possible by Runners. The Direct Runner executes pipelines locally on your development machine, while the Dataflow Runner deploys and executes pipelines on Google Cloud's distributed serverless infrastructure.

2. Why This Concept Exists

Developing distributed data pipelines directly on cloud infrastructure is slow and expensive:

  • Slow Feedback Loops: Waiting several minutes for cloud VMs to provision just to test a syntax or logic change is inefficient.
  • High Development Costs: Running small test batches on full cloud clusters incurs unnecessary charges.
  • Debugging Difficulties: Inspecting local variables and stdout is much simpler locally than searching through distributed cloud logs.

Conversely, running production workloads on a single development machine is impossible due to memory, CPU, and network limits. The Runner abstraction lets you transition from local development to production scale without modifying your core processing logic.

3. Key Terminology

  • Direct Runner: A local runner that executes your pipeline in a single Python process, simulating distributed execution using multiple threads.
  • Dataflow Runner: A managed cloud runner that compiles, uploads, and runs your pipeline across a scalable cluster of virtual machines in GCP.
  • Serialization: The process of converting Python code and functions into a binary format (pickle) to distribute them across network boundaries to workers.
  • Local Scope: Memory and resources restricted to the developer's workstation.
  • Distributed Scope: Resources spread across multiple distinct servers in a network.

4. How It Works

The behavior of the pipeline differs depending on the selected runner:

| Feature | Direct Runner (DirectRunner) | Dataflow Runner (DataflowRunner) | | :--- | :--- | :--- | | Execution Location | Local developer workstation | Distributed GCP Compute Engine VMs | | Parallelism | Multi-threaded within a single process | Multi-node distributed clustering | | Storage Access | Local filesystems (C:\... or /tmp/...) | Cloud Storage (gs://...), BigQuery, Pub/Sub | | Stdout (e.g. print()) | Appears immediately in local terminal | Forwarded to Cloud Logging / Dataflow Console | | Scaling | Fixed to local hardware limits | Dynamic autoscaling based on pipeline workload | | Billing | Free | Pay for Compute Engine, Storage, and Shuffle usage |

5. Visual Diagram

Direct Runner Flow (Local):
Local Pipeline
Multi-Thread Process
Local RAM / SSD
Dataflow Runner Flow (Cloud):
Local Pipeline
GCS Bucket
Worker VM Pool

6. Code Example

The following code pattern demonstrates how to configure pipeline arguments to dynamically choose between the Direct Runner and Dataflow Runner based on command-line inputs:

python
import argparse
import apache_beam as beam
from apache_beam.options.pipeline_options import PipelineOptions, GoogleCloudOptions, StandardOptions

def run_comparative_pipeline(argv=None):
    # 1. Parse command-line arguments
    parser = argparse.ArgumentParser()
    parser.add_argument("--environment", default="local", choices=["local", "cloud"])
    parser.add_argument("--output_path", required=True, help="Output destination path")
    known_args, pipeline_args = parser.parse_known_args(argv)

    # 2. Build base options
    options = PipelineOptions(pipeline_args)
    
    # 3. Configure runner specifics
    if known_args.environment == "cloud":
        # Dataflow Runner Options
        options.view_as(StandardOptions).runner = "DataflowRunner"
        
        gcp_options = options.view_as(GoogleCloudOptions)
        gcp_options.project = "my-gcp-project"
        gcp_options.region = "us-central1"
        gcp_options.staging_location = "gs://my-bucket/staging"
        gcp_options.temp_location = "gs://my-bucket/temp"
        gcp_options.job_name = "runner-comparison-job"
    else:
        # Direct Runner Options
        options.view_as(StandardOptions).runner = "DirectRunner"

    # 4. Core Pipeline (Logic remains identical)
    with beam.Pipeline(options=options) as p:
        (p
         | "Read" >> beam.Create(["banana", "apple", "cherry"])
         | "Uppercase" >> beam.Map(lambda x: x.upper())
         | "Write" >> beam.io.WriteToText(known_args.output_path)
        )

if __name__ == "__main__":
    # Example local call:
    # python script.py --environment local --output_path ./output.txt
    #
    # Example cloud call:
    # python script.py --environment cloud --output_path gs://my-bucket/output.txt
    run_comparative_pipeline()

7. Code Explanation

  • known_args, pipeline_args = parser.parse_known_args(argv) separates custom command-line arguments (like --environment) from standard Apache Beam command-line options.
  • If environment == "cloud", we assign the runner to "DataflowRunner" and configure GCP-specific options (project, region, and GCS paths).
  • If environment == "local", we run the pipeline on "DirectRunner".
  • The core transformation logic (Create, Map, WriteToText) remains completely unchanged, illustrating runner independence.

8. Real Production Example

A business intelligence team runs a pipeline to aggregate daily website user views.

  • During Development: Developers use the DirectRunner with a test dataset consisting of 100 rows. Execution completes in under 2 seconds, printing debug checkpoints directly to the VS Code terminal.
  • In Production: The deployment CI/CD pipeline triggers the same script with --environment cloud, running the pipeline on DataflowRunner against a 100 GB batch dataset loaded from Cloud Storage.

9. Common Mistakes

  • Hardcoding Local File Paths: Specifying an output directory like C:\outputs\output.txt or /tmp/output.txt and trying to run on DataflowRunner. Dataflow workers cannot access your local file system, causing the job to crash.
  • Relying on Global State variables: Writing to a global list or variable in a local Python scope inside a custom DoFn. On DirectRunner, this works because threads share memory. On DataflowRunner, VMs do not share memory, so updates to global variables will be lost.

10. Interview Perspective

  • Question: Why does my pipeline pass tests on the Direct Runner but crash on the Dataflow Runner?
  • Answer: The most common reason is serialization. Functions, custom classes, and libraries used in pipeline steps must be serializable (pickled) so they can be transmitted to remote workers. Also, remote workers must have necessary dependencies installed (configured via --setup_file or --requirements_file), whereas the local machine already has them.
  • Question: Can I debug with breakpoints (pdb) on the Dataflow Runner?
  • Answer: No. The code runs asynchronously on virtual machines inside Google's datacenters. For interactive debugging with breakpoints, you must use the DirectRunner.

11. Best Practices

  • Use DirectRunner for fast validation, unit testing, and initial pipeline development.
  • Always parameterize file input and output paths so they can dynamically toggle between local paths and Cloud Storage URLs.
  • Do not use Python global variables inside transforms; write stateless functions and pass data using PCollections.

12. Summary

  • DirectRunner executes locally on a single machine for quick iteration and testing.
  • DataflowRunner executes distributed workloads on Google Cloud.
  • Pipelines should be designed to be runner-agnostic.
  • Avoid using local state or local paths in jobs aimed at Dataflow.

13. Interactive Challenges

Challenge 1: Parse Runner Flag (Beginner)

Write a Python command-line parsing snippet that sets the StandardOptions.runner to "DataflowRunner" if --runner-flag is set to "dataflow", and "DirectRunner" otherwise.

Challenge 2: Dynamic Path Resolver (Intermediate)

Write a helper function resolve_input_path(runner_name, file_name) that returns a local file path (./data/{file_name}) if runner_name is "DirectRunner", and a GCS path (gs://my-bucket/inputs/{file_name}) if it is "DataflowRunner".

Challenge 3: TestPipeline Implementation (Advanced)

Write a complete unit test method using Apache Beam's testing utility TestPipeline to test a simple transform that filters out odd numbers from a list [1, 2, 3, 4].

14. Related Content

Advertisement
AdSense Slot #000001Leaderboard Banner (728x90)