<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[The Prefect Blog - Medium]]></title>
        <description><![CDATA[The Prefect data engineering platform - Medium]]></description>
        <link>https://medium.com/the-prefect-blog?source=rss----433b304c71e0---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>The Prefect Blog - Medium</title>
            <link>https://medium.com/the-prefect-blog?source=rss----433b304c71e0---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 04:13:35 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/the-prefect-blog" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Airflow Local Development Sucks]]></title>
            <link>https://medium.com/the-prefect-blog/airflow-local-development-sucks-2042ae5f6961?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/2042ae5f6961</guid>
            <category><![CDATA[airflow]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[workflow-automation]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-orchestration]]></category>
            <dc:creator><![CDATA[Christopher White]]></dc:creator>
            <pubDate>Tue, 29 Jul 2025 18:52:29 GMT</pubDate>
            <atom:updated>2025-07-29T18:52:28.929Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DFMjnvKOGzMwTogPY1Jzkw.png" /><figcaption>A day in the life of a data engineer using Airflow</figcaption></figure><p><strong><em>How to solve Airflow local development problems, testing challenges, and complex setup requirements with a simpler alternative</em></strong></p><p>Setting up Apache Airflow 3 for local development requires at least 4 separate services, a minimum of 4GB RAM (8GB recommended), and setup times that can stretch from hours to days — before writing a single line of workflow code. If you’re searching for “Airflow local development setup,” “Apache Airflow testing problems,” or “Airflow development environment issues,” you’re not alone.</p><p>Local development should be straightforward: write code, test it, iterate quickly. However, setting up Airflow 3 locally reveals significant architectural complexity that affects every developer who works with the framework. Despite being the biggest release in Airflow’s history, Airflow 3 maintains design decisions that prioritize production deployment over development experience.</p><p>The challenges are systematic rather than incidental. Airflow was built for ops teams managing static workflows on dedicated servers, not for developers who want to iterate quickly. Prefect, on the other hand, was built with modern expectations in mind: teams routinely report 3x improvements in development speed and over 50% reduction in cost when migrating from Airflow to Prefect.</p><h3>Airflow Local Setup Requirements: The Multi-Component Infrastructure and Configuration Problem</h3><p>Airflow 3 requires multiple services for even basic local development, each demanding careful configuration. The official Airflow documentation lists the required components:</p><ul><li><strong>Webserver</strong>: For the UI interface</li><li><strong>Scheduler</strong>: Responsible for triggering DAG runs and submitting tasks to executors</li><li><strong>DAG Processor</strong>: A separate process for parsing DAG files</li><li><strong>Database</strong>: PostgreSQL or similar for metadata storage</li></ul><p>The quickstart documentation attempts to simplify this with airflow standalone, but this approach has limitations for realistic development scenarios. When developers need to test concurrent runs, external dependencies, or realistic configurations, they must configure the full multi-component setup through extensive airflow.cfg files, environment variables, and command-line parameters.</p><p>The <a href="https://github.com/apache/airflow/blob/main/airflow-core/docs/howto/docker-compose/docker-compose.yaml">official Docker Compose setup</a> demonstrates the true scope of this complexity. The docker-compose.yaml file requires multiple separate containers with careful orchestration of startup dependencies, plus configuration management for database connections, executor selection (LocalExecutor, CeleryExecutor, or KubernetesExecutor), resource limits, timeouts, parallelism settings, and security management. The documentation recommends allocating at least 4GB of memory to Docker, with 8GB recommended for stability. Compare this with <a href="https://docs.prefect.io/v3/how-to-guides/self-hosted/docker-compose">the far simpler Prefect docker compose setup</a> that supports production work.</p><p>Because of this complexity, small configuration mistakes in Airflow can lead to silent failures, tasks hanging indefinitely, or error messages that require deep framework knowledge to decode. This configuration sprawl compounds issues when developers are attempting to debug issues with their production Airflow DAGs locally.</p><p>Much of this infrastructure complexity exists because DAGs in Airflow are not functions: they are complicated objects that the Airflow scheduler must parse and execute regularly, requiring persistent services and extensive configuration to coordinate their interactions.</p><p>In Prefect, by contrast, flows can be executed directly as functions within unit test suites, tasks can be executed individually outside of their flows, and deployments can be served locally without spinning up any Prefect services. Even schedule objects can be imported and unit tested.</p><h3>Simple Alternative: Zero Infrastructure Development</h3><p>Prefect eliminates these problems entirely: no scheduler, database, webserver, or Docker containers are required. Crucially, the exact same code path executes identically in local development and remote execution environments, eliminating the debugging complexity that comes from architectural differences between development and deployment. Workflows run as standard Python processes with zero additional infrastructure requirements and no configuration files, allowing you to focus on workflow logic rather than service orchestration:</p><pre># /// script<br># dependencies = [&quot;prefect&quot;]<br># ///<br><br>from prefect import flow, task<br><br><br>@task<br>def extract_data():<br>    return {&quot;key&quot;: &quot;value&quot;}<br><br><br>@task<br>def transform_data(data):<br>    return data[&quot;key&quot;].upper()<br><br><br>@flow<br>def my_workflow():<br>    data = extract_data()<br>    result = transform_data(data)<br>    return result<br><br><br>if __name__ == &quot;__main__&quot;:<br>    my_workflow()</pre><p><strong>💡 Try it now</strong>: Save this code as workflow.py and run uv run workflow.py - no setup, configuration, or infrastructure required!</p><h3>Airflow Docker Development Issues: Windows Compatibility and Resource Problems</h3><p>For Windows users these problems are further exacerbated. Airflow treats <a href="https://airflow.apache.org/docs/apache-airflow/stable/installation/prerequisites.html">Windows as an afterthought</a> and requires Linux-based distributions for production use. Because of this, Windows users face particular challenges as noted in community discussions: “<a href="https://dev.to/jfhbrook/how-to-run-airflow-on-windows-with-docker-2d01">running Airflow in Windows natively is dead in the water</a>”.</p><p>Common Airflow Docker development issues include:</p><ul><li><strong>Windows compatibility</strong>: Airflow has limited Windows support, forcing Windows developers to use WSL or Docker with their own complications</li><li><strong>Resource consumption</strong>: Docker setups often consume more resources than the actual work being performed</li><li><strong>Development workflow friction</strong>: Mounting volumes, managing container lifecycle, and debugging across containerized services adds overhead</li><li><strong>Network complexity</strong>: Getting proper communication between scheduler, webserver, and database requires careful configuration</li><li><strong>Iteration speed</strong>: Code changes require container rebuilds or volume sync delays</li></ul><p>The Docker approach introduces architectural differences between local development environments and typical deployment scenarios, making it difficult to debug environment-specific issues.</p><p>Prefect runs natively on all platforms, including <a href="https://docs.prefect.io/v3/how-to-guides/self-hosted/server-windows">full Windows support</a>. No Docker, WSL, or virtual machines are required for development.</p><h3>Airflow Task Isolation Problems: Development Friction and Debugging Challenges</h3><p>While separating orchestration from business logic is critical for workflow systems, Airflow 3 takes this to an extreme that complicates development. It encourages submitting every task through an executor, adding infrastructure overhead even for simple development scenarios. Moreover, users cannot submit tasks directly themselves — the DAG processor and scheduler must parse the code for submission on their respective loops.</p><p>Airflow’s task isolation means:</p><ul><li><strong>Executor dependency</strong>: Even basic testing requires configuring and running an executor</li><li><strong>Process boundaries</strong>: Tasks run in separate processes or containers, making debugging complex</li><li><strong>Resource overhead</strong>: Each task submission involves scheduler communication and process spawning</li><li><strong>Development friction</strong>: Simple changes require full infrastructure restarts</li></ul><p>Prefect balances isolation with development simplicity. Tasks can run in the same process for development and testing, while still supporting distributed execution when needed. The isolation level is configurable rather than mandatory.</p><p>Additionally, Prefect supports <a href="https://docs.prefect.io/v3/advanced/submit-flows-directly-to-dynamic-infrastructure">direct submission to infrastructure</a> without managing persistent processes. Infrastructure is provisioned on-demand, executes the workflow, and tears down automatically.</p><h3>Configuration-Free Development Alternative</h3><p>Prefect requires zero configuration for local development. It uses sensible defaults and configuration-as-code patterns that eliminate setup overhead. The same code that runs locally executes identically in remote environments, ensuring consistent behavior across development and deployment contexts.</p><p>As an example, the following code will reliably execute the simple workflow on a cron schedule with zero configuration:</p><pre># /// script<br># dependencies = [&quot;prefect&quot;]<br># ///<br><br>from prefect import flow<br><br><br>@flow(log_prints=True)<br>def hello_world():<br>    print(&#39;hello world!&#39;)<br><br><br>if __name__ == &quot;__main__&quot;:<br>    hello_world.serve(name=&quot;example-deployment&quot;, cron=&quot;* * * * *&quot;)</pre><p><strong>💡 Try it now</strong>: Save this as scheduled_workflow.py and run uv run scheduled_workflow.py - it will start running on a cron schedule immediately!</p><h3>Airflow Parameter Limitations: Dynamic Workflow and Testing Constraints</h3><p>Airflow 3’s approach to workflow parametrization reveals a fundamental design limitation that affects both development and testing. While Airflow provides DAG Params, the implementation has significant constraints.</p><p>Airflow’s parametrization challenges include:</p><ul><li><strong>Parse-time limitations</strong>: DAG parameters cannot be used in the DAG body during parsing, as demonstrated in community discussions. Parameters are only available at runtime through templating.</li><li><strong>Limited dynamic behavior</strong>: Parameters cannot drive dynamic task generation or DAG structure changes, requiring complex workarounds.</li><li><strong>Testing difficulties</strong>: The parametrization model makes dependency injection patterns for testing nearly impossible.</li></ul><p>These limitations make Airflow parameters unsuitable for the dependency injection patterns common in modern software development. Testing scenarios that require different parameter values must rely on external configuration systems or complex templating approaches.</p><h3>Flexible Parametrization Alternative</h3><p>Prefect’s parametrization enables proper dependency injection for both runtime flexibility and comprehensive testing. Parameters can drive workflow logic, task generation, and even dynamically chosen execution paths, making workflows truly testable and maintainable. Additionally, parametrization works identically in local development and remote execution, eliminating another source of environment-specific complexity.</p><pre># /// script<br># dependencies = [&quot;prefect&quot;]<br># ///<br><br>from prefect import flow, task<br>from typing import List<br><br><br>@task<br>def process_item(item: str, multiplier: int = 1) -&gt; str:<br>    return f&quot;Processed {item} x{multiplier}&quot;<br><br><br>@flow<br>def dynamic_workflow(items: List[str], processing_multiplier: int = 2):<br>    results = []<br>    # Parameters can drive dynamic task generation<br>    for item in items:<br>        result = process_item(item, processing_multiplier)<br>        results.append(result)<br>    return results<br><br><br>if __name__ == &quot;__main__&quot;:<br>    # Test with different parameters easily<br>    test_result = dynamic_workflow([&quot;apple&quot;, &quot;banana&quot;], processing_multiplier=1)<br>    print(&quot;Test result:&quot;, test_result)<br>    # Run with production parameters<br>    prod_result = dynamic_workflow([&quot;data1&quot;, &quot;data2&quot;, &quot;data3&quot;], processing_multiplier=5)<br>    print(&quot;Production result:&quot;, prod_result)</pre><p><strong>💡 Try it now</strong>: Save as dynamic_workflow.py and run uv run dynamic_workflow.py - see how parameters drive workflow behavior instantly!</p><h3>Solutions for Airflow Development Problems</h3><p>The challenges outlined above reflect fundamental architectural decisions that affect every developer working with Airflow. While the community continues to develop workarounds and improvements, these solutions often add additional complexity rather than addressing the root causes.</p><p>For teams experiencing persistent productivity issues with local development, testing, and debugging workflows, Prefect offers a fundamentally different approach that prioritizes developer experience. Recent migration stories reveal dramatic improvements: after switching to Prefect, <a href="https://www.prefect.io/blog/modern-orchestration-endpoint-s-evolution-from-airflow-to-prefect">Endpoint increased their turnaround time by 3x</a> while also achieving a 73% reduction in orchestration costs. <a href="https://www.prefect.io/blog/airflow-to-prefect-why-modern-teams-choose-prefect">LiveEO reported similar results</a> — they <strong>tripled their development speed</strong> and cut AWS costs by 63%.</p><h3>Conclusion: Streamlining Workflow Development</h3><p>Airflow 3’s approach to local development reflects its design heritage as a production-focused batch processing system. While version 3 introduces significant architectural improvements, it maintains the assumption that developers will work within complex infrastructure environments.</p><p>The architectural differences create measurably different development experiences. Time spent managing infrastructure and debugging environment issues becomes time available for actual development work. When infrastructure complexity is removed from the development process, teams can focus on building sophisticated data applications rather than managing deployment environments.</p><p>Prefect’s architecture prioritizes developer experience as a primary design consideration. The result is faster iteration cycles, higher code quality, and development teams that can concentrate on business logic rather than operational concerns. Most importantly, the identical execution paths between local development and remote deployment eliminate a major source of environment-specific bugs and debugging complexity.</p><p><strong>Ready to experience streamlined workflow development?</strong> Try any of the code examples above with uv run filename.py and see the difference for yourself - no configuration, setup, or infrastructure required.</p><p><em>To learn more about Prefect:</em></p><ul><li><a href="https://prefect.io/"><em>visit our website</em></a></li><li><a href="https://github.com/PrefectHQ/prefect"><em>visit us on GitHub</em></a><em> to open issues and pull requests</em></li><li><em>follow us on </em><a href="https://www.linkedin.com/company/prefect/"><em>LinkedIn</em></a><em> for regular updates</em></li><li><em>join our active </em><a href="https://prefect.io/slack"><em>Slack community</em></a></li></ul><p><em>Happy Engineering!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2042ae5f6961" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/airflow-local-development-sucks-2042ae5f6961">Airflow Local Development Sucks</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Paying 24/7 for Batch Pipeline Compute]]></title>
            <link>https://medium.com/the-prefect-blog/how-to-cut-data-pipeline-costs-by-75-with-kubernetes-spot-instances-514df98fa132?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/514df98fa132</guid>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[data-platforms]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Christopher White]]></dc:creator>
            <pubDate>Mon, 07 Jul 2025 23:57:28 GMT</pubDate>
            <atom:updated>2025-07-16T23:54:58.741Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NNmX2fmZnDtkXA_zL_k5nA.png" /></figure><p>Data teams have embraced Kubernetes for many reasons. It provides dynamic resource allocation for workloads that swing from lightweight data ingestion to GPU-hungry ML training. Its native support for auto-scaling matches infrastructure to demand. Declarative configuration codifies requirements alongside pipelines and multiple teams can share infrastructure safely with workload isolation.</p><p>But there’s a fundamental economics problem lurking beneath this technical elegance: the same dynamic allocation that makes Kubernetes perfect for data workloads also makes it expensive. Your ETL pipeline needs 16 cores for 2 hours daily but pays for them 24/7. Your ML training job scales to 100 nodes for a few hours weekly, burning budget on unused capacity the other 160+ hours.</p><p>This is precisely why spot instances exist — and why they’re uniquely suited to data workloads running on Kubernetes.</p><h3>The Spot Instance Opportunity</h3><p>Spot instances represent cloud providers’ excess capacity, offered at steep discounts when demand is low. The numbers are compelling: using Amazon EC2 Spot Instances can result in up to a 90% discount compared to on-demand prices, with typical savings of 60–75% according to <a href="https://aws.amazon.com/ec2/spot/instance-advisor/">AWS’s Spot Instance Advisor.</a> Google Cloud’s <a href="https://cloud.google.com/spot-vms/pricing">Spot VMs</a> offer similar discounts of 60–91% off standard on-demand prices for most machine types.</p><p>For data teams running substantial compute workloads — ML training, ETL pipelines, analytical queries — these savings compound fast. A team spending $50,000 monthly on compute could dramatically reduce costs down to $5,000-$15,000 by migrating appropriate workloads to spot instances.</p><p>The economics work because cloud providers need spare capacity for demand spikes but don’t want it sitting idle. Instead of wasting this capacity, they offer it at reduced rates with the caveat that it can be reclaimed with minimal notice when needed elsewhere. AWS provides a two-minute interruption notice, while GCP typically provides one minute notice.</p><h3>The State Management Problem</h3><p>Spot instances create a tension for data workflows that doesn’t exist for typical application workloads. When your web server gets terminated, users might see a brief error, but the request can be retried elsewhere, as there is no piece of state that ties an individual request to a specific container instance. When a batch processing job gets terminated halfway through processing 10TB of customer transaction data, the consequences are very different.</p><p>Unlike stateless web applications that can be killed and restarted elsewhere with minimal impact, data processing jobs are inherently stateful and often tied to specific business outcomes. Consider a typical ETL pipeline that processes customer transactions:</p><ul><li>Pipeline kickoff requires precise parametrized inputs</li><li>Loading data from multiple sources, possibly using costly queries</li><li>Transforming and validating the data through several interdependent stages</li><li>Storing results to a data warehouse that downstream systems depend on</li><li>Triggering downstream reporting processes that have SLA requirements</li></ul><p>If this pipeline is interrupted midway through processing, the naive approach of “just restart everything” can lead to duplicate processing if your pipeline is not idempotent. This results in inconsistent states and the kind of data quality issues that keep data engineers awake at night.</p><p>Traditional Kubernetes approaches handle spot instance interruptions through basic restart mechanisms, but these aren’t designed for the complex state management that data workflows require. A pod that gets terminated during a critical transformation step may restart from the beginning, losing hours of computation and risks data inconsistencies that propagate through your entire data ecosystem.</p><p>This is where workflow orchestrators come into the picture. Workflow orchestration is fundamentally about managing workflow state, from scheduling and triggering rules to intermediate checkpoints that turn fragile workflows into durable multi-step processes.</p><h3>How Prefect enables Spot Instances</h3><p>Prefect’s architecture makes it uniquely suited for running data-intensive workloads on spot instances because it was designed from the ground up with failure recovery and dynamic infrastructure as core principles.</p><h3>Expressive Task Caching for Idempotent Pipelines</h3><p>When your pipeline gets interrupted halfway through processing — whether from spot instance termination or any other failure — you need to resume from the last successful task rather than starting over from scratch. Authoring truly idempotent code is difficult, but appropriate caching can give your workflows functional idempotency.</p><p>Sophistication here lies in configurability: Prefect offers many dimensions for expressing how workflow tasks should cache their results, and when:</p><ul><li><strong>Cache Policies</strong> allow you to define precisely what inputs (if any) determine whether tasks use cached results or execute fresh computation.</li><li><strong>Result Storage</strong> controls how and where task results are serialized and stored. This is critical for spot instance deployments because your cache needs to survive infrastructure failures.</li><li><strong>Transactions</strong> provide the ability to group tasks as atomic units. This prevents the scenario where some tasks in a dependent chain use cached results while others don’t, creating inconsistent data states.</li></ul><p>This level of caching sophistication is what enables workflow restarts to occur from meaningful checkpoints rather than complete do-overs. Instead of a 3-hour pipeline restarting from zero at hour 2.5, it resumes from checkooint and finishes in 30 minutes.</p><h3>Infrastructure-Aware Orchestration</h3><p>Most workflow orchestrators treat infrastructure as a black box — they submit jobs and at best retry them wholesale when they fail. Prefect’s Kubernetes worker, by contrast, understands the infrastructure it’s running on and can react intelligently to the specific failure modes that spot instances introduce.</p><p>When a pod receives SIGTERM (imminent termination), Prefect handles the signal and reschedules the workflow run with full context preservation. The worker is aware of each job’s backoff limit, and either manages rescheduling the work through Prefect’s backend — allowing any other Kubernetes worker to pick it up — or by letting Kubernetes reschedule the pod and automatically monitoring the new pod. Both patterns ensure no work is lost and the workflow continues seamlessly using task caches where necessary.</p><h3>Stateless Worker Design for Maximum Resilience</h3><p>The Prefect Kubernetes worker is stateless by design. Rather than storing run state in memory (where it can be lost during worker restarts or spot interruptions), Prefect stores all necessary run metadata in job and pod labels within the Kubernetes cluster itself. This metadata propagates to the underlying run, where the Prefect client can access task caches and the Prefect API for more granular state information.</p><p>Workers can be interrupted and restarted without losing track of running workflows. A terminated worker can restart on different infrastructure and immediately resume orchestrating flows based on the state recorded in the cluster. This resilience is crucial for spot instance deployments, where even the worker’s infrastructure can be reclaimed with minimal notice.</p><h3>Dynamic Infrastructure Configuration</h3><p>Unlike traditional orchestrators that require infrastructure to be defined statically at deployment time, Prefect allows for per-run infrastructure configuration.</p><p>This capability is useful for spot instance strategies because it enables your data engineers to:</p><ul><li>Specify different instance types for different stages of a pipeline based on current spot pricing</li><li>Automatically fall back to on-demand instances for time-sensitive workloads when spot capacity is limited</li><li>Dynamically adjust resource requirements based on data volume and available spot instance types</li></ul><p>This flexibility is useful for production spot instance deployments, where available instance types and pricing can vary significantly across availability zones and over time. Recent analysis shows that spot instance prices have increased by 21% on AWS and decreased by 26% on GCP over the past year, with significant regional variations. A static infrastructure configuration can’t adapt to this pricing volatility, but Prefect’s dynamic approach can.</p><h3>The Economics of Reliability</h3><p>Prefect’s robust failure handling and spot instance cost savings creates a compelling economic argument beyond simple cost reduction. Consider a typical data team scenario using D-type ECS instances:</p><ul><li><strong>Traditional approach</strong>: $50,000/month on on-demand instances, no rescheduling of work</li><li><strong>Prefect + Spot instances</strong>: $12,500/month, some rescheduling of work</li></ul><p>The $37,500 monthly savings enables teams to invest in additional tooling, hire more engineers, or expand their data infrastructure significantly, going well beyond simple cost minimization. The slight reliability trade-off is usually acceptable for most data workloads, especially when Prefect’s durability mechanisms ensure that jobs will complete successfully.</p><p>The traditional approach to data infrastructure often involves significant over-provisioning to handle peak workloads, leading to resource utilization rates of 30–40%. Spot instances with intelligent orchestration can achieve utilization rates of 80–90% while maintaining reliability, materially changing the economics of data processing at scale.</p><h3>The Broader Implications</h3><p>The combination of Kubernetes spot instances and intelligent orchestration can drive a significant shift in your team’s data infrastructure economics. When compute costs drop by 80–90%, new possibilities emerge — experimental workloads, deeper analytics, and more frequent model training all become possible within existing budget constraints.</p><p>The technical sophistication required to make spot instances work reliably for data workloads is considerable, but the right orchestration platform can abstract this complexity while providing the control and visibility that data engineers need.</p><p>While over 20% of Prefect Cloud users currently run workflows in Kubernetes, this percentage has been slowly declining as teams migrate toward even more dynamic infrastructure patterns. Fully serverless compute platforms like AWS Fargate and Google Cloud Run are becoming more and more common, along with emerging players like <a href="https://modal.com/">Modal</a> and <a href="https://coiled.io/">Coiled</a>. Even as the ecosystem evolves toward serverless architectures, the cost optimization and durability strategies explored here remain relevant across different compute paradigms. For teams running substantial workloads, investigating this approach allows teams to get the most value out of their compute.</p><h3>Further Reading</h3><p>For more information check out <a href="https://docs.prefect.io/v3/how-to-guides/deployment_infra/kubernetes#how-to-run-flows-on-kubernetes">Prefect’s Kubernetes documentation</a>, <a href="https://github.com/PrefectHQ/prefect">visit us on GitHub</a>, or <a href="https://app.prefect.cloud/auth/sign-up">start deploying Prefect flows for free</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=514df98fa132" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/how-to-cut-data-pipeline-costs-by-75-with-kubernetes-spot-instances-514df98fa132">Stop Paying 24/7 for Batch Pipeline Compute</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How AnyIO Powers Prefect’s Async Architecture]]></title>
            <link>https://medium.com/the-prefect-blog/how-anyio-powers-prefects-async-architecture-9d0da9b14bbb?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/9d0da9b14bbb</guid>
            <category><![CDATA[orchestration]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[asynchronous-programming]]></category>
            <category><![CDATA[data-engineering]]></category>
            <dc:creator><![CDATA[A. Adam Azzam]]></dc:creator>
            <pubDate>Fri, 16 May 2025 16:29:30 GMT</pubDate>
            <atom:updated>2025-05-16T16:29:30.786Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sFTCzHueV-3iRaOmSB10Nw.png" /></figure><p><em>Prefect is built on a lot of great ideas, but even more importantly, it’s built on great tools. This post is part of a series of love letters to the libraries and technologies that make Prefect possible.</em></p><p><em>We’re kicking things off with AnyIO — the async concurrency library that brought sanity, structure, and elegance to our workflow engine. If you’ve ever tried to wrangle asyncio and lived to tell the tale, you’ll understand why this one deserves a bit more than just a passing mention. This is our thank-you, our technical deep dive, and maybe just a little bit of a love letter.</em></p><p>In large-scale data processing and workflow orchestration, concurrency is crucial for keeping systems responsive and utilizing resources efficiently. Yet managing concurrency with raw asyncio can quickly turn into a mind-bending maze of callbacks, race conditions, and partial cancellations. That’s where AnyIO comes in, bridging Python’s main async frameworks under one carefully designed API — and freeing developers (us included!) to focus on building reliable, high-scale applications.</p><p>Below, we’ll explore why Prefect relies so heavily on AnyIO, show how we use it to solve real-world concurrency challenges, and share the lessons we’ve learned that might save you from your own async pitfalls. Let’s dive in.</p><h3>The Challenge of Asynchronous Programming in Python</h3><p>Asynchronous programming has become indispensable for building responsive, scalable systems. By allowing tasks to run concurrently — whether you’re dealing with heavy network I/O, CPU-bound computations, or file reads — you can make sure your program isn’t just sitting idle waiting for one operation to finish. In other words, async code helps you squeeze the most out of your CPU cycles, memory, and network connections so your application can handle more work without grinding to a halt.</p><p>Python’s async journey started with asyncio in Python 3.4, standardizing cooperative multitasking but also introducing a fair bit of complexity. That complexity paved the way for Trio, designed with a focus on simplicity, better debugging, and correctness. However, it also meant developers had to pick sides between asyncio and Trio, leaving the ecosystem fragmented.</p><p>In short, as the ecosystem evolved, fragmentation and complexity became the norm. So how did AnyIO unify the landscape? Let’s look under the hood.</p><h3>What is AnyIO? A Technical Overview</h3><p>AnyIO is an asynchronous concurrency library that lets you seamlessly write code compatible with Python’s two main async frameworks: asyncio and trio. But it’s more than just a compatibility layer — it’s designed to blend the best ideas from both frameworks into a clear, unified API that smooths out their quirks and differences.</p><p>Here’s why this matters in practice:</p><pre>import asyncio<br><br>async def asyncio_example():<br>    await asyncio.sleep(1)<br>    return &quot;completed with asyncio&quot;<br><br># trio version<br>import trio<br><br>async def trio_example():<br>    await trio.sleep(1)<br>    return &quot;completed with trio&quot;</pre><p>But with AnyIO, you only write your async code once and run it anywhere:</p><pre>import anyio<br><br>async def anyio_example():<br>    await anyio.sleep(1)<br>    return &quot;completed with AnyIO&quot;<br><br># Run your code with the asyncio backend<br>anyio.run(anyio_example, backend=&quot;asyncio&quot;)<br><br># Or swap to trio just as easily<br>anyio.run(anyio_example, backend=&quot;trio&quot;)</pre><p>Beyond just simplifying your choices, AnyIO provides clean and consistent interfaces for common async needs:</p><ul><li><strong>Task Groups</strong>: Manage groups of tasks easily, with automatic error handling and cleanup.</li><li><strong>Cancellation Scopes</strong>: Cancel tasks gracefully and reliably, ensuring resources are cleaned up properly.</li><li><strong>Networking and Streams</strong>: Use the same APIs for TCP/UDP sockets, pipes, and more across both frameworks.</li><li><strong>Synchronization Tools</strong>: Get locks, events, semaphores, and conditions — everything you need to coordinate tasks without headaches.</li><li><strong>File Operations</strong>: Perform file reads and writes asynchronously without blocking your event loop.</li><li><strong>Subprocesses</strong>: Run external commands efficiently within your async workflows.</li></ul><p>The beauty of AnyIO is how it guides you toward structured concurrency — helping you write async code that’s predictable, easy to reason about, and resistant to subtle bugs like resource leaks or deadlocks.</p><h3>AnyIO’s Position in the Python Async Ecosystem</h3><p>AnyIO sits at a unique intersection in Python’s async ecosystem. Unlike most async libraries, which typically commit exclusively to either asyncio or trio, AnyIO bridges the gap by offering a universal abstraction that works seamlessly with both frameworks. This flexibility becomes even more crucial as the ecosystem grows and developers seek solutions that aren’t locked into one specific tool.</p><p>AnyIO shines especially bright for middleware and framework developers, enabling them to build software that remains agnostic to their users’ async framework preferences. This versatility is why many prominent projects have embraced AnyIO:</p><ul><li><strong>Starlette</strong>: To power backend-agnostic web services.</li><li><strong>Prefect</strong>: For robust, async-powered workflow orchestration.</li><li><strong>OpenAI, Anthropic, and Google Gemini Python SDKs</strong>: For reliable async file uploads in interactions with large language models.</li><li><strong>DsPY</strong>: To efficiently handle concurrent calls to LLMs while managing resources effectively.</li></ul><p>By providing a unified and easy-to-use abstraction, AnyIO helps maintain cohesion and compatibility across the diverse landscape of Python’s asynchronous ecosystem.</p><p>But who is behind AnyIO’s success? Naturally, you might wonder who’s guiding and shaping its evolution.</p><h3>The Minds Behind AnyIO: Maintainers and Community</h3><p>AnyIO is primarily maintained by Alex Grönholm, an experienced software developer deeply involved in Python’s async community. Alex also created asphalt, an application framework, and actively contributed to the ASGI specification, which significantly shaped Python web frameworks.</p><p>The project thrives on contributions from developers throughout the Python community, including core maintainers from both asyncio and trio. This collaborative approach ensures that AnyIO embodies a consensus on asynchronous programming best practices rather than reflecting just one developer’s vision.</p><h3>Why Prefect Chose AnyIO: Technical Decision Points</h3><p>At Prefect, concurrency isn’t just a feature — it’s at the heart of our workflow engine. Here’s what we needed:</p><ol><li><strong>Parallel Task Execution</strong>: Running many tasks simultaneously while respecting their dependencies.</li><li><strong>Graceful Cancellation</strong>: Easily and reliably stopping tasks when users halt their workflows.</li><li><strong>Reliable Timeouts</strong>: Preventing runaway tasks from consuming resources indefinitely.</li><li><strong>Clear, Debuggable Execution Paths</strong>: Ensuring transparency and ease of debugging in complex concurrent scenarios.</li></ol><p>We chose AnyIO for a few key reasons:</p><ul><li><strong>Structured Concurrency</strong>: AnyIO’s structured concurrency model clearly defines task lifetimes and relationships. This aligns perfectly with our workflows, ensuring reliable cleanup even when things go wrong.</li><li><strong>Backend Flexibility</strong>: Currently, we rely on asyncio, but the option to seamlessly switch to trio without rewriting code gives us strategic flexibility. It also allows us to leverage libraries from either ecosystem.</li><li><strong>Robust Cancellation and Timeout Handling</strong>: AnyIO’s powerful primitives for handling cancellations and timeouts surpass raw asyncio capabilities, essential for a workflow system where task cancellation needs to be dependable.</li><li><strong>Clean and Thoughtful API Design</strong>: AnyIO’s APIs are carefully designed to avoid common async pitfalls. Its task groups, for example, handle exceptions cleanly and prevent resource leaks — critical features for running user-defined workflows safely and reliably.</li></ul><h3>AnyIO in Action: How Prefect Leverages the Library</h3><p>Below, we’ll show two major ways AnyIO underpins Prefect’s concurrency story. First, let’s look at how Prefect’s worker architecture uses structured concurrency for reliable task execution. Then we’ll see how capacity limiters solve a common real-world problem — SQLite concurrency issues.</p><h3>Flow Run Orchestration Using TaskGroups</h3><p>In Prefect, workers form the backbone of our orchestration lifecycle, continuously polling for scheduled workflows and delegating their execution. At the core of this lifecycle is <strong>structured concurrency</strong>, a powerful pattern implemented via AnyIO’s TaskGroups.</p><p>Structured concurrency explicitly models task dependencies, creating a clear hierarchical structure of upstream and downstream relationships. This ensures that cancellations and errors propagate swiftly and coherently through the execution graph, gracefully terminating affected tasks while maintaining system integrity.</p><p>Early deployments revealed several concurrency challenges, including deadlocks and cancellation race conditions. TaskGroups addressed these issues elegantly by offering predictable cleanup, clear error propagation, resource management, and simpler concurrency overall.</p><p>Here’s how we integrate TaskGroups into our BaseWorker class:</p><pre>async def setup(self) -&gt; None:<br>    &quot;&quot;&quot;Prepares the worker to run.&quot;&quot;&quot;<br>    self._logger.debug(&quot;Setting up worker...&quot;)<br>    self._runs_task_group = anyio.create_task_group()<br><br>    # ... other setup code ...<br><br>    await self._exit_stack.enter_async_context(self._runs_task_group)<br><br>    # ... additional setup code ...<br><br>    self.is_setup = True</pre><p>By initializing a task group within an exit stack during setup, we guarantee all tasks within the group are properly awaited or canceled upon teardown, eliminating orphaned processes.</p><p>When submitting flow runs concurrently, we use TaskGroup’s start_soon method:</p><pre># From the _submit_scheduled_flow_runs method<br>self._runs_task_group.start_soon(self._submit_run, flow_run)</pre><p>For tasks returning values, we prefer the .start() method:</p><pre># From the _submit_run method<br>readiness_result = await self._runs_task_group.start(self._submit_run_and_capture_errors, flow_run)</pre><p>Handling cancellations cleanly was among our most challenging problems. With structured concurrency, cancellations naturally propagate through task hierarchies. We use AnyIO’s cancellation scopes to define precise boundaries:</p><pre>async def _schedule_task(<br>    self, __in_seconds: int, fn: Callable[..., Any], *args: Any, **kwargs: Any<br>):<br>    &quot;&quot;&quot;Schedule a background task to start after a delay.&quot;&quot;&quot;<br>    async def wrapper(task_status: anyio.abc.TaskStatus[Any]):<br>        if self.is_setup:<br>            with anyio.CancelScope() as scope:<br>                self._scheduled_task_scopes.add(scope)<br>                task_status.started()<br>                await anyio.sleep(__in_seconds)<br>                self._scheduled_task_scopes.remove(scope)<br>        else:<br>            task_status.started()<br><br>        result = fn(*args, **kwargs)<br>        if asyncio.iscoroutine(result):<br>            await result<br><br>    await self._runs_task_group.start(wrapper)</pre><p>During teardown, cancellation is straightforward:</p><pre>async def teardown(self, *exc_info: Any) -&gt; None:<br>    &quot;&quot;&quot;Cleans up resources after the worker is stopped.&quot;&quot;&quot;<br>    self._logger.debug(&quot;Tearing down worker...&quot;)<br>    self.is_setup = False<br>    for scope in self._scheduled_task_scopes:<br>        scope.cancel()<br><br>    # ... other cleanup code ...</pre><p>In a production setting, dozens or hundreds of tasks can spawn simultaneously. AnyIO’s structured concurrency ensures tasks are never orphaned if the parent is canceled. The predictable cleanup model spares developers from headaches like race conditions and stuck processes.</p><h3>Database Protection via Capacity Limiters</h3><p>Beyond orchestrating flow runs, concurrency also impacts our database usage. Prefect uses a database to track workflow states, initially prioritizing SQLite for simplicity. However, SQLite struggles with concurrent writes, quickly leading to “database is locked” errors under high concurrency.</p><p>In real-world scenarios, these resource bottlenecks can cripple applications. To protect SQLite from overload, we introduced a simple yet effective middleware using AnyIO’s CapacityLimiter:</p><pre>class RequestLimitMiddleware:<br>    &quot;&quot;&quot;<br>    Middleware limiting concurrent API requests to protect SQLite.<br>    &quot;&quot;&quot;<br><br>    def __init__(self, app: Any, limit: float):<br>        self.app = app<br>        self._limiter = anyio.CapacityLimiter(limit)<br><br>    async def __call__(self, scope: Any, receive: Any, send: Any) -&gt; None:<br>        async with self._limiter:<br>            await self.app(scope, receive, send)</pre><p>This middleware ensures:</p><ul><li>A controlled maximum number of concurrent operations.</li><li>Fair request handling through FIFO queuing.</li><li>Requests waiting in a queue if the concurrency limit is reached, resuming as soon as capacity becomes available.</li></ul><p>The result? SQLite remains responsive, and lock errors are drastically reduced. This simple pattern can work for any shared resource — like an external API — where too many concurrent requests can lead to throttling, rate limits, or outright failures.</p><h3>Performance and Reliability Benefits for Prefect Users</h3><p>From a performance perspective, AnyIO adds minimal overhead compared to direct asyncio usage, typically less than 1ms per operation. The library is also memory-efficient, avoiding unnecessary allocations and object churn that can impact systems running thousands of concurrent workflows.</p><h3>Looking Forward: AnyIO’s Future and Its Impact on Prefect</h3><p>The future of AnyIO looks promising. The library continues to mature, with recent improvements focused on:</p><ul><li>Better memory efficiency for large-scale concurrent operations.</li><li>Enhanced networking primitives for modern protocols.</li><li>More comprehensive support for structured concurrency patterns.</li></ul><p>For Prefect, these improvements directly translate to a more capable and reliable workflow engine. As we scale to support more complex orchestration scenarios, AnyIO’s abstractions provide the foundation we need. Looking ahead, we see several exciting possibilities:</p><ol><li><strong>Enhanced Cancellation Semantics</strong>: Future versions of AnyIO may provide even more nuanced cancellation controls, allowing for partial workflow cancellation with sophisticated cleanup logic.</li><li><strong>Backend Optimizations</strong>: As both asyncio and Trio evolve, AnyIO will provide access to performance improvements from either ecosystem without requiring code changes.</li><li><strong>Ecosystem Growth</strong>: As more libraries adopt AnyIO, Prefect will be able to integrate with a wider range of async tools regardless of their underlying implementation.</li></ol><p>AnyIO may not be the most well-known Python library, but it’s a critical piece of infrastructure that enables Prefect to deliver a reliable, performant workflow orchestration engine. By providing unified abstractions over Python’s async landscape, it allows us to focus on building the best possible orchestration experience rather than wrestling with low-level concurrency management.</p><p>For data engineers already using Prefect, understanding AnyIO helps explain how we achieve reliable concurrent execution. For those building their own async systems, AnyIO represents a battle-tested approach to managing concurrency that’s worth consideration.</p><p>We’re grateful to Alex Grönholm and the AnyIO community for their dedication to improving Python’s async story. If you’re curious to try AnyIO for yourself, check out the <a href="https://github.com/agronholm/anyio">official AnyIO documentation</a> and Prefect’s own docs on concurrency and orchestration. You’ll be well on your way to writing clear, resilient async code.</p><h4>Next Steps</h4><ul><li><a href="https://www.prefect.io/pricing?utm_source=medium">Sign up for Prefect Cloud</a></li><li><a href="https://prefect.io/slack">Join our Slack Community</a></li><li><a href="https://github.com/PrefectHQ/prefect">Give us a star us on GitHub</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9d0da9b14bbb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/how-anyio-powers-prefects-async-architecture-9d0da9b14bbb">How AnyIO Powers Prefect’s Async Architecture</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Platform Approach to Workflow Orchestration]]></title>
            <link>https://medium.com/the-prefect-blog/a-platform-approach-to-workflow-orchestration-6057022ddbcc?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/6057022ddbcc</guid>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[prefect]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Jeff Hale]]></dc:creator>
            <pubDate>Wed, 06 Dec 2023 16:57:19 GMT</pubDate>
            <atom:updated>2023-12-06T19:43:15.042Z</atom:updated>
            <content:encoded><![CDATA[<h4>Customized workflow orchestration for self-service data teams</h4><p>At Prefect, we’ve seen how a platform approach to data engineering can massively increase developer velocity. This approach abstracts away the complexity of infrastructure while giving engineers the power to build resilient, complex workflows. For developers this means reduced mental overhead, less development time, and fewer headaches to get their code up and running quickly.</p><p>For platform teams, it means protecting your infrastructure with a minimally invasive architecture while meeting the needs of the teams you support.</p><h3>Prefect is perfect for platform people</h3><p><a href="https://www.prefect.io/">Prefect</a> is a workflow orchestration tool empowering developers to build, observe, and react to whatever workflows they are building. It just so happens that Prefect is also a highly composable application framework that can be customized by platform teams to better serve developers and stakeholders.</p><p>With Prefect, platform teams can:</p><ul><li>Customize a platform that developers can use to build and observe their workflows</li><li>Set appropriate levels of access to infrastructure and other resources</li><li>Create and scale opinionated interfaces to infrastructure, without significant overhead</li><li>Provide infrastructure abstractions appropriate for the skill level of the team they support</li></ul><figure><img alt="Gif of our powers combined" src="https://cdn-images-1.medium.com/max/400/0*MnXN2jjTRDdrUQ2G" /></figure><h3>Developers, the workflow builders</h3><p>“Everything fails, all the time” -Amazon CTO, Werner Vogels</p><p>Code breaks, it is a fact of life. APIs change, infrastructure fails unexpectedly, and any other of an infinite number of problems can occur. Every developer wants to make their workflows resilient to failure.</p><p><strong>Developers…</strong></p><ul><li>want to schedule and observe their code</li><li>don’t want to rewrite</li><li>need help with infrastructure abstractions such as containers, Kubernetes, AWS ECS, etc.</li></ul><p>With no standardization or platform team helping them, developers are left figuring out how to deploy on their own. While this is possible for some, it isn’t for many.</p><h3>Platform teams, the infrastructure owners</h3><p>Enter platform teams, engineers responsible for the infrastructure of an organization.</p><p>Compared to a data engineer who is often building one-off data pipelines or trying to piece things together themselves, platform teams care about ensuring that the business has the tools needed to achieve its goals at scale. To this end, platform teams build and expose foundational frameworks to help other teams build faster (particularly with respect to infrastructure) while maintaining compliance and access control standards.</p><p><strong>Platform teams…</strong></p><ul><li>are responsible for the sanctity of a company’s infrastructure</li><li>help developer teams put their work into production using this infrastructure</li></ul><p>By providing a standardized surface area for how developers interact with data, infrastructure, and other systems, platform teams can help workflow builders create faster and scale safely.</p><p>At Prefect, we see this platform approach to data engineering frequently, with organizations ranging from Fortune 100 companies to smaller tech firms who want to move as quickly as possible.</p><h3>Creating a platform with Prefect</h3><p>Platform teams can use the modular architecture of Prefect to create a custom platform that helps data engineers, data scientists, or anyone creating workflows to build faster while meeting infrastructure and access requirements.</p><h3>Give developers code interfaces and a UI</h3><p>Prefect turns every object into discrete API automatically, accessible by your users. Create deployment templates for your team to submit work via an API, or give them the ability to build their own highly custom workflows.</p><p>The Prefect UI gives teams a UI to track and observe their work, or even to submit templated workflows. Even non-technical teams can submit workflows with parameters, and track their progress.</p><h3>Customize your infrastructure concepts</h3><p>Prefect <a href="https://docs.prefect.io/latest/concepts">workers and work pools</a> allow platform teams to customize their infrastructure definitions in Prefect:</p><ul><li>Create opinionated infrastructure patterns</li><li>Tightly control who uses what infrastructure with RBAC + deployments</li><li>Dynamically assign infrastructure, switching between cloud providers and services easily</li></ul><h3>Keep it minimally invasive</h3><p>Prefect’s <a href="https://www.prefect.io/security#hybrid-model">hybrid model</a> means that execution and orchestration are separate concepts, meaning you can keep your data and code on your own premises. With Prefect Cloud, isolate and scale the infrastructure behind your workflow, without needing to worry about the orchestration server.</p><h3>Platform user patterns</h3><p>Let’s see some examples of how Prefect provides clear interfaces to create guardrails for different developer personas.</p><h3>Deployment creators</h3><p>Work pools can be used to lock down deployment infrastructure options. For example, you can remove a field you don’t want deployment creators to adjust or hard code a value for it.</p><figure><img alt="Base job template work pool showing JSON for customizability" src="https://cdn-images-1.medium.com/max/1024/0*egdjxDi942Za-VxG" /></figure><p>Prefect Block types are Python classes that store configuration and code. Blocks have a structured HTML form for input and can be used in workflow code. Alternatively, they can be defined in Python code. Blocks are great for sharing configuration, as they live on the server.</p><p>Blocks are extremely flexible. Custom block types can be created to do whatever your team needs. Instead of needing to fork Prefect, you can just create a block with the capabilities you need and have your team use it. We see many platform teams using blocks to create</p><p>With Prefect’s team management, granular infrastructure control options, and RBAC options including custom roles, platform teams can make sure right people have access to the right resources.</p><h3>Deployment runners</h3><p>Through role-based access controls (RBAC), Prefect Cloud teams can limit users to run workflows but not create them.</p><p>Additionally, workflow authors can specify the data types of parameters required for the entry point function. This way at runtime, the deployment runner must provide a parameter value of an acceptable type to run the workflow.</p><figure><img alt="Form field screenshot with parameters for running a deployment" src="https://cdn-images-1.medium.com/max/1024/0*L3rIa7TII3TmH3HO" /></figure><p>Under the hood, Prefect uses Pydantic, so parameters can be limited to a list of choices through <a href="https://docs.pydantic.dev/latest/api/standard_library_types/#enum">enums</a>. Authors can even create a custom Pydantic class to require that a value in a valid email address format is provided or ensure a maximum or minimum numeric value.</p><h3>Deployment viewers</h3><p>Prefect Cloud administrators can further limit user permissions to viewing the results of runs, but not running them. The principle of least privilege states that each entity should be granted the minimum resources and authorizations needed to perform its function. Sometimes the ability to check in is all you need.</p><p>Prefect Cloud also allows for automations that fire PagerDuty notifications, emails, Slack messages, or other notifications in response to event triggers you specify. So if a flow run fails or data doesn’t look how you expect, the right people can be automatically alerted.</p><p>Additionally, if a Prefect team is using error summaries powered by MarvinAI, they can quickly see the source of workflow failures at a glance in the UI.</p><figure><img alt="Marvin AI error summary example that explains a bit about why a flow run failed" src="https://cdn-images-1.medium.com/max/1024/0*uiwua3m-JsCyXg25" /></figure><p>As you can see, Prefect provides data platform teams the ability to create self-service guardrails for personas of any type.</p><h3>Build a platform for your developers</h3><p>We see more and more companies adopting a platform approach to data engineering. This approach empowers developers to build faster while providing infrastructure and access safeguards.</p><p>A platform team can tailor Prefect however they choose: from adding permissions to infrastructure environments, to heavily opinionated deployment patterns. Developer teams will move faster, and your infrastructure will run smoother than ever.</p><p>For examples of teams using Prefect as a platform for a variety of engineering teams, check out the following case studies:</p><ul><li><a href="https://www.prefect.io/blog/rtr-struts-the-data-runway-with-prefect">Rent the Runway</a></li><li><a href="https://www.prefect.io/blog/modern-health-hipaa">Modern Health</a></li><li><a href="https://www.prefect.io/blog/rec-room-case-study">Rec Room</a></li></ul><p><em>Prefect makes complex workflows simpler, not harder. Try </em><a href="https://app.prefect.cloud/?deviceId=b08b25b7-6948-4683-9796-cd5807434933"><em>Prefect Cloud</em></a><em> for free for yourself, download our </em><a href="https://github.com/PrefectHQ/prefect"><em>open source package</em></a><em>, join our </em><a href="https://www.prefect.io/slack"><em>Slack community</em></a><em>, or </em><a href="https://calendly.com/prefect-experts/prefect-product-advocates"><em>talk to one of our engineers</em></a><em> to learn more.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6057022ddbcc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/a-platform-approach-to-workflow-orchestration-6057022ddbcc">A Platform Approach to Workflow Orchestration</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Beyond Scheduling: Event-Driven Flows with Prefect]]></title>
            <link>https://medium.com/the-prefect-blog/beyond-scheduling-event-driven-flows-with-prefect-b072edd06833?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/b072edd06833</guid>
            <category><![CDATA[prefect]]></category>
            <category><![CDATA[event-driven-architecture]]></category>
            <category><![CDATA[data]]></category>
            <category><![CDATA[kafka]]></category>
            <dc:creator><![CDATA[Will Raphaelson]]></dc:creator>
            <pubDate>Mon, 10 Jul 2023 21:18:11 GMT</pubDate>
            <atom:updated>2023-08-30T18:46:12.304Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/612/0*bvYxwCTAkP58TElF" /></figure><h3>Forget about Time</h3><p>Time is, generally speaking, weird. It’s the least-worst way to describe when something has occurred, is occurring, or will occur, but it’s kind of a messy abstraction. In the realm of data pipelines, it’s an abstraction, or a guess, of when <strong>something else</strong> has happened that demands a pipeline be executed.</p><p>Running a refresh pipeline every hour represents a guess that in the span of an hour, new data thats worth moving or displaying elsewhere was produced. Scheduling a pipeline to run weekly is similarly a guess that the supply of or demand for data changes about once a week.</p><p>But modern data practitioners need to take action on data as it is produced — emailing users as soon as they sign up, updating fulfillment statuses as soon as an order is placed, and displaying sensor data as soon as it makes a reading.</p><p>Event-driven architectures allow us to stop guessing — rather than guessing that some customer data came in in the past hour, we should just run the pipeline when new customer data comes in, immediately, with no extra steps and <strong>no educated guesses</strong>.</p><p>Time is, of course, useful — pipeline runs almost always cost money, and you might not want to spend money every time an event occurs — but it shouldn’t be the only way to say when something should run. Prefect makes event-driven data pipelines simple.</p><h3>Event-Driven Flows with Kafka, Event Webhooks and Deployment Triggers</h3><p>Imagine you have a web app, and luckily, people are signing up! You know that your onboarding email series is most effective if you send it immediately after signup.</p><p>In this post, I’ll wire up a system that subscribes to a Kafka topic with my new user signups, pipes those events into Prefect Cloud, and triggers the new user onboarding pipeline on receipt of those events. It assumes basic familiarity with Prefect Cloud. If you’re not quite there yet, check out <a href="http://docs.prefect.io/">our docs</a> or hit me up in the <a href="https://www.prefect.io/slack/">Slack Community</a>.</p><h3>New User Signups → Prefect Events</h3><p>When a new user signs up for my app, my app produces a message to a Kafka topic, which is an event store commonly used in distributed applications. I have a Kafka topic hosted on <a href="https://www.confluent.io/lp/confluent-cloud">Confluent Cloud</a> that contains these messages.</p><p>I know that I want to use a Prefect Flow to reach out to those new users as soon as possible after they sign up. To do this, I’m going to need to get these signup messages into Prefect, where I can observe and react to them.</p><p>To get messages from my topic into Prefect Cloud, we’re going to take advantage of Prefect Cloud event webhooks. Webhooks are an ultra-lightweight way to expose an endpoint that accepts HTTP traffic, and are used for simple integrations across the modern web. When an HTTP call is made to a Prefect webhook, it emits a Prefect event and captures data from the HTTP call for use downstream.</p><p>Using Confluent’s Httpsink connector, I can automatically hit a Prefect webhook every time a message appears in the topic.</p><p>So if I get a new user signup message in the form of:</p><pre>&quot;[<br>  {<br>    \\&quot;registertime\\&quot;:1494287144756,<br>    \\&quot;gender\\&quot;:\\&quot;OTHER\\&quot;,<br>    \\&quot;regionid\\&quot;:\\&quot;Region_4\\&quot;,<br>    \\&quot;userid\\&quot;:\\&quot;User_5\\&quot;<br>  }<br>]&quot;</pre><p>Kafka will fire out to Prefect and save the message as a Prefect event in real time. We use a Jinja template in our webhook configuration to do some light transformation — instructing prefect to construct an event name from the user id of the message:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/828/1*-KdbsS3B2kl4keaNQv_4Dw.png" /><figcaption>A Prefect Cloud Event Webhook configuration modal</figcaption></figure><p>Upon save, we immediately see user events start streaming into Prefect Cloud</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9oq1V0Uf5RBZk4sDOQ6V6A.png" /><figcaption>A Prefect Cloud Event Stream showing new user signup events</figcaption></figure><h3>New User Events → Deployment Triggers</h3><p>Now that I have my new user signups streaming into Prefect, it’s time to <strong>do something</strong> with them. As it happens (😉) I have the following Python code, which sends a welcome email, checks to ensure that the email didn’t bounce, and updates an internal marketing database. I’ve turned this code into a flow and my functions into tasks with Prefect decorators and deployed it to Prefect Cloud.</p><pre>from prefect import flow, task<br><br>@task(log_prints=True)<br>def send_welcome_email(name: str=&quot;will&quot;) -&gt; None:<br>    print(f&quot;Sending onboarding campaign to {name}!&quot;)<br>    ...<br>    pass<br><br>@task(log_prints=True)<br>def check_for_bounce(email_id: int=12345) -&gt; None:<br>    print(f&quot;checking for bounce on email {email_id}&quot;)<br>    ...<br>    pass<br><br>@task(log_prints=True)<br>def update_marketing_tracker(email_id: int=12345):<br>    print(f&quot;updating marketing tracker for email {email_id}&quot;)<br>    ...<br>    pass<br><br>@flow<br>def new_user_flow(name: str) -&gt; None:<br>    send_welcome_email(name)<br>    check_for_bounce()<br>    update_marketing_tracker()<br><br>if __name__ == &quot;__main__&quot;:<br>    new_user_flow(name=&quot;will&quot;)</pre><p>I need this flow to run when a new user event hits my system, using the user id from the event payload as the input parameter to the flow. there are a number of ways to do this both in code and the UI, but the simplest way is to navigate to an event like the ones you want to use to trigger your flow run, and click automate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XBft9DNj0zxEYg6Lyw3Dig.png" /></figure><p>This bootstraps a trigger definition that listens for customer events. Then we can proceed to the next step of the wizard to tell it what to go when the trigger fires.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WDzl_LI3A8xZZPqr2PFBXg.png" /><figcaption>A Prefect Cloud trigger configuration screen</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2tb41ca5mOzjOKXkzU2Azg.png" /><figcaption>A Prefect Cloud action configuration screen</figcaption></figure><p>You’ll notice some interesting syntax in the user_id field. Automations capture information from the event that triggered them, and you can use Jinja syntax to pass that information into your flow run at runtime. {{ event.payload.userid }} says to take the userid from the event we captured, and use that as the main parameter value for the flow. On creation, this automation is now linked to the deployment and visible on the deployment details page under “Triggers”.</p><h3>Putting it all Together</h3><p>On the creation of this link, flows start kicking off in response to the Kafka new user messages:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0XZBKTeOzlVgbCTZd6ZPGw.png" /><figcaption>New user flow runs kicked off in response to events</figcaption></figure><p>And our flows and events tell the story of a new user signing up and getting their welcome email just seconds after their signup:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WQe-cP9JB12HevDbeol1Rw.png" /><figcaption>A new user flow run in action</figcaption></figure><p>This is just the tip of the iceberg on event-driven flows with Prefect. Triggers can fire on a <strong><em>lack</em></strong> of observed events, webhooks can use jinja to flexibly transform payloads into Prefect events from CloudEvents or custom calls, and you can specify trigger policies directly from deployments without having to create automations manually. Give event-driven flows a try and let me know what you think in the <a href="https://www.prefect.io/slack/">Slack Community</a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b072edd06833" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/beyond-scheduling-event-driven-flows-with-prefect-b072edd06833">Beyond Scheduling: Event-Driven Flows with Prefect</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Keeping  on AI tools with Marvin]]></title>
            <link>https://medium.com/the-prefect-blog/keeping-on-ai-tools-with-marvin-1dc287b8d177?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/1dc287b8d177</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[pydantic]]></category>
            <category><![CDATA[prefect]]></category>
            <category><![CDATA[llm]]></category>
            <dc:creator><![CDATA[Nate Nowack]]></dc:creator>
            <pubDate>Fri, 16 Jun 2023 12:48:32 GMT</pubDate>
            <atom:updated>2023-06-16T12:48:32.447Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t6_fPRr2R21eSr-w9I6v-Q.png" /><figcaption>a tapestry of random, potentially related events</figcaption></figure><p>Since March 14, when I <a href="https://medium.com/the-prefect-blog/keeping-your-eyes-on-your-ai-tools-6428664537da">posted about instrumenting LLM tools with Prefect</a>, a whole lot has happened in the AI tooling landscape.</p><p>Most significant to my world, <a href="https://github.com/PrefectHQ/marvin">Marvin</a> has <a href="https://star-history.com/#prefecthq/marvin&amp;Date">taken on a life of its own</a>. Leveraging the diverse LLM support from the open-source community, the data engineering amenities offered by Prefect, and the type-safety of Pydantic — Marvin has grown up to be a pythonic LLM toolbox that you can drop right into traditional software contexts to solve problems. If you want to learn more about how Marvin works, <a href="https://www.askmarvin.ai/">read the docs!</a></p><h4>expanding the original use case</h4><p>Making a Slack-based assistant for our <a href="https://www.prefect.io/community/">Prefect Community</a> was the start of all things Marvin, and it has been the use-case where we prove out our concepts to make sure they work in practice.</p><p>Today we’ll look at a concrete example of observability in an AI system — namely how we can track Marvin’s global token usage with the Events and Automations systems offered by Prefect Cloud (<a href="https://docs.prefect.io/2.10.13/cloud/cloud-quickstart/#sign-in-or-register">generous free tier</a> btw).</p><h4>background</h4><p>You can chat with our Prefect assistant bot (affectionately, also Marvin) in the <strong>#ask-marvin </strong>channel in our Slack community. There’s a parallel Marvin bot we run internally to assist with tasks like support, debugging and summarizing internal process documents .</p><p>Each time you or I tag any of our Marvins, Slack will hit that Marvin app’s FastAPI endpoints with the Slack event data and prompt a Prefect-aware <a href="https://www.askmarvin.ai/guide/concepts/bots/">Bot</a> with the user question:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/956/1*CSQowEdmcDdTiKyLtbc4jw.png" /><figcaption>Marvin answering questions in the wild</figcaption></figure><p>Each time, the bot will leverage an LLM (like GPT 4) and may use an arbitrary number of tools in order to answer the user question — you can learn more about the state of Marvin’s OpenAI tool use <a href="https://www.factsmachine.ai/p/the-prodigal-json">here</a>.</p><p>Between LLM calls and plugin execution, this can be time and token intensive stuff!</p><p>Prefect Cloud offers the idea of an Event , which is just a named and timestamped payload that we can emit from anywhere and then react to.</p><h4>emitting events</h4><p>Once Marvin settles on a response and sends it to you, we’ll run a little prefect routine from inside the endpoint called emit_event:</p><pre>from prefect.events import emit_event<br><br>...<br><br>async def _slackbot_response(event: SlackEvent):<br><br>    # other things happening ...<br><br>    response = await bot.say(text)<br><br>    slack_response = await _post_message(<br>        channel=event.channel, message=response.content, thread_ts=event.ts<br>    )<br><br>    prompt_tokens = count_tokens(text)<br>    response_tokens = count_tokens(response.content)<br><br>    # this will do nothing if Prefect credentials are not configured<br>    emit_event(<br>        event=f&quot;bot.{bot.name}.responded&quot;,<br>        resource={&quot;prefect.resource.id&quot;: f&quot;bot.{bot.name}&quot;},<br>        payload={<br>            &quot;user&quot;: event.user,<br>            &quot;channel&quot;: event.channel,<br>            &quot;thread_ts&quot;: thread,<br>            &quot;text&quot;: text,<br>            &quot;response&quot;: response.content,<br>            &quot;prompt_tokens&quot;: prompt_tokens,<br>            &quot;response_tokens&quot;: response_tokens,<br>            &quot;total_tokens&quot;: prompt_tokens + response_tokens,<br>        },<br>    )<br>    <br>    # more things happening ...</pre><p>… basically saying, “hey! Marvin responded in slack” to Prefect Cloud.</p><p>As a technical note, our community Marvin bot is running as a GCP Cloud Run service and has two important environment variables set:</p><pre>PREFECT_API_KEY=pnu_XXX<br>PREFECT_API_URL=https://api.prefect.cloud/api/accounts/&lt;UUID/workspaces/&lt;UUID&gt;</pre><p>which allow it to talk to our Prefect Cloud workspace.</p><p>So after I emit my event, I can pop into the marvin-bot workspace’s Event Feed and see some details about it:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/714/1*NSC4ruKmctR8WEzeAMUEwQ.png" /></figure><p>Now this event lives in the Events DB of my Prefect Cloud workspace, so I can query events over time for token usage by user or whatever else.</p><p>Being able to track aggregate token usage is great, but most exciting to me is the idea of using this event to immediately trigger other actions.</p><h4>the automations system</h4><p>Automations in Prefect Cloud have the form: <strong>Trigger</strong> &gt; <strong>Action</strong></p><p>So, with Prefect Cloud as a listener, I can have the receipt of certain events <strong>trigger</strong> an additional <strong>action</strong>. So here’s my trigger:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/746/1*666fUAOHL7KkKBaO8Y31ew.png" /><figcaption>Where any events with resource ids like `bot.*` will trigger my automation</figcaption></figure><p>… which reads as, trigger this automation once I receive 1 event where the resource id looks like bot.* .</p><p>Now, we decide what our action will be. Let’s choose Run a deployment , since I <a href="https://github.com/PrefectHQ/marvin/blob/main/examples/flows/token_usage.py#L66-L95">already implemented a small flow to increment a token tracker</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/752/1*QDrkR3sNEOMozwWymycoWA.png" /><figcaption>I can use jinja to template event payload data into string fields</figcaption></figure><p>Once I finish configuring the automation, a flow will run each time a user tags Marvin in Slack, incrementing my token tracker.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AyzCZHuNYy8ygBWhsRDAKw.png" /><figcaption>Flow run timeline view for token tracker flow</figcaption></figure><p>Now, if I only ever wanted to increment a token tracker, I could have just done that directly from within the _slackbot_response method from the Marvin app itself, without emitting an event that triggers a Prefect flow.</p><p>However, the larger idea here is that I can easily <strong>do</strong> <strong>arbitrary, observable work by eventing directly off of the user’s interaction with my application.</strong></p><p>I can maintain global state with Prefect Cloud and use it in many ways:</p><ul><li><a href="https://docs.prefect.io/2.10.13/concepts/states/">To govern the state transitions</a> of tasks within my application, and observe each unit of work performed (especially when the LLM is the one deciding how and when to use additional tools)</li><li>I could moderate user-bot interactions with my application(s), in a way that I can modify without re-deploying the Marvin service (i.e., edit an ACL Block in Cloud; the app reads from the Block in Cloud)</li><li>if I were using my own self-hosted LLM for my Marvin app, I could have this interaction <a href="https://docs.prefect.io/2.10.13/api-ref/prefect/deployments/?h=run_deplo#prefect.deployments.run_deployment">trigger a flow</a> to update a domain-specific training set</li><li>I could service user requests received by the Marvin app which require work from different applications, using the Prefect Cloud <a href="https://docs.prefect.io/2.10.13/cloud/events/">Events</a> and <a href="https://docs.prefect.io/2.10.13/cloud/automations/">Automations</a> systems as the router of that work</li></ul><h4>going forward</h4><p>I’m excited to expand the set of events that are emitted by Marvin, and in turn expand the set of actionable information I can leverage as a builder.</p><p>Having spent a lot of time in batch-schedule-land historically, its lovely to enter a fully event-driven world without breaking out a whole new streaming solution / tool.</p><p>As for Marvin’s future, we’ll be working on a first-class and opt-in Event instrumentation of Marvin’s LLM utilities to make as it as easy as possible to build transparent and reactive systems to control our use of AI in code.</p><p>Let us know if you have any questions in our <a href="https://www.prefect.io/community/">Prefect Slack community</a> or our <a href="https://discord.gg/Kgw4HpcuYG">Marvin Discord</a>.</p><p>—</p><p>Happy Engineering!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1dc287b8d177" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/keeping-on-ai-tools-with-marvin-1dc287b8d177">Keeping 👀 on AI tools with Marvin</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[More Memory, More Problems]]></title>
            <link>https://medium.com/the-prefect-blog/more-memory-more-problems-b182d7807e3c?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/b182d7807e3c</guid>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[scalability]]></category>
            <category><![CDATA[postgres]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[database]]></category>
            <dc:creator><![CDATA[Christopher White]]></dc:creator>
            <pubDate>Tue, 16 May 2023 19:21:03 GMT</pubDate>
            <atom:updated>2023-05-16T21:39:17.044Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*d2NsKQR4CmKebuf3" /></figure><h4>The single Postgres setting that almost took down our database</h4><p>Beginning in March, Prefect Cloud saw a significant increase in volume and traffic driven by both new user growth as well as existing teams scaling up — by most measures we were doubling approximately every two months. This is great! But unsurprisingly this level of growth comes with a whole host of challenges for our team, especially when it comes to service reliability and resiliency.</p><p>Importantly, some of our customers suffered from service interruptions related to the issues described here. First and foremost, we are incredibly sorry for the negative impact and stress these incidents caused. We are committed to doing better.</p><p>Sometimes configuring a database at scale can seem like a dark art. While the Postgres documentation is fantastic, there are no hard-and-fast rules for tuning given the diversity of workloads out there and much of what we learned came from sparse references and experimentation. We hope that compiling our findings will help future engineers get ahead of the challenges we faced — if you want to skip the narrative, feel free to <a href="#b999">jump ahead for a summary of our findings</a>.</p><h3>The Context: Scaling Up</h3><p>In anticipation of continued Prefect Cloud growth, we accelerated our plans for a proactive scale-up of our backend. The first phase of this took place in the first two weeks of April, during which we vertically scaled our primary Postgres 14 instance (running via CloudSQL in GCP).</p><p>The second phase of this work involved partitioning our most heavily used tables; large tables can exhibit poor query performance and latency due to indexes that are too large to fit into memory and unreliable table statistics. Partitioning a table essentially breaks the table up into many smaller virtual tables that the query planner can take advantage of, helping to avoid these issues. At the time of our decision, our fastest-growing table had 400 million rows and we expected it to have over 1 billion rows by the time the partitioning work would be completed. In order to partition our tables without incurring downtime, we rolled out our changes incrementally over several weeks (we’ll share more detail in an upcoming post). This allowed us to incrementally update our application to handle partitions, monitor for expected performance improvements, and adjust the plan as we went along.</p><p>We felt confident in our partition calculations and were optimistic that we’d begin seeing performance gains very soon…</p><h3>The Investigation: High Memory Utilization</h3><p>All the while that we were rolling out table partitions, our memory usage remained relatively constant and high. This was especially surprising because we had just vertically scaled the database, but it was difficult to understand the root cause because of the overhead of the in-flight partitioning work. It was of course also possible that our math was wrong and <a href="https://www.postgresql.org/docs/14/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE-BEST-PRACTICES">the partitions were inefficient</a>, or worse, that the memory usage was an independent problem altogether. We remained confident and focused on rolling out the last two table partitions before reaching any definitive conclusions.</p><p>On April 28th, 2023 our hand was forced — during a particularly bursty period of traffic, the database unexpectedly restarted with an out-of-memory (OOM) event when memory utilization hit 90%.</p><p>The first thing we observed was that memory utilization roughly correlated with the number of active connections — this isn’t unique or surprising, as individual connections do have a memory footprint. To reduce memory pressure we scaled down our services to reduce the number of connections at the cost of increased API latency. Connections shouldn’t take up more than a few hundred megabytes of memory <em>at most,</em> and we needed to eventually free up almost 100GB of memory to feel long-term stable — this is all to say that we knew we were only helping the system on the margin, and reducing the number of connections wasn’t a true solution.</p><p>While still in the midst of our investigations, on May 1st, 2023 the database restarted a second time due to another OOM event. It was painfully clear that we couldn’t wait for the partition work to resolve this issue (and even that wouldn’t be guaranteed) — something needed to be done immediately. We began iterating through various <a href="https://www.crunchydata.com/blog/optimize-postgresql-server-performance">Postgres configuration options</a> such as shared_buffers and work_mem to try and see if any moved the needle significantly (our services are able to handle <em>anticipated</em> DB restarts without disruption). All the while we were collectively trying to construct a meaningful theory for <em>why</em> any of this was happening - there was clearly a missing piece of the puzzle. Maybe it was a silent config change in our vertical scale up? Maybe the partition work was quietly introducing something nefarious? The team floated and tested many hypotheses.</p><p>It was in the midst of this experimentation that a new theory emerged — each webserver that communicated with the database managed its own in-process connection pool. In the absence of a pod restart, the connections within each pool were recycled every 2 hours. It was not out of the realm of possibility that these connections might be accruing an increasingly large cache that needed flushing. This seemed all the more likely given the relatively tight correlations we were seeing with scale ups / scale downs and large changes to memory utilization.</p><p>We <a href="https://docs.sqlalchemy.org/en/20/core/pooling.html#setting-pool-recycle">updated our connection pools to recycle their connections</a> every 10 minutes and quickly released the change to prod. Coincidentally and seemingly unrelated, we had noticed that an entirely separate database used for our events system was not being traced properly. So in addition to the pool recycle change, we released a small change that ensured <a href="https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/sqlalchemy/sqlalchemy.html">our SQLAlchemy instrumentor</a> was turned on for all engines (essentially replacing the line SQLAlchemyInstrumentor().instrument(engine=engine.sync_engine) with SQLAlchemyInstrumentor().instrument() ).</p><p>We released these changes and were all on the edge of our seats, monitoring our dashboards to see if we could discern any change. It didn’t take long to see something noticeable:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*51ZGS0ngE_McmqM_T5X4Gw.png" /><figcaption>Memory utilization in our primary database</figcaption></figure><p>It seemed like we had hit the jackpot! The database seemed healthier than ever — we carefully scaled our services back up with almost no effect on memory. On top of that, API latency was better than it had been in weeks. Despite feeling less acutely stressed, we remained unsatisfied with this empirical solution that lacked an airtight explanation; we began trying to refine our original theory to explain why this seemingly simple change brought us back to the healthiest state we had seen in weeks. However, we couldn’t seem to break past the hand-waving phase to anything more specific and testable…</p><h3>The Twist: Load-Bearing Comments</h3><p>As we reflected on the prior few days and were organizing our insights, we noticed a curiosity - a different database that manages account configuration saw an unprecedented increase in received bytes (an increase of almost 400%) at exactly the same time that our primary database recovered from its memory woes. This increase wasn’t inherently concerning as this database doesn’t serve complex workloads, but it was interesting and was not easily explained by our current story.</p><p>It turns out that enabling SQLAlchemyInstrumentor globally for all engines has the effect of adding comments to every query, for example:</p><pre>SELECT *<br>FROM auth.account<br>WHERE auth.account.id IN (%s) <br><br>/*db_driver=&#39;asyncpg&#39;,db_framework=&#39;sqlalchemy%%3A0.34b0&#39;,<br>traceparent=&#39;00-93d35b06052db659c4eb5f5be2db2548-a48d7cb6e8de48f7-01&#39;*/</pre><p>Because queries to our account database were generally small, this had the effect of increasing query size (in bytes) noticeably. You might ask: why is this relevant? We soon discovered that when we explicitly turned comments <em>off</em> across all engines, memory in our primary database began increasing back to the problematic levels.</p><p>This initially made no sense - production-grade tracers shouldn’t have <em>that</em> much impact on performance! We then remembered - we had seen this once before. After we successfully partitioned our events database many months back, we found that executing the exact same prepared statement six times in a row resulted in an indefinite hang on the sixth execution. Adding a unique comment (such as a trace ID) to each execution prevented the hang from ever occurring. This is because Postgres computes a “generic plan” (a plan independent of parameter values) on the sixth execution of the same prepared statement and caches it for future use - but generic plans are typically very costly for queries over partitioned tables (hence the hang). By default, Postgres uses heuristics to decide whether to use the generic plan vs. a custom plan but <a href="https://dba.stackexchange.com/a/289146">those heuristics don’t always work</a>.</p><p>And this was the final piece of the puzzle.</p><h3>The Resolution: Configuring plan_cache_mode</h3><p>Ultimately, here’s what was happening:</p><ul><li><a href="https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#prepared-statement-cache">The asyncpg driver for SQLAlchemy prepares every statement</a></li><li>After seeing the same prepared statement 5 times, Postgres creates a “generic plan” that is then cached for future use</li><li>Generic plans for prepared statements do not take into account specific parameter values and are <a href="https://amitlan.com/2022/05/16/param-query-partition-woes.html">therefore costly for queries over partitioned tables</a></li><li>Using SQL comments for tracing served as a “cache buster” — apparently the generic plan cache is keyed on the raw text of the query!</li><li>Postgres exposes a way to configure this behavior; setting plan_cache_mode=force_custom_plan prevents generic plans from being computed and cached entirely</li></ul><p>If you needed yet another story for why lines-of-code is not an effective measure of impact, the final resolution of our memory issues was to set this single value on each connection to our primary database (easily done by passing connect_args when creating the engine in SQLAlchemy).</p><h3>Conclusion</h3><p>Along with our new knowledge of cache plans in Postgres, our team also emerged with a few learnings that apply to taming the dark art of scaling a production database during periods of rapid iteration; in no particular order:</p><ul><li>incidents can be chaotic, with many moving pieces — maintaining a level head and constantly questioning your assumptions are incredibly valuable skills</li><li>having an airtight release process for quick iteration in prod is critical (thank you to our platform team!)</li><li>Google Cloud SQL reserves 10% of instance memory for other services running on the underlying instance, so database processes are likely to run out of memory when usage exceeds 90%</li><li>If a solution or explanation can’t be deconstructed and reconstructed from multiple perspectives, it’s possible that it isn’t an actual solution</li><li>Observability and tracing are key. Surprisingly, these tools are <em>not</em> always passive participants in your stack!</li><li>plan caches in Postgres are very useful for most standard queries (we definitely saw latency impact on our simple accounts database when toggling this setting), and are destructive for queries over partitioned tables — plan_cache_mode is your friend in this situation</li><li>caching continues to be the root of all evil</li></ul><p>Most importantly we want to thank our users and customers for their confidence and trust — which we do not take for granted — as we work harder than ever to strengthen Prefect’s resiliency far ahead of ongoing growth.</p><p><em>The hard-won knowledge represented above was uncovered and compiled by many engineers at Prefect — in particular Zach Angell, Jake Kaplan, Eddie Park, Chris Guidry, Andrew Brookins, and Jonathan Yu spent many restless days and nights experimenting, monitoring and learning to solve the issue described here and significantly improve our overall service reliability and scalability.</em></p><p><em>To learn more about Prefect:</em></p><ul><li><a href="https://prefect.io/"><em>visit our website</em></a></li><li><em>join our active </em><a href="https://prefect.io/slack"><em>Slack community</em></a></li><li><em>ask questions on our </em><a href="https://discourse.prefect.io/"><em>Discourse</em></a></li><li><a href="https://twitter.com/PrefectIO"><em>follow us on Twitter</em></a><em> for updates</em></li><li><a href="https://github.com/PrefectHQ/prefect"><em>visit us on GitHub</em></a><em> to open issues and pull requests</em></li></ul><p><em>Happy Engineering!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b182d7807e3c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/more-memory-more-problems-b182d7807e3c">More Memory, More Problems</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Picking March Madness Winners Using Prefect]]></title>
            <link>https://medium.com/the-prefect-blog/picking-march-madness-winners-using-prefect-19aa4c151926?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/19aa4c151926</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[prefect]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data]]></category>
            <dc:creator><![CDATA[Robert]]></dc:creator>
            <pubDate>Wed, 15 Mar 2023 20:27:13 GMT</pubDate>
            <atom:updated>2023-03-15T20:32:27.946Z</atom:updated>
            <content:encoded><![CDATA[<p>I grew up loving college basketball and the magic that is March Madness. The excitement of seeing buzzer-beaters and Cinderella stories make the NCAA men’s and women’s basketball tournaments two of the year&#39;s greatest sporting events. It is absolute chaos, and I love every minute of it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nM-qscNCcQ2TysSICGx7iA.png" /></figure><p>I’m invited to several March Madness bracket pools every year because of my love of college basketball (and how much I talk about it). To be honest, I’m not very good at them because navigating March Madness is chaos. As an engineer, I needed to add some logic to this situation to help me figure it out and at least give myself a shot at winning these bracket pools. As Ted Mosby from How I Met Your Mother once said: “This is not March Madness…”</p><figure><img alt="This is March Meticulously thought-outness" src="https://cdn-images-1.medium.com/max/299/1*rAqrtS3M-EzEuXqGdE9JaA.jpeg" /></figure><p>And what better tool than <a href="https://www.prefect.io/">Prefect</a> to help me orchestrate my first go at adding some logic to my decision process. The custom predictive algorithm is simplistic, but by gosh, it is orchestrated and observable thanks to Prefect.</p><p>All of my Prefect flow code and infrastructure configuration can be found at the Github link <a href="https://github.com/robfreedy/march-madness-2023">here</a>.</p><p>Note: This is just code for the Men’s tournament, but changing the RapidAPI ID’s to those for the Women’s tournament will yield similar predictions.</p><h3>The Code</h3><p>Using Prefect flows, subflows, and task decorators, I took the following steps to help me pick the winners of the brackets.</p><ol><li>I gathered the statistics for the 68 teams in the tournament. I used RapidAPI’s Basketball API to gather data for each team. I was able to securely store the API Key using a Prefect <a href="https://docs.prefect.io/concepts/blocks/">secret block</a>.</li><li>I then picked the winners for each team&#39;s first set of games. I used the statistics from each team to calculate a score using win percentage, points scored, and points allowed. Those scores were compared to determine a winner for each of the first individual matchups.</li><li>Then, I ranked the predicted first game winners. After calculating the score, I used Python native sort and lambdas to sort the teams by score. I then used these rankings to fill out the rest of my bracket. These rankings were printed to the screen using Prefect’s log_prints configuration.</li></ol><h3>The Infrastructure</h3><p>The following infrastructure was put together using Prefect deployments and blocks.</p><ul><li>GitHub for Version Control</li><li>S3 for Prefect flow code storage, connected with a Prefect S3 storage block.</li><li>EKS Cluster created using <a href="https://discourse.prefect.io/t/how-to-create-a-kubernetes-cluster-on-aws-eks/1041">eksctl</a>.</li><li>ECR to store the image that the job will use to run the Prefect flow.</li><li>Prefect KubernetesJob to define the job configuration for the flow run.</li></ul><p>All of these components were combined through Prefect deployments and blocks. Instructions for configuring all of these components can be found in the GitHub repository above.</p><p>While these flows can be run locally or on any infrastructure, I chose to run this flow using EKS because of how flexible Kubernetes is with scaling resources and applying updates. Prefect’s integration with Kubernetes is completely customizable, and running these deployments on scalable infrastructure allows me to share this flow with everyone that has access to my workspace, without them having to worry about spinning up new infrastructure. It also provides the flexibility to scale when running more complex models for next year&#39;s tournament!</p><h3>The Results</h3><p>The following results were outputted from the flow code. The algorithm used to calculate these results is <strong><em>SUPER</em></strong> simple, so take these results with a grain of salt. I look forward to adding more data science and machine learning concepts to next year’s tournament.</p><pre>11:23:13.032 | INFO    | Flow run &#39;lavender-asp&#39; - 1. Houston<br>11:23:13.033 | INFO    | Flow run &#39;lavender-asp&#39; - 2. Florida Atlantic<br>11:23:13.034 | INFO    | Flow run &#39;lavender-asp&#39; - 3. Charleston<br>11:23:13.036 | INFO    | Flow run &#39;lavender-asp&#39; - 4. Oral Roberts<br>11:23:13.040 | INFO    | Flow run &#39;lavender-asp&#39; - 5. Gonzaga<br>11:23:13.041 | INFO    | Flow run &#39;lavender-asp&#39; - 6. Alabama<br>11:23:13.042 | INFO    | Flow run &#39;lavender-asp&#39; - 7. UCLA<br>11:23:13.043 | INFO    | Flow run &#39;lavender-asp&#39; - 8. Purdue<br>11:23:13.043 | INFO    | Flow run &#39;lavender-asp&#39; - 9. Arizona<br>11:23:13.044 | INFO    | Flow run &#39;lavender-asp&#39; - 10. Kent State<br>11:23:13.044 | INFO    | Flow run &#39;lavender-asp&#39; - 11. Marquette<br>11:23:13.045 | INFO    | Flow run &#39;lavender-asp&#39; - 12. Iona<br>11:23:13.045 | INFO    | Flow run &#39;lavender-asp&#39; - 13. Drake<br>11:23:13.046 | INFO    | Flow run &#39;lavender-asp&#39; - 14. Furman<br>11:23:13.047 | INFO    | Flow run &#39;lavender-asp&#39; - 15. St. Marys (CA)<br>11:23:13.047 | INFO    | Flow run &#39;lavender-asp&#39; - 16. Louisiana Lafayette<br>11:23:13.048 | INFO    | Flow run &#39;lavender-asp&#39; - 17. Texas<br>11:23:13.049 | INFO    | Flow run &#39;lavender-asp&#39; - 18. Kansas<br>11:23:13.052 | INFO    | Flow run &#39;lavender-asp&#39; - 19. UC Santa Barbara<br>11:23:13.052 | INFO    | Flow run &#39;lavender-asp&#39; - 20. Utah State<br>11:23:13.052 | INFO    | Flow run &#39;lavender-asp&#39; - 21. Kennesaw State<br>11:23:13.053 | INFO    | Flow run &#39;lavender-asp&#39; - 22. Montana State<br>11:23:13.053 | INFO    | Flow run &#39;lavender-asp&#39; - 23. Boise State<br>11:23:13.054 | INFO    | Flow run &#39;lavender-asp&#39; - 24. Texas A&amp;M<br>11:23:13.054 | INFO    | Flow run &#39;lavender-asp&#39; - 25. Texas A&amp;M-CC<br>11:23:13.054 | INFO    | Flow run &#39;lavender-asp&#39; - 26. NC State<br>11:23:13.055 | INFO    | Flow run &#39;lavender-asp&#39; - 27. Nevada<br>11:23:13.055 | INFO    | Flow run &#39;lavender-asp&#39; - 28. USC<br>11:23:13.055 | INFO    | Flow run &#39;lavender-asp&#39; - 29. Kentucky<br>11:23:13.056 | INFO    | Flow run &#39;lavender-asp&#39; - 30. Pittsburgh<br>11:23:13.056 | INFO    | Flow run &#39;lavender-asp&#39; - 31. TCU<br>11:23:13.057 | INFO    | Flow run &#39;lavender-asp&#39; - 32. Maryland<br>11:23:13.058 | INFO    | Flow run &#39;lavender-asp&#39; - 33. Illinois<br>11:23:13.058 | INFO    | Flow run &#39;lavender-asp&#39; - 34. Auburn<br>11:23:13.059 | INFO    | Flow run &#39;lavender-asp&#39; - 35. Iowa State<br>11:23:13.059 | INFO    | Flow run &#39;lavender-asp&#39; - 36. Fairleigh Dickinson</pre><p>I will submit these results to an ESPN bracket, provide an update on how the bracket does, and predict the next Sweet 16 and Elite 8 matchups using Prefect’s flow deployment structure! Stay tuned.</p><h3>Conclusion</h3><p>All in all, college basketball is fun and chaotic. Filling out brackets is all a guessing game, but attempting to predict using simple math can help. One thing is certain: orchestrating and observing this math on your infrastructure using Prefect is the slam dunk your bracket needs.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=19aa4c151926" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/picking-march-madness-winners-using-prefect-19aa4c151926">Picking March Madness Winners Using Prefect</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Keeping Your Eyes on Your AI Tools]]></title>
            <link>https://medium.com/the-prefect-blog/keeping-your-eyes-on-your-ai-tools-6428664537da?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/6428664537da</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[openai]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[prefect]]></category>
            <category><![CDATA[langchain]]></category>
            <dc:creator><![CDATA[Nate Nowack]]></dc:creator>
            <pubDate>Tue, 14 Mar 2023 16:14:24 GMT</pubDate>
            <atom:updated>2023-03-14T16:14:24.149Z</atom:updated>
            <content:encoded><![CDATA[<h3>Keeping Your 👀 on Your AI Tools</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DWL7PzmKSSHbgGHcyFKkig.png" /><figcaption>Big Brother Marvin keeping a watchful eye over our AI minions</figcaption></figure><p>Like everyone else, I’ve been playing with AI over the last couple of months.</p><p>Projects like <a href="https://openai.com/">OpenAI</a> and <a href="https://huggingface.co/">Hugging Face</a> have made it simpler than ever to interact with LLMs like GPT3 — whether it’s popping open a new tab in my browser for <a href="https://openai.com/blog/chatgpt">ChatGPT</a> or <a href="https://openai.com/product#made-for-developers">making a simple API call</a> with Python.</p><blockquote><strong>What is an LLM? <br></strong>“Large Language Model” refers to a type of AI model that’s been trained on massive amounts of text to generate human-like language. There are <a href="https://en.wikipedia.org/wiki/Algorithmic_bias">open questions</a> about the use of LLMs.</blockquote><p>One very interesting way to leverage this new LLM accessibility is being explored by a project called <a href="https://github.com/hwchase17/langchain">LangChain</a>, the growth of which has exploded.</p><p>LangChain is open-source tooling that adds some useful abstractions for interacting with these LLMs, whether that’s:</p><ul><li>callable LLMs to simplify ad-hoc API calls</li><li><a href="https://langchain.readthedocs.io/en/latest/use_cases/agents.html">successive calls to LLMs + tools to refine answers to questions</a></li><li><a href="https://langchain.readthedocs.io/en/latest/use_cases/chatbots.html">building customized chatbots</a> by persisting information in vector <a href="https://pypi.org/project/chromadb/">embedding databases</a>, and querying them to provide LLMs with whatever context it needs to answer a question</li></ul><p>Through these LLM interactions + others, LangChain is in the business of “enabling developers to build applications that they previously could not.”</p><p>I’ll explore some functionality LangChain makes easy, and then present a first pass at a plugin powered by <a href="https://github.com/PrefectHQ/prefect">Prefect</a>, which can help us observe our use of LangChain, named <a href="https://github.com/PrefectHQ/langchain-prefect">langchain-prefect</a>.</p><p><strong>Exploring LangChain</strong></p><p>Starting barebones, if I have an OPENAI_API_KEY on my machine, I can go ahead and pip install langchain openai and do a random ol’ LLM call:</p><pre>from langchain.llms import OpenAI<br><br>llm = OpenAI(temperature=0.9)<br>answer: str = llm(<br>    &quot;What would be a good company name for &quot;<br>    &quot;a company that makes colorful socks?&quot;<br>)<br><br>print(answer) # prints something like `Socktastic!&#39;</pre><p>This is cool. But, to quote LangChain’s README:</p><blockquote>the real power comes when you can combine (LLMs) with other sources of computation or knowledge</blockquote><p>A tool like LangChain starts to gets interesting when I integrate other tools, APIs, and embedding databases — but it also starts to get complex enough that orchestration is non-trivial.</p><h3>As with any tool I’d want to build on top of…</h3><p>Before I dive headlong into building some LLM-based application using LangChain, it’s important to think pragmatically about a few things.</p><p>Let’s say I want to build a chatbot powered by LangChain and some LLM with domain-specific knowledge I can curate and regularly update.</p><p>I’d want the chatbot to handle a conversation with a user, answering their questions using the <strong>LLM + context + tools</strong> at its disposal.</p><p>Implementation details aside, I’d need to answer a couple of questions before deploying this chatbot in a production use case.</p><h4>Ballpark, how much will it cost?</h4><p>The main currency with these LLMs are tokens — for example, OpenAI charges $0.0200 / 1K tokens to use their DaVinci model. 1 token <strong><em>roughly </em></strong>corresponds to one word, depending on who you ask.</p><p>You pay for <strong>total tokens, </strong>which is <strong>prompt tokens </strong>+ <strong>completion tokens, </strong>i.e. text you send the LLM + text you get back from it.</p><p>If I’m using LangChain agents to make successive LLM calls / leverage tools, and they’re <a href="https://langchain.readthedocs.io/en/latest/modules/indexes/combine_docs.html?highlight=stuffing#stuffing">stuffing</a> extra context (tokens) in those calls, how much will that cost me?</p><p><strong>A quick experiment and some math:</strong></p><p>Let’s invoke a LangChain agent like my eventual chatbot might.</p><p>It will use the llm-math tool to do the math and fall back to asking the LLM when it doesn’t know the answer (you could add <a href="https://serpapi.com/blog/up-to-date-gpt-3-info-with/">serpapi</a> as a tool here).</p><pre>from langchain.agents import initialize_agent, load_tools<br>from langchain.llms import OpenAI<br><br>llm = OpenAI(temperature=0)<br>tools = load_tools([&quot;llm-math&quot;], llm=llm)<br>agent = initialize_agent(tools, llm)<br><br>answer: str = agent.run(<br>    &quot;How old is the current Dalai Lama? &quot;<br>    &quot;What is his age divided by 2 (rounded to the nearest integer)?&quot;<br>)<br>print(answer) # The current Dalai Lama&#39;s age divided by 2 is 43</pre><p>When I ran this, the agent made 7 calls to the LLM:</p><p>let<strong> total, p, c </strong>= <strong>total_tokens, prompt_tokens, completion_tokens</strong></p><ul><li><strong>total</strong>: 205 = <strong>p</strong>: 172 + <strong>c</strong>: 33</li><li><strong>total</strong>: 254 = <strong>p</strong>: 242 + <strong>c</strong>: 12</li><li><strong>total</strong>: 243 = <strong>p</strong>: 224 + <strong>c</strong>: 19</li><li><strong>total</strong>: 244 = <strong>p</strong>: 239 + <strong>c</strong>: 5</li><li><strong>total</strong>: 282 = <strong>p</strong>: 256 + <strong>c</strong>: 26</li><li><strong>total</strong>: 245 = <strong>p</strong>: 242 + <strong>c</strong>: 3</li><li><strong>total</strong>: 316 = <strong>p</strong>: 293 + <strong>c</strong>: 23</li></ul><p>That shakes out to a <strong>grand total </strong>of<strong> 1789 tokens = $0.03578 = 3.578 cents</strong></p><p>Not bad, but this is only one invocation of a barebones agent. If I used other <a href="https://langchain.readthedocs.io/en/latest/modules/agents/tools.html?highlight=tools">tools</a> (which could be chains/agents) or pulled more context from a <a href="https://langchain.readthedocs.io/en/latest/reference/modules/vectorstore.html">vectorstore</a> to stuff in my prompts, that number would rise pretty dramatically — depending on the math you’d have to do for your app.</p><p>If you’ve followed along and run the example above, you might wonder how I even figured out my total token usage. It didn’t come back as a return from agent.run 🧐 — but we’ll get to that in just a bit.</p><p><strong>So how much <em>does</em> it cost?</strong></p><p><strong>It depends. </strong>It depends on how well I write my application, what I want it to do, and how well LangChain is leveraging my context when talking to LLMs for me. It certainly seems like something I want to keep track of.</p><p>What I haven’t considered here yet at all is infrastructure costs, although, yeah, we will need a scalable and custom runtime for this app.</p><h4>How do I know that things are working the way I want?</h4><p>On its face, this seems obvious. It’s working if my users get a useful answer from the chatbot, right?</p><p>Well, that’s part of the story for sure. But, this question strikes me as one that’d be most difficult to nail down at production scale — where processes running LLM calls are just one part of a complex workflow.</p><p>As abstractions for interfacing with LLMs mature, it feels like it’s going get difficult to keep track of what’s going on under the hood — just like it’s non-trivial to keep track of modern ELT / ETL at production scale.</p><p><strong>Hypothetical question:</strong> A user opens a support ticket for my chatbot and says my chatbot <a href="https://youtu.be/1TO48Cnl66w?t=18">left them in the cold</a>. How do I know what went wrong?</p><ul><li>Is OpenAI having an incident? did they choke my API calls?</li><li>Is there a problem with my embeddings db?</li><li>Am I orchestrating my LangChain agents/chains incorrectly?</li></ul><p>Building tooling to frame and answer these sorts of questions sounds like a lot. But then again, <strong>Prefect <em>does</em> kinda do a lot</strong>.</p><h3>Making a Prefect plugin for LangChain</h3><p>I’ll share some progress on a plugin for LangChain to address some of the pain points I’ve mentioned above, dubbed langchain-prefect.</p><p>Let’s first go ahead and pip install langchain-prefect…</p><p>and return to that first example, with a new friend named RecordLLMCalls:</p><pre>from langchain.llms import OpenAI<br>from langchain_prefect.plugins import RecordLLMCalls<br><br>llm = OpenAI(temperature=0.9)<br><br>with RecordLLMCalls():<br>  answer: str = llm(<br>      &quot;What would be a good company name for &quot;<br>      &quot;a company that makes colorful socks?&quot;<br>  )<br>  <br>  print(answer)</pre><pre>17:45:19.782 | INFO | prefect.engine - Created flow run &#39;scarlet-chital&#39; for flow &#39;Execute LLM Call&#39;<br>17:45:21.319 | INFO | Flow run &#39;Calling langchain.llms.openai&#39; - Sending &#39;What would be a good company name for a company that makes colorful socks?&#39; to langchain.llms.openai via &lt;function BaseLLM.generate at 0x11e567a30&gt;<br>17:45:22.211 | INFO | Flow run &#39;Calling langchain.llms.openai&#39; - Recieved: NotAnArtifact(name=&#39;LLM Result&#39;, description=&#39;The result of the LLM invocation.&#39;, content=LLMResult(generations=[[Generation(text=&#39;\n\nHappy Feet Socks.&#39;, generation_info={&#39;finish_reason&#39;: &#39;stop&#39;, &#39;logprobs&#39;: None})]], llm_output={&#39;token_usage&#39;: {&#39;completion_tokens&#39;: 7, &#39;total_tokens&#39;: 22, &#39;prompt_tokens&#39;: 15}}))<br>17:45:22.336 | INFO | Flow run &#39;scarlet-chital&#39; - Finished in state Completed()<br><br>Happy Feet Socks.</pre><h4>Uhh, what just happened?</h4><p>Let’s break it down by looking at the logs.</p><p>With langchain-prefect you get prefect. With Prefect, <a href="https://docs.prefect.io/concepts/flows/#flows-overview">you can wrap arbitrary Python functions in a </a><a href="https://docs.prefect.io/concepts/flows/#flows-overview">@flow decorator</a> so when you run one of them, metadata on that execution is tracked (see footnotes).</p><p>From inside of RecordLLMCalls, the LangChain methods that actually make the API call to the LLM are wrapped in a @flow so that Prefect can track the execution when they are invoked. Prefect gives fun names like scarlet-chital to each flow run, so we see something like:</p><pre>prefect.engine - Created flow run &#39;scarlet-chital&#39; for flow &#39;Execute LLM Call&#39;</pre><p>Then we see what is actually being sent to the LLM, how the LLM is being prompted by LangChain, the LLM output, and how many tokens we used.</p><pre>Flow run &#39;Calling langchain.llms.openai&#39; - Sending &#39;What would be a good company name for a company that makes colorful socks?&#39; to langchain.llms.openai via &lt;function BaseLLM.generate at 0x11e567a30&gt;<br>Flow run &#39;Calling langchain.llms.openai&#39; - Recieved: NotAnArtifact(name=&#39;LLM Result&#39;, description=&#39;The result of the LLM invocation.&#39;, content=LLMResult(generations=[[Generation(text=&#39;\n\nHappy Feet Socks.&#39;, generation_info={&#39;finish_reason&#39;: &#39;stop&#39;, &#39;logprobs&#39;: None})]], llm_output={&#39;token_usage&#39;: {&#39;completion_tokens&#39;: 7, &#39;total_tokens&#39;: 22, &#39;prompt_tokens&#39;: 15}}))</pre><p>Finally, since our Python code (or LangChain’s) ran without errors, we see:</p><pre>Flow run &#39;scarlet-chital&#39; - Finished in state Completed()</pre><h4>Here’s where it gets fun.</h4><p>If we pop into a terminal and run prefect server start, you’ll be prompted to <strong>Check out the dashboard at </strong><a href="http://127.0.0.1:4200/"><strong>http://127.0.0.1:4200</strong></a></p><p>… where you’ll see a fancy dashboard just waiting to fill up with colors:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sIXe28O5x9Uoa9WPAVOfhw.png" /><figcaption>The default dashboard view, showing colored dots to represent states of our flow runs</figcaption></figure><p>If we click into our flow run, we see:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VVRpzTgWEuAG1XyP8w4LyA.png" /><figcaption>All the metadata and logs for a given flow run</figcaption></figure><p>The logs and a bunch of metadata on when, what, and how things happened.</p><p><strong>Prefect is uniquely interesting here.</strong></p><p>In Prefect, you can call flows from inside other flows — it’s called a <a href="https://docs.prefect.io/concepts/flows/#composing-flows">subflow</a>.<br>Look what happens if I run the agent example with RecordLLMCalls and add a flow wrapping my agent.run call:</p><pre>from langchain.agents import initialize_agent, load_tools<br>from langchain.llms import OpenAI<br>from langchain_prefect.plugins import RecordLLMCalls<br>from prefect import flow<br><br>llm = OpenAI(temperature=0)<br>tools = load_tools([&quot;llm-math&quot;], llm=llm)<br>agent = initialize_agent(tools, llm)<br><br>@flow<br>def my_flow():<br>    &quot;&quot;&quot;Flow wrapping any LLM calls made by the agent.&quot;&quot;&quot;<br>    return agent.run(<br>        &quot;How old is the current Dalai Lama? &quot;<br>        &quot;What is his age divided by 2 (rounded to the nearest integer)?&quot;<br>    )<br>with RecordLLMCalls(tags={&quot;agent&quot;}):<br>    result = my_flow()</pre><p>Remember how we ran this earlier, and it made 7 LLM calls? Here they are.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jr8R4y05SisyMTtjbgaNCg.png" /><figcaption>Prefect UI showing the subflow runs (i.e. LLM calls) of `my_flow`</figcaption></figure><p>For each one, we’re still tracking LLM input / output and token usage, and now we know where these came from. I used tags={‘agent’} to apply an agent tag to any LLM calls that agent.run created on my behalf.</p><p>The nice thing about Prefect flows is that my_flow that wraps these LLM calls could itself be called in the context of a larger workflow (even nested as a subflow of another wrapping parent flow).</p><p>Here’s what the usage page looks like in OpenAI:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/286/1*ITOT1_IIwRTBTaxkBmmAdA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MdvxydpmlY_DOoLx9FsT7g.png" /><figcaption>not the most enlightening</figcaption></figure><h3>Where do we go from here?</h3><p>Okay, hopefully, that was enough to get the blood flowing. Let’s sum up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7DF4YubZRtsnSylGb9ttqw.png" /><figcaption>so many possibilities</figcaption></figure><p>In general, LLMs are interesting and useful — building apps that use them responsibly feels like a no-brainer. Tools like LangChain make it easier to build LLM apps. We still need to know details about how our apps work, even when we want to use tools with convenient abstractions that can obfuscate those details.</p><p>Prefect is built to help data people run, scale, and observe event-driven work wherever they want. It provides a framework for <a href="https://docs.prefect.io/concepts/deployments/">deployments</a> on a whole slew of runtime environments (from Lambda to Kubernetes) on all the main cloud providers. Productionizing LLMs is right up Prefect’s alley.</p><p>Among the more advanced concepts in Prefect are <a href="https://docs.prefect.io/concepts/results/">results</a>, which are applicable here. Caching common LLM results, aggregating token usage across many actors, and routing good and bad LLM results for use in fine-training all feel like practical choices that results could help implement.</p><p>I’m biased, but frankly I’m excited about Prefect’s potential to address problems in LLM observability. As the AI hype train pushes forward, and we try to balance easy abstractions with control, I believe it will be increasingly important to understand what exactly is happening with applications built on LLMs — the same way it is now for apps built on data warehouses. Real outcomes depend on our tools working as expected.</p><p>Stay tuned for further exploration of tools like LangChain! Questions on langchain-prefect or want to contribute? Find and fork <a href="https://github.com/PrefectHQ/langchain-prefect">the code</a>.</p><p>Happy Engineering!</p><p>—</p><h4>Footnotes</h4><p><strong>How is metadata tracked?<br></strong>By default (with no configuration outside of pip install prefect), Prefect flows spin up a lightweight ephemeral API (FastAPI) and use it to start writing details about flow runs to a SQLite db file ~/.prefect/prefect.db.</p><p>You can inspect flow runs with the dashboard after prefect server start.</p><p>It’s also super simple to <a href="https://docs.prefect.io/ui/cloud-quickstart/">let Prefect Cloud handle UI, API, and DB</a> management for you, for free — but for dev’ing on things, the ephemeral API / local SQLite DB / local UI dashboard is great.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6428664537da" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/keeping-your-eyes-on-your-ai-tools-6428664537da">Keeping Your Eyes on Your AI Tools</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Coordinate ELT in 2023 with Airbyte, dbt and Prefect]]></title>
            <link>https://medium.com/the-prefect-blog/coordinate-elt-in-2023-with-airbyte-dbt-and-prefect-ecd7547e6c1a?source=rss----433b304c71e0---4</link>
            <guid isPermaLink="false">https://medium.com/p/ecd7547e6c1a</guid>
            <category><![CDATA[data]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Nate Nowack]]></dc:creator>
            <pubDate>Tue, 31 Jan 2023 22:29:26 GMT</pubDate>
            <atom:updated>2023-02-01T00:00:59.343Z</atom:updated>
            <content:encoded><![CDATA[<h4>Orchestrate data ingestion and transformation for Snowflake data warehouse with Airbyte, dbt, and Prefect</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jaIYOW5Nv0BmKWQeWVUlRw.png" /></figure><p>When wrangling data on a small team, it’s hard not to rely on tools that help us get event data from the world to our data consumers — they’re just too useful to ignore!</p><p>Though, as a data practice scales, it can get difficult to keep track of your tools’ interactions that are not <em>automated</em> and <em>visible</em>.</p><p>This tutorial is a quick start to a simple, scalable, and maintainable ELT workflow using an open-source stack and a free-tier data warehouse. We’ll be using the following tools:</p><ul><li><a href="https://airbyte.io/">Airbyte</a> to <strong>extract</strong> data sources and <strong>load</strong> them to a data warehouse (like <a href="https://www.snowflake.com/">Snowflake</a>)</li><li><a href="https://www.getdbt.com/">dbt</a> to <strong>transform</strong> the data</li><li><a href="https://www.prefect.io/">Prefect</a> to <strong>automate</strong> and <strong>observe</strong> our ecosystem of tools</li><li>GitHub as a source of event data and a place to store our code</li></ul><p>For the purpose of illustrating an ELT pattern, we choose a fun question that serves as the motivation for the workflow:</p><blockquote>Who are the <strong>common contributors</strong> to airbyte, dbt and prefect?</blockquote><p>Alrighty, let’s get into it!</p><h4>Table of Contents</h4><p>· <a href="#3c7c">Set up an Airbyte instance</a><br>· <a href="#8743">Create some Airbyte connections</a><br> ∘ <a href="#94bb">Create a GitHub source connection</a><br> ∘ <a href="#efc4">Create a Snowflake destination connection</a><br>· <a href="#77df">Create some dbt models to transform the raw data</a><br>· <a href="#2dd8">Write a Prefect flow to orchestrate our tools</a><br> ∘ <a href="#71e7">Let’s use some Prefect collections to make our lives easier</a><br>· <a href="#2834">Define a Prefect flow</a><br>· <a href="#5aca">Running Airbyte connections</a><br>· <a href="#331d">Run dbt models</a><br>· <a href="#a523">Run some queries against the transformed data</a><br>· <a href="#58f0">The whole enchilada</a><br>· <a href="#2de2">Running our flow</a><br>· <a href="#2de6">… and that’s it!</a><br>· <a href="#5958">Extending this example</a><br>· <a href="#623e">Next steps</a></p><h3>Set up an Airbyte instance</h3><p>This tutorial is based on Open-Source Airbyte. If you are using Airbyte Cloud, you’ll need to <a href="https://app.harvestr.io/roadmap/view/pQU6gdCyc/airbyte-roadmap">wait for the Airbyte Cloud API</a> to programmatically interact with Airbyte from Prefect.</p><p>To set up a local Airbyte instance, follow the <a href="https://docs.airbyte.com/quickstart/deploy-airbyte/">Airbyte Quickstart</a> guide or <a href="https://docs.airbyte.com/deploying-airbyte/on-kubernetes-via-helm/#add-helm-repository">use a helm chart</a>.</p><h3>Create some Airbyte connections</h3><p>For this tutorial, we’ll use <a href="https://docs.airbyte.io/integrations/sources/github">Airbyte’s GitHub source connector</a> to pull data from the GitHub organizations whose repositories are used in this tutorial:</p><ul><li><a href="https://github.com/AirbyteHQ/airbyte">airbytehq/airbyte</a></li><li><a href="https://github.com/dbt-labs/dbt-core">dbt-labs/dbt-core</a></li><li><a href="https://github.com/PrefectHQ/prefect">prefecthq/prefect</a></li></ul><h4>Create a GitHub source connection</h4><p>All Airbyte needs to connect to GitHub is a valid <a href="https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token">personal access token</a>.</p><p>Let’s go ahead and create a new connection in the Airbyte UI by clicking on the <strong>Sources</strong> tab and click <strong>+ New Source</strong>. Select the <strong>GitHub</strong> source connector and pass along your token (or comma-separated list of tokens to distribute load across multiple actors):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Zut6_w69PwMNzqT2Rlkm2w.png" /><figcaption>Airbyte UI when creating a GitHub source connector</figcaption></figure><p>For this tutorial, we’ll create a connection for each of the three GitHub organizations we’re interested in, grabbing the repository of interest for each organization.</p><h4>Create a Snowflake destination connection</h4><p>To tell Airbyte where to drop our raw data in Snowflake, we’ll need to provide the following information:</p><ul><li><strong>account</strong>: the name of your Snowflake account</li><li><strong>username</strong>: the name of the user that Airbyte will use in Snowflake</li><li><strong>password</strong>: the password for this user</li><li><strong>database</strong>: the name of the database that Airbyte will use to write data to</li><li><strong>schema</strong>: the name of the schema that Airbyte will use to write data to</li></ul><p>Let’s go ahead and create a new connection in the Airbyte UI by clicking on the <strong>Destinations</strong> tab and then clicking on <strong>+ New Destination</strong>. Select the <strong>Snowflake</strong> destination connector and pass in the information above.</p><blockquote><em>💡 </em><strong><em>Note</em></strong><em>: While you’ve got this info handy, you can also use it to connect your dbt CLI to Snowflake. See the dbt docs for more info — psst… </em><em>dbt debug is your friend!</em></blockquote><h3>Create some dbt models to transform the raw data</h3><p>Now that we’ve given Airbyte the ability to pull data from GitHub and write it to Snowflake, we can use dbt to transform the raw data into a format that’s more useful for our downstream consumers (like BI tools, fun dashboards and downstream dbt models).</p><p>While not the focus of this tutorial, the source code for the dbt models we’ll be using can be found in the linked <a href="https://github.com/zzstoatzz/blogs/tree/master/prefect-airbyte-dbt-snowflake/github_common_contributors">repository</a>.</p><p>In this tutorial, we’ll create two models: <strong>common_commiters</strong> and <strong>common_issue_submitters</strong>. These will create views in Snowflake containing the committers and issues submitters that are common across all three repositories. We’ll then be able to query these views and check out the overlap between the three communities.</p><h3>Write a Prefect flow to orchestrate our tools</h3><p>It’s time to connect our tools with Prefect!</p><h4>Let’s use some Prefect collections to make our lives easier</h4><pre>pip install prefect-airbyte prefect-dbt prefect-snowflake</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*h5tyGPqX197jlJLCIj43Aw.jpeg" /><figcaption>it’s 2023, use a venv</figcaption></figure><p>… and now let’s import some of their useful functionality for our flow:</p><pre>from prefect_airbyte import AirbyteConnection, AirbyteServer<br>from prefect_dbt.cli.commands import trigger_dbt_cli_command<br>from prefect_snowflake.database import SnowflakeConnector, snowflake_multiquery</pre><h3>Define a Prefect flow</h3><p>It’s often useful to take a step back and think about each step we want to take in our flow. In this case, we want to:</p><ul><li><strong>trigger</strong> existing <strong>Airbyte</strong> connections to drop raw data into our Snowflake database</li><li><strong>run</strong> <strong>dbt</strong> models to transform the raw data into something more useful</li><li><strong>query</strong> the transformed data in <strong>Snowflake</strong> to find the answer to our original question</li></ul><p>Let’s dive into the code for each of these steps!</p><h3>Running Airbyte connections</h3><p>Lucky for us, the AirbyteConnection block defined in prefect-airbyte takes care of the hard work for us. If we tell it which connection to trigger and which AirbyteServer to use, it&#39;ll do the rest!</p><p>Let&#39;s create our AirbyteConnection blocks, all on the same AirbyteServer:</p><pre>airbyte_server = AirbyteServer(server_host=&quot;localhost&quot;, server_port=8000)<br><br>airbyte_connections = [<br>    AirbyteConnection(<br>        connection_id=connection_id,<br>        airbyte_server=airbyte_server<br>    )<br>    for connection_id in [<br>        &quot;fa8d5164-ca22-47da-83e0-829cc86a70b8&quot;, # Airbyte Github Stats<br>        &quot;8e3a3ef1-6c2d-4255-99d4-21e9e542d853&quot;, # DBT Github Stats<br>        &quot;980c14d9-2992-46a7-a1dc-2b92e60e1475&quot; # Prefect Github Stats<br>    ]<br>]</pre><p>…and a task to manage their execution:</p><pre>@task<br>def run_airbyte_sync(connection: AirbyteConnection) -&gt; AirbyteSyncResult:<br>    job_run = connection.trigger()<br>    job_run.wait_for_completion()<br>    return job_run.fetch_result()</pre><blockquote><em>💡 </em><strong><em>Note</em></strong><em>: All Prefect blocks that implement the </em><em>JobBlock interface (like </em><em>AirbyteConnection) have a </em><em>trigger method that returns a </em><em>JobRun , which has </em><em>wait_for_completion and </em><em>fetch_result methods.</em></blockquote><h3>Run dbt models</h3><p>To trigger dbt run, you can leverage the predefined Prefect task from the dbt collection called trigger_dbt_cli_command. You can pass the command to run and the path to your dbt project as follows:</p><pre>dbt_result = trigger_dbt_cli_command( # run dbt models<br>    command=&quot;dbt run&quot;,<br>    project_dir=&quot;github_common_contributors&quot;,<br>    wait_for=airbyte_results<br>)</pre><p>Notice the use of wait_for here. This tells Prefect that we want to make sure the airbyte tasks have successfully dropped raw data in Snowflake before we try to transform it with dbt.</p><blockquote><em>💡 </em><strong><em>Note</em></strong><em>: If you’re using </em><a href="https://www.getdbt.com/product/what-is-dbt/"><em>dbt Cloud</em></a><em>, you can use the functionality defined in </em><em>prefect_dbt.cloud instead.</em></blockquote><h3>Run some queries against the transformed data</h3><p>Assuming we now have transformed data, we need to tell Prefect how to connect to Snowflake and which queries to run. The first part can be accomplished by creating a SnowflakeConnector block:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FrSHjINFgF3OICZGOydY4g.png" /><figcaption>Prefect UI while creating a `SnowflakeConnector` block</figcaption></figure><blockquote><em>💡 Note: A </em><em>SnowflakeConnector needs a set of </em><em>SnowflakeCredentials to authenticate with Snowflake. As with any </em><a href="https://docs.prefect.io/concepts/blocks/"><em>block</em></a><em>, you can create this in the UI or programmatically.</em></blockquote><p>Now we can use our SnowflakeConnector to connect to the right place in Snowflake and snowflake_multiquery (another pre-built task) to ask questions about the transformed data that lives there:</p><pre>common_authors, common_issue_submitters = snowflake_multiquery(<br>    queries=[<br>        &quot;select login from commit_authors&quot;,<br>        &quot;select login from issue_submitters&quot;<br>    ],<br>    snowflake_connector=SnowflakeConnector.load(&quot;github-contributors&quot;),<br>    wait_for=[dbt_result]<br>)</pre><p>In the same way as before, we use wait_for to make sure the dbt models have successfully run before we try to query the transformed data.</p><h3>The whole enchilada</h3><p>Pretty nifty, huh? Let’s put it all together into a @flow:</p><pre>@flow(log_prints=True)<br>def my_elt_flow():<br><br>    # run airbyte syncs<br>    airbyte_results = run_airbyte_sync.map(airbyte_connections)<br><br>    # run dbt models<br>    dbt_result = trigger_dbt_cli_command(<br>        command=&quot;dbt run&quot;,<br>        project_dir=&quot;github_common_contributors&quot;,<br>        wait_for=airbyte_results<br>    )<br><br>    # query our fancy new tables<br>    common_authors, common_issue_submitters = snowflake_multiquery(<br>        queries=[<br>            &quot;select login from commit_authors&quot;,<br>            &quot;select login from issue_submitters&quot;<br>        ],<br>        snowflake_connector=SnowflakeConnector.load(&quot;github-contributors&quot;),<br>        wait_for=dbt_result<br>    )<br>    <br>    # log the common contributors among our repos<br>    print(<br>        f&quot;Common authors: {common_authors} &quot;<br>        f&quot;Common issue submitters: {common_issue_submitters}&quot;<br>    )<br><br>if __name__ == &quot;__main__&quot;:<br>    my_elt_flow()</pre><h3>Running our flow</h3><p>To run our flow locally, we can simply run the script:</p><pre>python elt_orchestrator.py</pre><p>…and watch it go 🚀</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VBomeueviU1__UBziNfwsw.png" /><figcaption>shell output after running our flow locally</figcaption></figure><p>Turns out dependabot is the only common contributor since last August — laaaaaaaame.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*RAh09bSkOEV10luN" /><figcaption>Photo by <a href="https://unsplash.com/@thomascpark?utm_source=medium&amp;utm_medium=referral">Thomas Park</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>… but oh well — we have a nice and sturdy ELT pattern now.</p><p>Let’s create a <a href="https://docs.prefect.io/concepts/deployments/">deployment</a> for our flow so we can run it when and where we want.</p><p>To do this, we need to make two decisions:</p><ul><li><strong>where</strong> do we want <strong>to</strong> <strong>run</strong> our flow?</li><li><strong>where</strong> do we want <strong>to</strong> <strong>store</strong> our flow?</li></ul><p>To keep things simple, I’ll run our code locally and keep it in GitHub (where it is, anyway!).</p><p>In terms of prefect deployments, that means I’m using the default <a href="https://docs.prefect.io/concepts/infrastructure/#process">Process infrastructure</a> and a <a href="https://docs.prefect.io/api-ref/prefect/filesystems/#prefect.filesystems.GitHub">GitHub filesystem block</a>.</p><p>The command to create the deployment is:</p><pre>prefect deployment build flows/elt_orchestrator.py:my_elt_flow \<br>	-n &quot;elt-pipeline&quot; \<br>	-sb github/demo-repo \<br>	-a</pre><blockquote><em>💡 </em><strong><em>Note</em></strong><em>: If we wanted to use a different runtime infrastructure (like Kubernetes), we could specify that in the </em><em>prefect deployment build command by passing </em><em>-ib kubernetes/my-k8s-job-template. </em><em>-a tells prefect to go ahead and </em><em>apply the deployment.</em></blockquote><p>With our new deployment created, we can hop over to the UI and run it manually or attach an interval/cron schedule.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5UhgxbYIdeAKiXt6Xk8_rw.png" /><figcaption>Note the “<strong>Schedule</strong>: ✏️ Add” button on the right side</figcaption></figure><h3>… and that’s it!</h3><p>We now have an intelligent Prefect workflow that will not waste time/resources running downstream tasks if upstream tasks fail.</p><p>When the flow fails (as it probably will at some point), it’s easy to identify and triage issues by observing the failing flow run in the Prefect UI!</p><p>If we ever want to change how or when our flow executes, we can do so without ever changing our code!</p><ul><li>change how often it runs → update the deployment schedule in the UI</li><li>change where we store our code → add new storage block, rebuild deployment with prefect deployment build ... -sb block-type/name (same goes for infrastructure block determining where it runs)</li></ul><h3>Extending this example</h3><p>While this pattern is sophisticated enough to handle many teams’ ELT workflows, there are many ways we could extend it:</p><ul><li>parameterize my_elt_flow to accept an arbitrary number of Airbyte Connections or dbt commands to run</li><li>create flow runs from this deployment via API calls when you know Airbyte has new data to sync instead of on a schedule</li><li>use the Airbyte sync row count in AirbyteSyncResult to decide how many threads dbt should run!</li><li>do basically anything you want in Python at any point.</li></ul><h3>Next steps</h3><p>If you haven’t already, you can <a href="https://app.prefect.cloud/">create a Prefect Cloud account</a> for free to serve as your launch pad into a world of more effective data engineering!</p><p>Questions? Feel free to ask them in <a href="https://www.prefect.io/slack/">our Slack Community</a>!</p><p>Happy Engineering!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ecd7547e6c1a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-prefect-blog/coordinate-elt-in-2023-with-airbyte-dbt-and-prefect-ecd7547e6c1a">Coordinate ELT in 2023 with Airbyte, dbt and Prefect</a> was originally published in <a href="https://medium.com/the-prefect-blog">The Prefect Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>