<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Keshav</title>
    <description>The latest articles on DEV Community by Keshav (@keshav07).</description>
    <link>https://dev.to/keshav07</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4209411%2F23249a96-9f60-4a4b-a567-b7669bb79d50.jpg</url>
      <title>DEV Community: Keshav</title>
      <link>https://dev.to/keshav07</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/keshav07"/>
    <language>en</language>
    <item>
      <title>Airflow vs Dagster vs Prefect: How to Choose</title>
      <dc:creator>Keshav</dc:creator>
      <pubDate>Mon, 26 Oct 2026 19:21:04 +0000</pubDate>
      <link>https://dev.to/keshav07/airflow-vs-dagster-vs-prefect-how-to-choose-211o</link>
      <guid>https://dev.to/keshav07/airflow-vs-dagster-vs-prefect-how-to-choose-211o</guid>
      <description>&lt;p&gt;Airflow, Dagster and Prefect are all open-source Python orchestrators. All three schedule pipelines, retry failed steps, keep a run history, show it in a web UI and run on Kubernetes or a cloud container service. "Which one is best?" has no general answer. What differs is the &lt;strong&gt;unit you model&lt;/strong&gt; (a task, a data asset, or a Python function call), how much infrastructure you operate, and how far your existing code and skills already lean one way.&lt;/p&gt;

&lt;p&gt;This lesson compares them as a decision framework. It assumes you know Airflow's basics from &lt;a href="https://datadank.com/airflow/dag-fundamentals-taskflow/" rel="noopener noreferrer"&gt;Dag fundamentals and TaskFlow&lt;/a&gt; and &lt;a href="https://datadank.com/airflow/dags-scheduling-retries/" rel="noopener noreferrer"&gt;scheduling and assets&lt;/a&gt;, and does not re-teach them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versions compared
&lt;/h2&gt;

&lt;p&gt;As of October 2026: &lt;strong&gt;Apache Airflow 3.x&lt;/strong&gt; (3.3.2 used here), &lt;strong&gt;Dagster 1.x&lt;/strong&gt; (1.13.26) and &lt;strong&gt;Prefect 3.x&lt;/strong&gt; (3.8.8). Older comparisons are often wrong now. Airflow 3 renamed datasets to &lt;strong&gt;assets&lt;/strong&gt; and added event-driven scheduling; in Prefect 3 &lt;strong&gt;workers&lt;/strong&gt; are standard, replacing the agents that were deprecated during Prefect 2. Dagster stays on 1.x and its documentation now scaffolds and runs projects with the &lt;code&gt;create-dagster&lt;/code&gt; and &lt;code&gt;dg&lt;/code&gt; command-line tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same pipeline in all three
&lt;/h2&gt;

