Prefect — Modern Workflow Orchestration for Data Engineers
Modern Python workflow orchestration — write workflows as Python functions, deploy to cloud or self-hosted, with a clean UI and first-class async support.
✅ Pros
- + Pythonic: workflows are just decorated Python functions, not DAGs in YAML
- + Native async support — run concurrent tasks without threading complexity
- + Prefect Cloud: managed orchestration with runs, logs, and scheduling UI
- + Deployments: package and run flows from anywhere (Docker, K8s, serverless)
- + Subflows: compose workflows from other workflows
⚠️ Cons
- − Smaller operator ecosystem than Airflow
- − Prefect Cloud can be expensive for high-volume pipelines
- − Less battle-tested at extreme enterprise scale than Airflow
- − API breaking changes between Prefect 1 and 2 left some users cautious
Overview
Prefect is the modern Python alternative to Airflow. Instead of defining pipelines as DAG objects with explicit dependency graphs, Prefect workflows are just Python functions decorated with @flow and @task. Dependencies are inferred from function calls.
Basic Flow
from prefect import flow, task
from prefect.logging import get_run_logger
@task(retries=3, retry_delay_seconds=60)
def extract_data(source_url: str) -> list[dict]:
logger = get_run_logger()
logger.info(f"Extracting from {source_url}")
return fetch_api_data(source_url)
@task
def transform_data(raw: list[dict]) -> list[dict]:
return [clean_record(r) for r in raw if r.get('active')]
@task
def load_to_warehouse(data: list[dict], table: str) -> int:
write_to_bigquery(data, table)
return len(data)
@flow(name="daily-etl", log_prints=True)
def daily_etl_pipeline(source: str, table: str) -> None:
raw = extract_data(source)
clean = transform_data(raw)
count = load_to_warehouse(clean, table)
print(f"Loaded {count} records to {table}")
# Run locally
if __name__ == "__main__":
daily_etl_pipeline(
source="https://api.example.com/data",
table="analytics.events"
)
Concurrent Tasks
from prefect import flow, task
from prefect.futures import wait
@task
async def process_file(filename: str) -> dict:
data = await read_s3_file(filename)
return transform(data)
@flow
async def process_files_concurrently():
files = await list_s3_files("my-bucket/input/")
# Submit all tasks concurrently
futures = [process_file.submit(f) for f in files]
results = [f.result() for f in futures]
return results
Deployments
# Create a deployment to schedule the flow
from prefect.deployments import DeploymentSpec
DeploymentSpec(
flow=daily_etl_pipeline,
name="production",
schedule=CronSchedule(cron="0 6 * * *"), # Daily at 6am
tags=["production", "etl"],
parameters={
"source": "https://api.example.com/data",
"table": "analytics.events"
}
)
# Deploy
prefect deploy --name production
# Trigger manually
prefect deployment run 'daily-etl/production' \
--param source=https://api.example.com/other
Prefect vs Airflow
| Prefect | Airflow | |
|---|---|---|
| DAG definition | Python functions | DAG class + operators |
| Learning curve | Low | Medium-high |
| Async support | Native | Limited |
| Operator ecosystem | Smaller | 600+ operators |
| Managed cloud | Prefect Cloud | Astronomer, MWAA, Composer |
| Best for | Python teams, modern stacks | Enterprise ETL, complex integrations |
Prefect is the better default for new Python data engineering projects. Use Airflow when you need its extensive operator ecosystem or are joining an existing enterprise Airflow environment.
Best For
- Python-first data teams who find Airflow’s operator model and DAG syntax awkward — Prefect flows feel like normal Python
- ML engineers orchestrating training pipelines, feature engineering, and model evaluation with async concurrency
- Startups and small teams who need managed orchestration without the ops burden of running Airflow infrastructure
- Projects with dynamic workflows — Prefect’s conditional logic and dynamic task mapping are more natural than Airflow’s branching operators
- Teams needing retries and caching —
@task(retries=3, cache_key_fn=...)with zero boilerplate
Concrete Use Case: Orchestrating a Daily ML Feature Pipeline with Multi-Source Ingestion
A machine learning team at an e-commerce company needed to compute daily feature vectors for their product recommendation model. The pipeline pulled data from five separate sources: a PostgreSQL transactional database, a Snowflake analytics warehouse, a third-party product catalog API, a Redis cache containing real-time click-stream aggregates, and an S3 bucket with historical user behavior parquet files. Each source had different reliability characteristics — the third-party API had intermittent rate limiting, the Snowflake queries occasionally timed out during peak hours, and the S3 bucket was in a different AWS region with variable latency. The team needed retry logic per source, concurrent extraction where possible, data validation between stages, and Slack alerts when any stage failed or when the entire pipeline completed.
The team implemented the pipeline as a Prefect flow with five @task-decorated extraction functions, each configured with source-appropriate retry policies. The API extractor used retries=5, retry_delay_seconds=[30, 60, 120, 240, 480] with exponential backoff to handle rate limiting. The Snowflake task used retries=3, retry_delay_seconds=300 to wait out timeout periods. All five extraction tasks were submitted concurrently using task.submit(), and Prefect’s native async support meant the pipeline did not block waiting for the slowest source. After extraction, a transformation task joined the five datasets, computed feature vectors using pandas and scikit-learn preprocessing, and validated the output shape and null percentages. A Slack notification block sent alerts on failure with the specific task name, error message, and a link to the Prefect Cloud run log. On success, it posted a summary with row counts from each source and the total feature computation time.
The pipeline was deployed to Prefect Cloud with a CronSchedule running at 4:00 AM UTC daily, early enough that the ML training pipeline (a separate Prefect flow triggered as a subflow) could consume the fresh features before business hours. Prefect Cloud’s UI gave the team visibility into every run: which tasks succeeded, which retried and how many times, total duration trends over weeks, and parameter history. When the third-party API changed its rate limit policy, the team adjusted the retry configuration in Python and redeployed in seconds — no YAML editing, no DAG recompilation, no scheduler restart. The entire pipeline definition, including all five extractors, transformations, validations, and notifications, fit in a single 180-line Python file that any data scientist on the team could read and modify.
When to Use Prefect
Use Prefect when:
- You are building data pipelines in Python and want to define workflows as decorated functions with automatic dependency inference rather than manually constructing DAG objects
- Your pipeline requires per-task retry policies, concurrency controls, and timeout settings that differ across tasks within the same workflow
- You want a managed orchestration UI (Prefect Cloud) that provides run history, log aggregation, scheduling, and alerting without self-hosting a scheduler, metadata database, and web server
- Your team values rapid iteration — Prefect flows can be tested locally with a simple function call and deployed to production without changing the code structure
- You need to compose workflows from subflows, allowing complex pipelines to be built from reusable, independently testable components
When NOT to use Prefect:
- Your organization has an established Apache Airflow deployment with custom operators, and the cost of migrating existing DAGs outweighs the ergonomic benefits of Prefect
- You need access to Airflow’s extensive operator ecosystem (600+ operators) for integrations with enterprise systems like SAP, Oracle, or Salesforce that Prefect does not have pre-built connectors for
- Your workflow orchestration needs are simple enough that a cron job with a shell script or a managed service like AWS Step Functions would suffice without the overhead of a Python orchestration framework
- You are working in a language other than Python — Prefect is Python-only, and teams using JVM-based or polyglot pipelines should consider alternatives like Dagster, Temporal, or Apache Beam