&lt;p&gt;A tiny extract, transform, load job: three order rows go in, revenue per country comes out, and it is written to SQLite with &lt;code&gt;INSERT OR REPLACE&lt;/code&gt;, so running it twice gives the same table. In each tool the business logic is the same; only the wrapping changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Airflow: a Dag of tasks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow.sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="n"&gt;DB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@dag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@daily&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;catchup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;orders_pipeline&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nd"&gt;@task&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;120.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;80.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nd"&gt;@task&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;

    &lt;span class="nd"&gt;@task&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE IF NOT EXISTS revenue (country TEXT PRIMARY KEY, total REAL)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executemany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT OR REPLACE INTO revenue VALUES (?, ?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loaded &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;


&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;orders_pipeline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dag.test()&lt;/code&gt; executes a real Dag run, so the file has to sit in the Dags folder and the metadata database must exist (&lt;code&gt;airflow db migrate&lt;/code&gt;). Selected lines of the output (the full log is about 50 structured lines):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loaded 2 rows: {'IN': 150.0, 'US': 80.0}
... DagRun Finished: dag_id=orders_pipeline, ... run_duration=7.226455, state=success, run_type=manual, ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The return values travel between tasks as XComs, which is fine for a small dict and wrong for a DataFrame; see &lt;a href="https://datadank.com/airflow/xcom-variables-connections/" rel="noopener noreferrer"&gt;XCom, Variables and Connections&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dagster: three software-defined assets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dagster&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;

&lt;span class="n"&gt;DB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@dg.asset&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;raw_orders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;120.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;80.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="nd"&gt;@dg.asset&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;country_revenue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw_orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;


&lt;span class="nd"&gt;@dg.asset&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;revenue_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country_revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaterializeResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE IF NOT EXISTS revenue (country TEXT PRIMARY KEY, total REAL)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executemany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT OR REPLACE INTO revenue VALUES (?, ?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country_revenue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MaterializeResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country_revenue&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;materialize&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;raw_orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country_revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;revenue_table&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country_revenue:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output_for_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country_revenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no explicit dependency wiring: the parameter name &lt;code&gt;raw_orders&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; the dependency on the asset of that name. Selected output lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;... raw_orders - Writing file at: /tmp/tmpd69noogr/storage/raw_orders using PickledObjectFilesystemIOManager...
... raw_orders - ASSET_MATERIALIZATION - Materialized value raw_orders.
... revenue_table - STEP_SUCCESS - Finished execution of step "revenue_table" in 252ms.
... RUN_SUCCESS - Finished execution of run for "__ephemeral_asset_job__".
success: True
country_revenue: {'IN': 150.0, 'US': 80.0}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the line about the IO manager: Dagster stored each returned value (pickled, in a temporary directory) and loaded it for the downstream asset. In production you choose an IO manager that writes to your warehouse or object store, or you write the data yourself, as &lt;code&gt;revenue_table&lt;/code&gt; does, and return only metadata.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefect: a flow calling tasks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;prefect&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="n"&gt;DB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;120.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;80.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;


&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE IF NOT EXISTS revenue (country TEXT PRIMARY KEY, total REAL)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executemany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT OR REPLACE INTO revenue VALUES (?, ?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_prints&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;orders_pipeline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loaded &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;orders_pipeline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;07:22:44.864 | INFO    | prefect - Starting temporary server on http://127.0.0.1:8282
See https://docs.prefect.io/v3/concepts/server#how-to-guides for more information on running a dedicated Prefect server.
07:22:51.455 | INFO    | Flow run 'fierce-kagu' - Beginning flow run 'fierce-kagu' for flow 'orders-pipeline'
07:22:51.533 | INFO    | Task run 'extract-d55' - Finished in state Completed()
07:22:51.538 | INFO    | Task run 'transform-5d2' - Finished in state Completed()
07:22:51.549 | INFO    | Task run 'load-185' - Finished in state Completed()
07:22:51.550 | INFO    | Flow run 'fierce-kagu' - loaded 2 rows
07:22:52.532 | INFO    | Flow run 'fierce-kagu' - Finished in state Completed()
07:22:52.563 | INFO    | prefect - Stopping temporary server on http://127.0.0.1:8282
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling the flow is just calling a Python function. With no server configured, Prefect started a temporary local API server for the run and stopped it afterwards. Data passed between tasks as ordinary return values in one process.&lt;/p&gt;

&lt;p&gt;What the three snippets show: the Airflow and Prefect versions look almost identical, but Airflow parses the file into a Dag the scheduler stores and runs task by task, while Prefect discovers the graph as the function executes. Dagster's version names &lt;strong&gt;things that exist&lt;/strong&gt; (&lt;code&gt;raw_orders&lt;/code&gt;, &lt;code&gt;country_revenue&lt;/code&gt;, &lt;code&gt;revenue_table&lt;/code&gt;) instead of &lt;strong&gt;steps that run&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Programming model
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Airflow: Dags and tasks.&lt;/strong&gt; A Dag declares tasks and their dependencies; the Dag processor parses the file and stores a serialized version, and the scheduler creates runs of it. The graph is fixed when the file is parsed, with dynamic task mapping for fan-out at run time. Since Airflow 3, assets are first-class: a task can declare &lt;code&gt;outlets&lt;/code&gt;, a Dag can be scheduled on assets, and the &lt;code&gt;@asset&lt;/code&gt; decorator is shorthand for a Dag with one task that updates one asset. Assets are still attached to tasks; the task is the primary unit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dagster: software-defined assets.&lt;/strong&gt; The Dagster docs define an asset as an object in persistent storage (a table, file or model) and an asset definition as code describing what should exist and how to produce it. Dependencies are declared on the assets, so the asset graph exists before anything runs, and "run this pipeline" becomes "materialize this selection of assets". Dagster also has ops, graphs and jobs for task-style work, but you do not need them to use assets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefect: flows and tasks.&lt;/strong&gt; Flows and tasks are decorated Python functions, and dependencies are whatever your code does: function calls, loops, &lt;code&gt;if&lt;/code&gt; statements. Calling a task runs it in the caller's thread by default; &lt;code&gt;.submit()&lt;/code&gt; and &lt;code&gt;.map()&lt;/code&gt; send it to a task runner for concurrency (a thread pool by default, process pools, or Dask and Ray through extras). The graph is recorded at run time rather than declared up front, which makes data-dependent branching natural and makes "what will this run do?" harder to answer before it runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and event triggers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Airflow 3&lt;/th&gt;
&lt;th&gt;Dagster 1.x&lt;/th&gt;
&lt;th&gt;Prefect 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time-based&lt;/td&gt;
&lt;td&gt;Cron and presets, timetables, data intervals with catchup and backfill&lt;/td&gt;
&lt;td&gt;Schedules (cron) on jobs or asset selections; time-partitioned assets&lt;/td&gt;
&lt;td&gt;Schedules (cron, interval, RRule) on deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data-aware&lt;/td&gt;
&lt;td&gt;Dags scheduled on asset updates, combined with AND and OR conditions&lt;/td&gt;
&lt;td&gt;Declarative Automation: conditions on assets such as &lt;code&gt;on_cron&lt;/code&gt;, &lt;code&gt;eager&lt;/code&gt;, &lt;code&gt;on_missing&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Deployment triggers: run a deployment when matching events arrive, for example another flow run completing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External events&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AssetWatcher&lt;/code&gt; on an asset with an event trigger, for example on a message queue (added in 3.0)&lt;/td&gt;
&lt;td&gt;Sensors (polling with cursors and run keys); pushing through the GraphQL API&lt;/td&gt;
&lt;td&gt;Automations on events; webhooks in Prefect Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absence of events&lt;/td&gt;
&lt;td&gt;Deadline Alerts on Dag runs (experimental since 3.1)&lt;/td&gt;
&lt;td&gt;Freshness policies (still under active development) with freshness alerts&lt;/td&gt;
&lt;td&gt;Proactive triggers: fire when an expected event does &lt;em&gt;not&lt;/em&gt; happen&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two details catch people out. Airflow's event-driven scheduling only accepts triggers that inherit from &lt;code&gt;BaseEventTrigger&lt;/code&gt;, not every deferrable trigger. Dagster's Declarative Automation only acts once the default automation condition sensor is switched on in the UI. For Airflow's interval semantics (logical date, data interval, catchup), see &lt;a href="https://datadank.com/airflow/dags-scheduling-retries/" rel="noopener noreferrer"&gt;scheduling and assets&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data awareness and lineage
&lt;/h2&gt;

&lt;p&gt;This is where the tools differ most.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dagster&lt;/strong&gt; puts the asset graph at the centre of the UI: each asset shows its upstreams, last materialization, metadata and &lt;strong&gt;asset checks&lt;/strong&gt; (data-quality tests attached to an asset). The dbt integration loads each dbt model as an asset, so dbt models and Python assets appear in one graph and can be selected together. Dagster+ adds an asset catalog with column-level lineage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Airflow&lt;/strong&gt; tracks assets by URI and shows which Dags produce and consume them; asset events can carry extra metadata (for example a row count). For lineage across systems, the OpenLineage provider emits lineage events to an OpenLineage-compatible backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefect&lt;/strong&gt; models assets with the &lt;code&gt;@materialize&lt;/code&gt; decorator and a URI key, infers dependencies from how materializations feed each other, and tracks them across workflows. Its documentation marks assets as a Prefect Cloud feature, so check availability before planning around it on a self-hosted server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If lineage and "is this table fresh and tested?" are the questions your stakeholders ask most, that favours Dagster's model. If they ask "did last night's run succeed?", all three answer it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local development and testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Airflow&lt;/strong&gt;: &lt;code&gt;dag.test()&lt;/code&gt; runs a whole Dag in one process for debugging, but it needs an initialised metadata database and a Dag inside a Dag bundle (the Dags folder by default). Unit tests usually call the task's Python function or load the &lt;code&gt;DagBag&lt;/code&gt; to check that files import. The full approach is in &lt;a href="https://datadank.com/airflow/best-practices-testing-cicd/" rel="noopener noreferrer"&gt;testing and CI/CD&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dagster&lt;/strong&gt;: assets can be called directly in unit tests with plain inputs and mocked resources, and &lt;code&gt;materialize()&lt;/code&gt; runs a selection in process, as in the example. &lt;code&gt;dg dev&lt;/code&gt; starts the local UI on a project scaffolded with &lt;code&gt;create-dagster&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefect&lt;/strong&gt;: flows and tasks are called like functions; &lt;code&gt;prefect_test_harness()&lt;/code&gt; runs them against a temporary database in tests. The example ran with nothing installed beyond &lt;code&gt;pip install prefect&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prefect and Dagster have the shorter loop from "edit" to "see it run". Airflow's loop got shorter with &lt;code&gt;dag.test()&lt;/code&gt;, but you still carry a metadata database and the parse-then-run model, which matters for tests that build Dags in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment and scaling
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Airflow 3&lt;/th&gt;
&lt;th&gt;Dagster OSS&lt;/th&gt;
&lt;th&gt;Prefect 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Long-running services&lt;/td&gt;
&lt;td&gt;Scheduler, Dag processor, API server, metadata database; triggerer for deferrable tasks; workers depending on executor&lt;/td&gt;
&lt;td&gt;Webserver, daemon (schedules, sensors, run queue), code location servers, database&lt;/td&gt;
&lt;td&gt;Prefect server (API, UI) with its database; workers for hybrid work pools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How work is placed&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Executor&lt;/strong&gt; in the scheduler: Local, Celery, Kubernetes, Edge, AWS ECS and others; several executors at once since 2.10&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Run launcher&lt;/strong&gt; starts a run worker (process, Docker, Kubernetes job, ECS task); an &lt;strong&gt;executor&lt;/strong&gt; inside the run runs steps (multiprocess by default, or a pod or task per step)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Work pool&lt;/strong&gt; holds infrastructure config; a typed &lt;strong&gt;worker&lt;/strong&gt; polls it and starts each flow run (process, Docker, Kubernetes, ECS, Cloud Run and others)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Granularity of isolation&lt;/td&gt;
&lt;td&gt;Per task&lt;/td&gt;
&lt;td&gt;Per run, or per step with a step executor&lt;/td&gt;
&lt;td&gt;Per flow run; tasks run inside it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User code isolation&lt;/td&gt;
&lt;td&gt;Task SDK: tasks talk to the API server, not the metadata database&lt;/td&gt;
&lt;td&gt;Code locations run in their own processes, separate from the webserver and daemon&lt;/td&gt;
&lt;td&gt;Flow code is pulled from an image, Git or storage by the worker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The isolation row matters for cost and reliability. Airflow isolates tasks (with the KubernetesExecutor each task is its own pod); Prefect isolates flow runs, and tasks inside one run share its process and memory; Dagster defaults to a process per step inside a run and can go to a pod per step. In all three, heavy computation should still run in Spark, the warehouse or a container the task launches, not in the orchestrator's workers. Scaling the Airflow side is covered in &lt;a href="https://datadank.com/airflow/executors-scaling/" rel="noopener noreferrer"&gt;executors, pools and scaling&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed offerings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Airflow&lt;/strong&gt;: no first-party managed service from the Apache project. The Airflow ecosystem page lists Astronomer's Astro, Google Cloud Composer, Amazon MWAA and Azure's managed Airflow, among others. Each runs its own release schedule and supported versions, so check whether the Airflow 3 version you need is available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dagster+&lt;/strong&gt;: run by the Dagster company in two forms, &lt;strong&gt;Serverless&lt;/strong&gt; (your code runs in Dagster's environment) and &lt;strong&gt;Hybrid&lt;/strong&gt; (Dagster+ runs the control plane, your agent and code run in your infrastructure). It adds Insights (cost and performance trends), alerting, RBAC and audit logs, the asset catalog and branch deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefect Cloud&lt;/strong&gt;: a hosted control plane that, per the self-hosting docs, adds organisation features such as RBAC, audit logs and SSO on top of what a self-hosted server provides. Besides hybrid work pools with your own workers, it offers &lt;strong&gt;push&lt;/strong&gt; work pools (runs submitted straight to serverless cloud infrastructure, no worker) and &lt;strong&gt;Prefect Managed&lt;/strong&gt; pools (runs on Prefect's infrastructure).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three can be self-hosted. The real question is who upgrades, scales and secures the control plane, and whether your company already buys one of these services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem and providers
&lt;/h2&gt;

&lt;p&gt;Airflow's ecosystem is the largest and oldest: more than 80 community-maintained provider packages (cloud services, databases, warehouses, dbt Cloud, Kafka, Spark and more) that bundle hooks, operators, sensors and asset URI schemes, plus third-party ones. Dagster's integration libraries (&lt;code&gt;dagster-dbt&lt;/code&gt;, &lt;code&gt;dagster-k8s&lt;/code&gt;, &lt;code&gt;dagster-aws&lt;/code&gt; and many more) are built around assets, for example loading dbt models as assets. Prefect's integrations are PyPI packages (&lt;code&gt;prefect-aws&lt;/code&gt;, &lt;code&gt;prefect-gcp&lt;/code&gt;, &lt;code&gt;prefect-kubernetes&lt;/code&gt;, and others) that add blocks, tasks and worker types; because a Prefect task is any function, a plain SDK call is often all you need.&lt;/p&gt;

&lt;p&gt;If you depend on a specific operator (a vendor's transfer operator, a mature sensor), check that it exists in the other tool before assuming parity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;All three UIs show run history, per-step logs and states, and retries. The difference is the default view: Airflow centres on &lt;strong&gt;Dags and task instances&lt;/strong&gt; (grid and graph views per run); Dagster on the &lt;strong&gt;asset graph and asset health&lt;/strong&gt;, with runs underneath; Prefect on &lt;strong&gt;flow runs and their states&lt;/strong&gt;, with automations that turn state changes (or a run stuck in Running) into notifications or actions. For alerting design that applies to any of them, see &lt;a href="https://datadank.com/etl-elt/pipeline-observability/" rel="noopener noreferrer"&gt;pipeline observability&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Airflow 3&lt;/th&gt;
&lt;th&gt;Dagster 1.x&lt;/th&gt;
&lt;th&gt;Prefect 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit you model&lt;/td&gt;
&lt;td&gt;Task in a Dag&lt;/td&gt;
&lt;td&gt;Data asset&lt;/td&gt;
&lt;td&gt;Python function (flow, task)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graph defined&lt;/td&gt;
&lt;td&gt;At parse time (plus dynamic mapping)&lt;/td&gt;
&lt;td&gt;Before run, from asset dependencies&lt;/td&gt;
&lt;td&gt;At run time, by your code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data passing&lt;/td&gt;
&lt;td&gt;XCom for small values; data in external storage&lt;/td&gt;
&lt;td&gt;IO managers, or write yourself and return metadata&lt;/td&gt;
&lt;td&gt;Return values in the flow's process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data-aware scheduling&lt;/td&gt;
&lt;td&gt;Assets, asset expressions, asset watchers&lt;/td&gt;
&lt;td&gt;Declarative Automation, asset sensors&lt;/td&gt;
&lt;td&gt;Event triggers on deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lineage&lt;/td&gt;
&lt;td&gt;Asset graph; OpenLineage provider&lt;/td&gt;
&lt;td&gt;Asset graph, asset checks; column lineage in Dagster+&lt;/td&gt;
&lt;td&gt;Assets (Prefect Cloud)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local run&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dag.test()&lt;/code&gt; with a metadata DB&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;materialize()&lt;/code&gt;, &lt;code&gt;dg dev&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Call the flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution placement&lt;/td&gt;
&lt;td&gt;Executors (Local, Celery, Kubernetes, Edge, ECS)&lt;/td&gt;
&lt;td&gt;Run launchers plus executors&lt;/td&gt;
&lt;td&gt;Work pools and workers; push and managed pools in Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed options&lt;/td&gt;
&lt;td&gt;Astro, Cloud Composer, MWAA and others&lt;/td&gt;
&lt;td&gt;Dagster+ Serverless and Hybrid&lt;/td&gt;
&lt;td&gt;Prefect Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem&lt;/td&gt;
&lt;td&gt;Largest provider ecosystem&lt;/td&gt;
&lt;td&gt;Strong on dbt and the modern data stack&lt;/td&gt;
&lt;td&gt;Python packages; any SDK works in a task&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Migration cost
&lt;/h2&gt;

&lt;p&gt;Switching orchestrators is rarely about the decorator syntax; it is everything around it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rewriting definitions.&lt;/strong&gt; Each Dag, operator and sensor needs an equivalent. Task-to-task moves (Airflow to Prefect) map fairly directly; Prefect's migration guide maps Dags to flows, operators to tasks, executors to work pools and workers, and XComs to return values. Moving to Dagster means remodelling tasks as assets, which is more work and also where the benefit is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling semantics.&lt;/strong&gt; Airflow's data intervals, catchup and backfills by logical date have no one-to-one equivalent; Dagster uses partitions, Prefect passes parameters. Any task that relies on &lt;code&gt;data_interval_start&lt;/code&gt; must be re-parameterised, and that is where silent bugs come from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connections and secrets.&lt;/strong&gt; Airflow Connections and Variables become Dagster resources or Prefect blocks and settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations.&lt;/strong&gt; New deployment, CI/CD, alerting, access control and on-call runbooks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running both.&lt;/strong&gt; Dagster documents an &lt;code&gt;AirflowInstanceComponent&lt;/code&gt; that peers with an existing Airflow instance so Airflow Dags appear in Dagster while you migrate incrementally. Running two orchestrators for a while is normal; plan the cut-over per pipeline, not as one big switch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Staying put and upgrading is a legitimate option too: Airflow 2 to 3 brings assets, event-driven scheduling and the Task SDK without changing tools (see the &lt;a href="https://datadank.com/interview/airflow/airflow-2-to-3-migration/" rel="noopener noreferrer"&gt;Airflow 2 to 3 migration question&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  When to choose each
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If your situation is mostly...&lt;/th&gt;
&lt;th&gt;Leaning&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Many teams, many systems, lots of time-partitioned batch jobs&lt;/td&gt;
&lt;td&gt;Airflow&lt;/td&gt;
&lt;td&gt;Mature scheduling semantics, largest provider ecosystem, managed options on every major cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An analytics platform built around tables, dbt models and their freshness and quality&lt;/td&gt;
&lt;td&gt;Dagster&lt;/td&gt;
&lt;td&gt;Assets, asset checks and lineage are the core model; dbt models become assets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python-heavy, dynamic workflows (ML, API-driven jobs, branching on data), small platform team&lt;/td&gt;
&lt;td&gt;Prefect&lt;/td&gt;
&lt;td&gt;Plain functions, graph built at run time, low ceremony to go from script to scheduled deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A large, working Airflow estate&lt;/td&gt;
&lt;td&gt;Airflow (upgrade to 3)&lt;/td&gt;
&lt;td&gt;Migration cost usually outweighs feature differences; adopt assets incrementally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A handful of jobs that all live on one platform&lt;/td&gt;
&lt;td&gt;That platform's scheduler&lt;/td&gt;
&lt;td&gt;Databricks jobs, dbt Cloud or &lt;a href="https://datadank.com/aws/step-functions/" rel="noopener noreferrer"&gt;Step Functions&lt;/a&gt; may be enough&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Comparing Airflow 2 or Prefect 2 (with agents) against current Dagster. Compare current versions.&lt;/li&gt;
&lt;li&gt;Treating Dagster as "Airflow with a different decorator" and then writing one asset per step; you lose the asset graph's value.&lt;/li&gt;
&lt;li&gt;Assuming Prefect tasks are isolated like Airflow tasks on Kubernetes; by default they share the flow run's process.&lt;/li&gt;
&lt;li&gt;Passing large data between steps (XComs in Airflow, pickled values through Dagster's default IO manager, big in-memory objects in Prefect) instead of writing to storage.&lt;/li&gt;
&lt;li&gt;Choosing on the developer loop alone and ignoring who will operate the control plane, upgrades and on-call.&lt;/li&gt;
&lt;li&gt;Forgetting that idempotency is your job in all three: retries and reruns are only safe if each step can run twice (&lt;a href="https://datadank.com/etl-elt/idempotency-in-data-pipelines/" rel="noopener noreferrer"&gt;idempotency in pipelines&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  In an interview
&lt;/h2&gt;

&lt;p&gt;For "Airflow, Dagster or Prefect?", give the framework before the answer: what you model (tasks, assets, functions), how runs are triggered, lineage and data quality needs, how the team develops and tests, who operates it, the ecosystem you depend on, and migration cost. Then commit to a recommendation &lt;strong&gt;for the scenario given&lt;/strong&gt;. For example: "For a dbt-centred analytics platform where freshness and tests matter most, I would lean Dagster for its asset graph and checks; if the company already runs 300 Airflow Dags, I would upgrade to Airflow 3 and use assets instead of migrating." Expect follow-ups on Airflow's data intervals versus partitions, how each isolates work, and what you would never run inside an orchestrator worker. A shorter version of this answer is the &lt;a href="https://datadank.com/interview/data-engineering/choose-an-orchestrator/" rel="noopener noreferrer"&gt;choosing an orchestrator interview question&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaway
&lt;/h2&gt;

&lt;p&gt;Airflow orchestrates &lt;strong&gt;tasks&lt;/strong&gt;, Dagster orchestrates &lt;strong&gt;data assets&lt;/strong&gt;, Prefect orchestrates &lt;strong&gt;Python functions&lt;/strong&gt;. All three are production-ready in their current versions. Choose by the model that matches how your team thinks about its pipelines, the operations you can carry or buy, the integrations you depend on, and what it would cost to move what you already run.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://datadank.com/airflow/airflow-vs-dagster-vs-prefect/" rel="noopener noreferrer"&gt;DataDank&lt;/a&gt;, a free Data Engineering learning site with a &lt;a href="https://datadank.com/planner/90-day-plan/" rel="noopener noreferrer"&gt;90-day interview plan&lt;/a&gt; and &lt;a href="https://datadank.com/interview/questions/" rel="noopener noreferrer"&gt;377+ interview questions&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>airflow</category>
      <category>python</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
