<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://docs.coiled.io</id>
  <title>Blog</title>
  <updated>2026-10-13T18:03:16.390111+00:00</updated>
  <link href="https://docs.coiled.io"/>
  <link href="https://docs.coiled.io/blog/atom.xml" rel="self"/>
  <generator uri="https://ablog.readthedocs.io/" version="0.11.13">ABlog</generator>
  <entry>
    <id>https://docs.coiled.io/blog/coiled-stability-update.html</id>
    <title>Coiled Stability Update</title>
    <updated>2026-07-08T00:00:00+00:00</updated>
    <author>
      <name>Luke Canavan</name>
    </author>
    <content type="html">&lt;section id="coiled-stability-update"&gt;

&lt;p&gt;Coiled experienced two outages in the last few weeks; several production resources began operating too close to their limits, and our monitoring did not surface the risk early enough. The result was multiple production outages. This level of stability does not meet Coiled’s standard for a service customers rely on, so we want to share what happened and what we have done to address it.&lt;/p&gt;
&lt;section id="database-cpu-saturation"&gt;
&lt;h2&gt;Database CPU Saturation&lt;/h2&gt;
&lt;p&gt;On May 6, the production database became the bottleneck when the Aurora writer sustained near-max CPU utilization. During that period, database-backed platform operations became slow or unreliable, including cluster startup. Each cluster startup includes a check of current account usage and quota to ensure users don’t go past their desired spend limit. That check performed a CPU-intensive scan to sum historical usage, and as startup volume increased, both the number of checks and the amount of historical usage data grew. Together, that put too much load on the database writer.&lt;/p&gt;
&lt;p&gt;This has been fixed by moving usage calculations to incremental running totals. Instead of scanning historical usage on every cluster startup, quota checks now use a stored checkpoint and only process newer usage events.&lt;/p&gt;
&lt;figure class="align-default" id="rds-cpu-utilization"&gt;
&lt;img alt="RDS CPUUtilization panel in the AWS console." src="https://docs.coiled.io/_images/rds-cpu-utilization.png" style="width: 700px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;RDS CPUUtilization panel in the AWS console.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The impact is visible in RDS CPU utilization for the production Aurora writer. Before the deployment on July 8, CPU was highly variable with frequent spikes; the deployment occurred after noon, and utilization was lower and more stable afterward. Improved database monitoring and alerting is also now in place so database pressure is visible earlier.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="application-memory-saturation"&gt;
&lt;h2&gt;Application Memory Saturation&lt;/h2&gt;
&lt;p&gt;On June 29, the application infrastructure serving platform traffic became non-responsive due to memory saturation, causing platform requests to fail. Increased platform traffic had driven memory utilization higher across the app stack, pushing the service too close to its operating limits.&lt;/p&gt;
&lt;p&gt;This has been addressed with the following interventions:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;increasing application serving capacity and memory headroom&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rolling application logs more aggressively after disk pressure was found to be contributing to instability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;tuning app worker counts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;removing unnecessary application dependencies&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="align-default" id="app-memory-utilization"&gt;
&lt;img alt="Application memory utilization in Grafana." src="https://docs.coiled.io/_images/app-memory-utilization.png" style="width: 700px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Application memory utilization in Grafana.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Grafana metrics show the impact of the resize: before the incident, application memory utilization was steadily climbing and reached roughly 90%, leaving little operating headroom. After application serving capacity was increased later on June 29, memory utilization dropped sharply and stabilized around 25-40%, leaving substantially more room for traffic spikes.&lt;/p&gt;
&lt;p&gt;This incident also exposed a monitoring gap. During our move from Datadog to Grafana, host-level resource alerts had not been fully re-established, so memory and disk pressure were not surfaced early enough to operating engineers. Alerting on host saturation is essential for a service like Coiled, and host-level Grafana monitoring and alerting is now in place for metrics like memory and disk utilization.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="silver-lining"&gt;
&lt;h2&gt;Silver Lining&lt;/h2&gt;
&lt;p&gt;These failures were due to load increasing beyond our expectations and plans.  Usage has roughly doubled since the beginning of the year.  While this doesn’t excuse outages, it does signal that the company itself is healthy and highly motivated to keep the platform well supported and running smoothly.  This is our top priority.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="closing"&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;The main lesson is that saturation becomes a production risk before a resource reaches 100%. We have added capacity, reduced avoidable database load, and put monitoring in place so these pressures are visible earlier.&lt;/p&gt;
&lt;p&gt;We know Coiled is a service that customers depend on to run important workloads. Supporting those workloads reliably is our highest priority, and we will continue to invest in the capacity, monitoring, and operational practices needed to meet that bar.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled-stability-update.html"/>
    <summary>Coiled experienced two outages in the last few weeks; several production resources began operating too close to their limits, and our monitoring did not surface the risk early enough. The result was multiple production outages. This level of stability does not meet Coiled’s standard for a service customers rely on, so we want to share what happened and what we have done to address it.RDS CPUUtilization panel in the AWS console.</summary>
    <category term="coiled" label="coiled"/>
    <category term="infrastructure" label="infrastructure"/>
    <category term="reliability" label="reliability"/>
    <published>2026-07-08T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/mlflow-on-coiled-sidecars-filestores.html</id>
    <title>MLflow on Coiled with Sidecars and Filestores</title>
    <updated>2025-10-20T00:00:00+00:00</updated>
    <author>
      <name>Alexandre Chabot-Leclerc</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Oct 20, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="mlflow-on-coiled-with-sidecars-and-filestores"&gt;

&lt;p&gt;&lt;strong&gt;Coiled lets you run ephemeral clusters in the cloud. We introduce &lt;em&gt;sidecars&lt;/em&gt; to run arbitrary containers alongside your workload, and &lt;em&gt;filestores&lt;/em&gt; to sync local data to the cloud and persist data across runs. We use them to run an MLflow server concurrently with model training.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://mlflow.org/"&gt;MLflow&lt;/a&gt; is a widely used open source tool for ML experiment tracking, model packaging, registry management, and deployment. Some people host their own server, but many use a hosted solution. Our users asked us about running MLflow on Coiled, and, using some new features, we finally found a way that aligns with Coiled’s principle of only paying for what you use. It’s idiosyncratic, but it works quite well.&lt;/p&gt;
&lt;section id="running-mlflow-as-a-sidecar"&gt;
&lt;h2&gt;Running MLflow as a Sidecar&lt;/h2&gt;
&lt;p&gt;It’s always been possible to run MLflow on Coiled using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;, but this required starting the server before training, obtaining the server’s URL, and passing it to the training code—too many moving parts.&lt;a class="footnote-reference brackets" href="#id2" id="id1" role="doc-noteref"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;1&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Instead, we introduce &lt;em&gt;sidecars&lt;/em&gt;, a feature that lets you specify one or more Docker containers to run on the scheduler and/or workers during jobs. With them, you can run the MLflow server alongside the training code. We have no intention of going full Docker Compose on you, but you may recognize the format if you squint at a sidecar spec YAML file:&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;astral/uv:debian-slim&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uvx mlflow server --host 0.0.0.0 --port 5000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;There’s a name for the sidecar, the image to use, the command to run, and any ports to expose. To launch MLflow, we use a small uv container and &lt;a class="reference external" href="https://docs.astral.sh/uv/guides/tools/"&gt;uvx&lt;/a&gt; instead of using the official MLflow image, like we did in the &lt;a class="reference external" href="https://docs.coiled.io/blog/marimo-on-coiled.html"&gt;marimo&lt;/a&gt; post.&lt;/p&gt;
&lt;p&gt;You can also define sidecars directly in your Python code. Here’s an example of adding a sidecar to a Dask cluster:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scheduler_sidecars&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mlflow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;astral/uv:debian-slim&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;uvx mlflow server --host 0.0.0.0 --port 5000&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;ports&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="persisting-data-with-filestores"&gt;
&lt;h2&gt;Persisting Data with Filestores&lt;/h2&gt;
&lt;p&gt;To persist data across jobs, we use &lt;a class="reference external" href="https://docs.coiled.io/user_guide/data/filestores.html"&gt;filestores&lt;/a&gt;, a new feature that enables:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Copying data between your local machine and Coiled-managed VMs in the cloud,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Persisting it between multiple runs, and&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transferring data between steps in a processing pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s a quick example of using a filestore with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt;&lt;/code&gt; to copy local files from &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;local-directory&lt;/span&gt;&lt;/code&gt; to the cloud VM where &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.py&lt;/span&gt;&lt;/code&gt; runs:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--sync&lt;span class="w"&gt; &lt;/span&gt;local-directory&lt;span class="w"&gt; &lt;/span&gt;--wait&lt;span class="w"&gt; &lt;/span&gt;my_script.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The files will be available in the workers’ working directory, and any outputs that the code writes to the working directory on the cloud VM will be stored in a filestore, available for download to the local machine or to other Coiled-managed cloud VMs. All tasks in a batch job share the filestore, and you can sync back outputs from all the tasks together to your local machine with:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;download&lt;span class="w"&gt; &lt;/span&gt;--into&lt;span class="w"&gt; &lt;/span&gt;output-data
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In the example above, we add the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--wait&lt;/span&gt;&lt;/code&gt; flag to wait until work completes, and then sync new or modified files back to the local machine, instead of the default non-blocking behavior for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The sidecar spec lets you specify filestores to attach. Data in named filestores are downloaded to the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;file:///scratch/filestores&lt;/span&gt;&lt;/code&gt; directory on the VMs. Here, we attach a filestore named &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;mlflow-data&lt;/span&gt;&lt;/code&gt; to persist the MLflow backend data:&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;mlflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;astral/uv:debian-slim&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uvx --python 3.13 mlflow server --host 0.0.0.0 --port 5000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--backend-store-uri file:///scratch/filestores/mlflow-data&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;filestores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mlflow-data&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="training-code-and-launching-the-cluster"&gt;
&lt;h2&gt;Training Code and Launching the Cluster&lt;/h2&gt;
&lt;p&gt;With Coiled Batch, the training code doesn’t &lt;em&gt;need&lt;/em&gt; to know anything about Coiled. That way, it can also run locally. Yet, we find it convenient to set a few &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#&lt;/span&gt; &lt;span class="pre"&gt;COILED&lt;/span&gt;&lt;/code&gt; comments in the header that Coiled will pick up, like:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# COILED env MLFLOW_TRACKING_URI=http://coiled-scheduler:5000&lt;/span&gt;
&lt;span class="c1"&gt;# COILED env MLFLOW_TRACKING_USERNAME=alex&lt;/span&gt;
&lt;span class="c1"&gt;# COILED gpu true&lt;/span&gt;
&lt;span class="c1"&gt;# COILED map-over-values 0.1, 0.05, 0.01&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;These comments:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Set the MLflow server’s URL as the scheduler’s address, now available as &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;http://coiled-scheduler&lt;/span&gt;&lt;/code&gt; on the workers (it’s still available as the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;COILED_BATCH_SCHEDULER_ADDRESS&lt;/span&gt;&lt;/code&gt; environment variable too),&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the user name so it’s logged with the experiment results,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Request VMs with a GPU attached,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And specify the list of learning rates to try. Coiled will launch a VM for each value.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://docs.coiled.io/examples/run-mlflow-on-coiled.html"&gt;model we train in the example&lt;/a&gt; is a straightforward PyTorch classifier on the FashionMNIST dataset and doesn’t contain any Coiled-specific code. The only notable thing is that it accepts the loss value as an argument, so we can train the model locally and on the cloud without modifying the code. You can run it locally with:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;train.py&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Or in the cloud, with the MLflow sidecar alongside, with:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--scheduler-sidecar-spec&lt;span class="w"&gt; &lt;/span&gt;mlflow-sidecar.yaml&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;train.py&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;COILED_BATCH_TASK_INPUT
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;While the cluster is running, you can access the MLflow server using the button on the cluster’s page:&lt;/p&gt;
&lt;p&gt;&lt;img alt="The cluster dashboard shows a button to open the sidedcar app when ports are opened on the sidecar spec." src="https://docs.coiled.io/_images/mlflow-cluster-dashboard.png" /&gt;&lt;/p&gt;
&lt;p&gt;Once the cluster has stopped, you can download the filestore data to your computer with:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;download&lt;span class="w"&gt; &lt;/span&gt;--into&lt;span class="w"&gt; &lt;/span&gt;mlflow-data
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="The filestore data is downloaded to the local machine." src="https://docs.coiled.io/_images/mlflow-file-download.png" /&gt;&lt;/p&gt;
&lt;p&gt;And start the MLflow server to browse the results:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mlflow&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;--backend-store-uri&lt;span class="w"&gt; &lt;/span&gt;mlflow-data&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;--artifacts-destination&lt;span class="w"&gt; &lt;/span&gt;mlflow-data
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Check out the complete &lt;a class="reference external" href="https://docs.coiled.io/examples/run-mlflow-on-coiled.html"&gt;MLflow example&lt;/a&gt; to learn how to browse MLflow data remotely and split the metadata and model data into separate filestores.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="mlflow-is-for-sharing"&gt;
&lt;h2&gt;MLflow Is for Sharing&lt;/h2&gt;
&lt;p&gt;To share MLflow data with other users, simply use the same filestore name. Because filestores share the same bucket for a given Coiled workspace in a given region, data is automatically available to all members of a workspace. The MLflow unique IDs ensure there are no collisions between experiments and runs.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-else-can-you-do-with-sidecars-and-filestores"&gt;
&lt;h2&gt;What Else Can You Do With Sidecars and Filestores?&lt;/h2&gt;
&lt;p&gt;Other than running MLflow, we know some users plan on using sidecars to run a proxy on each worker to make authorized, encrypted connections to databases.&lt;/p&gt;
&lt;p&gt;As for filestores, they address a common request to make it easy to upload local files to clusters when using Coiled Batch. We think you’ll like them. Check out the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/data/filestores.html"&gt;filestore docs&lt;/a&gt; to learn more, and let us know what you think about them.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;hr class="footnotes docutils" /&gt;
&lt;aside class="footnote-list brackets"&gt;
&lt;aside class="footnote brackets" id="id2" role="doc-footnote"&gt;
&lt;span class="label"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;&lt;a role="doc-backlink" href="#id1"&gt;1&lt;/a&gt;&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;If you really want to try running the MLflow server with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;, you can do something like this:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;mlflow&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--sync&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--container&lt;span class="w"&gt; &lt;/span&gt;astral/uv:debian-slim&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;mlflow&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Once the cluster launched, use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.host&lt;/span&gt;&lt;/code&gt; URL as the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;MLFLOW_TRACKING_URI&lt;/span&gt;&lt;/code&gt; in the training script.&lt;/p&gt;
&lt;/aside&gt;
&lt;/aside&gt;
</content>
    <link href="https://docs.coiled.io/blog/mlflow-on-coiled-sidecars-filestores.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Oct 20, 2025</summary>
    <published>2025-10-20T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/how-to-run-pandas-on-gpus-with-coiled.html</id>
    <title>How to run pandas on GPUs with Coiled</title>
    <updated>2025-09-05T00:00:00+00:00</updated>
    <author>
      <name>Sarah Johnson</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Sept. 5, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;6 min read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="how-to-run-pandas-on-gpus-with-coiled"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Sarah Johnson&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a class="reference external" href="https://pandas.pydata.org/"&gt;Pandas&lt;/a&gt; is the go-to library for data manipulation and analysis in Python, but it struggles with large datasets due to its single-threaded, CPU-bound design. As data grows in size and complexity, analysts and data scientists often face slow processing times and memory bottlenecks that interrupt analytical flow and limit business responsiveness.&lt;/p&gt;
&lt;p&gt;Traditional pandas is single-threaded and CPU-bound, which makes it great for prototyping but increasingly inefficient for large-scale data work. By offloading operations to parallel GPU cores, you can accelerate pandas-like workflows without rewriting your entire codebase. Accessing GPUs can be challenging, though. Most people don’t have NVIDIA GPUs locally, so they need cloud GPUs. This typically involves complex cloud setup, IAM configuration, Docker images, infrastructure maintenance, and cost management.&lt;/p&gt;
&lt;p&gt;With tools like RAPIDS cuDF and Coiled, it’s possible to scale these workloads easily and access GPU acceleration with minimal code changes.&lt;/p&gt;
&lt;p&gt;In this article, you’ll learn how to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Accelerate pandas operations using cuDF with minimal code changes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easily access cloud GPUs with Coiled&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Achieve dramatic performance improvements on real datasets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Determine whether GPU-acceleration might be a good fit for your workflow&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="why-gpu-accelerated-pandas-delivers-faster-performance"&gt;
&lt;h2&gt;Why GPU-accelerated pandas delivers faster performance&lt;/h2&gt;
&lt;p&gt;Many data analysis tasks on large datasets are well-suited for GPU acceleration. Embarrassingly parallel operations like filtering, element-wise mappings, and reductions can be run in parallel on a GPU rather than sequentially, resulting in dramatically shorter computation times.&lt;/p&gt;
&lt;p&gt;GPU-accelerated pandas operations can achieve performance improvements of 10-150x compared to CPU-only processing. Real-world benchmarks demonstrate these dramatic speedups:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;H2O.ai Database-like Ops Benchmark&lt;/strong&gt;: &lt;a class="reference external" href="https://developer.nvidia.com/blog/rapids-cudf-accelerates-pandas-nearly-150x-with-zero-code-changes/"&gt;150x faster processing&lt;/a&gt; on 5GB datasets with complex joins and group-by operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;NYC ride-share analysis&lt;/strong&gt;: &lt;a class="reference external" href="https://developer.nvidia.com/blog/simplify-setup-and-boost-data-science-in-the-cloud-using-nvidia-cuda-x-and-coiled/"&gt;8.9x faster execution time&lt;/a&gt; on 64.8 million records&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By leveraging GPU capabilities, data scientists and analysts can:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Process larger datasets without hitting memory limitations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run transformations and calculations significantly faster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Iterate more quickly on data exploration and model development&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="how-coiled-simplifies-gpu-access"&gt;
&lt;h2&gt;How Coiled simplifies GPU access&lt;/h2&gt;
&lt;p&gt;Despite the performance advantages of GPUs, accessing cloud GPU resources often requires complex infrastructure setup, specialized cloud configuration, and significant DevOps expertise. Coiled eliminates these barriers by making cloud GPUs accessible to anyone with basic Python skills, removing the need for specialized hardware investments or cloud infrastructure management.&lt;/p&gt;
&lt;p&gt;The Coiled platform removes common barriers to GPU computing, including:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Hardware acquisition costs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Complex cloud infrastructure configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Driver compatibility issues&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DevOps expertise requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resource provisioning and scaling complexity&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="example-how-often-do-new-yorker-s-tip"&gt;
&lt;h2&gt;Example: How often do New Yorker’s tip?&lt;/h2&gt;
&lt;p&gt;To see how to use Coiled for GPU-accelerated pandas in practice, we’ll look at a subset of &lt;a class="reference external" href="https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page"&gt;Uber and Lyft rides in New York City&lt;/a&gt; over the last several years and perform some simple calculations.&lt;/p&gt;
&lt;section id="sign-up"&gt;
&lt;h3&gt;Sign up&lt;/h3&gt;
&lt;p&gt;First, we &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;sign up for Coiled&lt;/a&gt; using GitHub, Google, or by making our own username and password.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="install-coiled"&gt;
&lt;h3&gt;Install Coiled&lt;/h3&gt;
&lt;p&gt;Next, we’ll install Coiled.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;login
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This will redirect us to the Coiled website to authenticate our computer. It will also save our Coiled token on our computer.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="create-a-gpu-enabled-software-environment"&gt;
&lt;h3&gt;Create a GPU-enabled software environment&lt;/h3&gt;
&lt;p&gt;By default, Coiled &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/sync.html"&gt;automatically copies our local environment to remote VMs&lt;/a&gt;. However, if we do not have an NVIDIA GPU available locally, then we need to create a software environment or use the RAPIDS Docker container (see the &lt;a class="reference external" href="https://docs.rapids.ai/install/"&gt;RAPIDS installation guide&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;To define a software environment, we define the dependencies in a conda environment YAML file:&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id1"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;rapids-env.yaml&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rapidsai&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;conda-forge&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nvidia&lt;/span&gt;
&lt;span class="nt"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python=3.12&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rapids&amp;gt;=25.1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cuda-version&amp;gt;=12.0,&amp;lt;=12.9&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;s3fs&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyterlab&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And then create a Coiled software environment:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;rapids&lt;span class="w"&gt; &lt;/span&gt;--gpu-enabled&lt;span class="w"&gt; &lt;/span&gt;--conda&lt;span class="w"&gt; &lt;/span&gt;rapids-env.yaml
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="launch-a-gpu-enabled-jupyter-notebook"&gt;
&lt;h3&gt;Launch a GPU-enabled Jupyter Notebook&lt;/h3&gt;
&lt;p&gt;Next, we start a JupyterLab instance on a GPU-enabled VM on the cloud:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;notebook&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;--gpu&lt;span class="w"&gt; &lt;/span&gt;--software&lt;span class="w"&gt; &lt;/span&gt;rapids
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Under the hood, Coiled handles:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Provisioning a cloud VM with GPU hardware. In this case, a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;g4dn.xlarge&lt;/span&gt;&lt;/code&gt; (Tesla T4) instance on AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downloading the software environment onto the VM&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tearing everything down once you’re done (no idle costs)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We could also include additional arguments like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--sync&lt;/span&gt;&lt;/code&gt; to live-sync local files, including Jupyter Notebooks, to the cloud machine. Any changes made in the notebook running remotely will be synchronized back to our computer. We need to install Mutagen for that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--vm-type&lt;/span&gt;&lt;/code&gt; to request a specific VM type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--region&lt;/span&gt;&lt;/code&gt; to specify a region. We find GPUs are often easier to get in AWS region us-west-2.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/notebooks.html"&gt;Coiled notebooks documentation&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="running-gpu-accelerated-cudf-pandas"&gt;
&lt;h3&gt;Running GPU-accelerated &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;cudf.pandas&lt;/span&gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;NVIDIA RAPIDS offers GPU acceleration for data science workloads with minimal code changes. The &lt;a class="reference external" href="https://rapids.ai/cudf-pandas/"&gt;cudf.pandas&lt;/a&gt; accelerator enables instant GPU execution of pandas operations by loading the extension with: &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;%load_ext&lt;/span&gt; &lt;span class="pre"&gt;cudf.pandas&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We adapted this example from the &lt;a class="reference external" href="https://docs.rapids.ai/deployment/nightly/examples/rapids-coiled-cudf/notebook/"&gt;RAPIDS documentation&lt;/a&gt;. We’ll look at a subset of the NYC taxi dataset (~60 million rows) and calculate how often New Yorkers tip.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;load_ext&lt;/span&gt; &lt;span class="n"&gt;cudf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pandas&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;s3fs&lt;/span&gt;

&lt;span class="n"&gt;path_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;660&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;715&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;path_files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-data/uber/part.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tipped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tip_frac&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;base_passenger_fare&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tolls&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bcf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sales_tax&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;congestion_surcharge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;airport_fee&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;service&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;HV0003&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;uber&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;HV0005&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;lyft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;HV0002&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;juno&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;HV0004&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;via&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tipped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We can monitor GPU utilization from the Coiled dashboard as the code runs (and afterwards too):&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Chart showing GPU utilization was ~80% during the computation." src="https://docs.coiled.io/_images/gpu-util-cudf-pandas.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="run-a-python-script-on-a-cloud-gpu"&gt;
&lt;h3&gt;Run a Python script on a cloud GPU&lt;/h3&gt;
&lt;p&gt;While Jupyter Notebooks are helpful for interactive workflows, there are a number of ways we can use Coiled to access GPUs. In particular, &lt;a class="reference external" href="https://docs.coiled.io/user_guide/batch.html"&gt;Coiled Batch&lt;/a&gt; is an easy way to run any script on the cloud. It is especially useful for scaling large, independent tasks (ie, embarrassingly parallel workloads) across hundreds of VMs.&lt;/p&gt;
&lt;p&gt;We could run the same analysis as above by adding the following metadata to the top of the script:&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id2"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.py&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# COILED gpu True&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# COILED software rapids&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# COILED region us-east-2&lt;/span&gt;
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;s3fs&lt;/span&gt;

&lt;span class="n"&gt;path_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;660&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;715&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;path_files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-data/uber/part.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tipped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tip_frac&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;base_passenger_fare&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tolls&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bcf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sales_tax&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;congestion_surcharge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;airport_fee&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;service&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;HV0003&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;uber&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;HV0005&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;lyft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;HV0002&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;juno&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;HV0004&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;via&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tipped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;and then launch the script with the following command, using the &lt;a class="reference external" href="https://docs.rapids.ai/api/cudf/stable/cudf_pandas/usage/#command-line-usage"&gt;RAPIDS CLI&lt;/a&gt; to use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;cudf.pandas&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;cudf.pandas&lt;span class="w"&gt; &lt;/span&gt;my_script.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Under the hood Coiled will:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Spin up appropriate machines as defined by &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#&lt;/span&gt; &lt;span class="pre"&gt;COILED&lt;/span&gt;&lt;/code&gt; comments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Download the specified software environment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shut down the machines&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this example, we’re running a single Python script, but Coiled Batch especially excels at easily scaling large embarrassingly parallel jobs, without the scheduler overhead of tools like Dask. Some common use cases include:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Running an arbitrary bash script 100 times in parallel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/gpu-job.html"&gt;&lt;span class="doc"&gt;Training a PyTorch model on a GPU&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/distributed-training-accelerate.html"&gt;&lt;span class="doc"&gt;Multi-node GPU LLM fine-tuning&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="when-to-use-gpu-acceleration"&gt;
&lt;h2&gt;When to use GPU acceleration&lt;/h2&gt;
&lt;p&gt;While GPUs can offer impressive speedups, they’re not always the right choice, and there’s some nuance to using them well. Some general guidelines include:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Minimize memory transfers&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Avoid moving data back and forth between CPU and GPU repeatedly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The PCIe bus transfer time can dominate your computation time&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For small datasets with simple operations, this transfer overhead can make GPU processing slower than CPU&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Maximize operational intensity&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Choose problems that do lots of computation on relatively small amounts of data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Good fit&lt;/strong&gt;: Matrix multiplication (O(n³) operations on O(n²) data)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Poor fit&lt;/strong&gt;: Simple string matching (O(n) operations on O(n) data)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;a class="reference external" href="https://docs.nersc.gov/tools/performance/roofline/"&gt;roofline analysis&lt;/a&gt;: memory-bound operations may not benefit from GPU acceleration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Don’t launch lots of small kernels&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Avoid operations that create thousands of tiny CUDA kernels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each kernel launch has overhead - many small operations can be slower than fewer large ones&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Let optimized libraries like cuDF handle the kernel management&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kaashif Hymabaccus gave a detailed &lt;a class="reference external" href="https://www.youtube.com/watch?v=WfXfj29KhLo"&gt;talk on this topic at PyCon US&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="faqs"&gt;
&lt;h2&gt;FAQs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Can you run pandas on a GPU?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Traditional pandas cannot run directly on a GPU, but RAPIDS cuDF offers a nearly identical API that executes on GPU hardware.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is the fastest way to run pandas on GPUs?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The fastest way to run pandas on GPUs is by using RAPIDS cuDF with the cudf.pandas accelerator. It requires minimal code changes and can achieve &lt;a class="reference external" href="https://developer.nvidia.com/blog/rapids-cudf-accelerates-pandas-nearly-150x-with-zero-code-changes/"&gt;up to 150x speedup&lt;/a&gt; on analytics workloads. You can scale this further using Dask-cuDF for multi-GPU processing and manage GPU clusters easily with Coiled’s cloud platform.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I set up Coiled for GPU-accelerated pandas workflows?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To use Coiled for GPU workloads, install Coiled via pip/conda/uv and launch a GPU-enabled cluster. Coiled handles all cloud provisioning, GPU drivers, and scaling. See the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/gpu.html"&gt;Coiled documentation on GPUs&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When should I use GPU acceleration for pandas workflows?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Consider using GPU acceleration for:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large datasets with high computational intensity&lt;/strong&gt;: There is some overhead with copying data to the GPU, so your data must be sufficiently large to offset this cost.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Matrix operations and linear algebra&lt;/strong&gt;: Operations that scale as O(n²) or O(n³) with input size&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parallel aggregations&lt;/strong&gt;: Group-by operations on large datasets with many groups (where data shuffling isn’t required)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Batch processing&lt;/strong&gt;: You can process multiple operations together rather than one at a time&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;When should I stick with a CPU for pandas workflows?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GPU acceleration may actually hurt performance when:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Small datasets&lt;/strong&gt;: Transfer overhead dominates computation time&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simple operations&lt;/strong&gt;: String searches, basic filtering, or operations with low computational intensity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory-bound problems&lt;/strong&gt;: Operations limited by memory bandwidth rather than compute capacity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frequent CPU-GPU transfers&lt;/strong&gt;: Workflows that require moving data back and forth repeatedly&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/how-to-run-pandas-on-gpus-with-coiled.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Sept. 5, 2025</summary>
    <published>2025-09-05T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/building-a-scientific-python-stack.html</id>
    <title>Building a scientific Python stack with Coiled</title>
    <updated>2025-08-26T00:00:00+00:00</updated>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Aug 26, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;9 min read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="building-a-scientific-python-stack-with-coiled"&gt;

&lt;p&gt;At the heart of most scientific computing stacks are a handful of core libraries that provide general-purpose numerical methods, domain-specific analyses, and the machinery for machine learning.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://numpy.org/"&gt;NumPy&lt;/a&gt; and &lt;a class="reference external" href="https://scipy.org/"&gt;SciPy&lt;/a&gt; provide fundamental array operations, linear algebra, and scientific routines. &lt;a class="reference external" href="https://pandas.pydata.org/"&gt;Pandas&lt;/a&gt; and &lt;a class="reference external" href="https://docs.xarray.dev/en/stable/"&gt;Xarray&lt;/a&gt; handle structured/tabular and labeled multi-dimensional data, respectively. Matplotlib and Plotly enable visualization. Scikit-learn, PyTorch, and TensorFlow power machine learning and deep learning workflows. Dask scales computations from a single laptop to a distributed cluster.&lt;/p&gt;
&lt;p&gt;In many cases, custom libraries or domain-specific frameworks are also part of the mix, especially for specialized fields like geospatial analysis, genomics, or computational physics.&lt;/p&gt;
&lt;p&gt;Traditionally, these tools are used on local machines, possibly leveraging multi-core CPUs or single-GPU setups. As dataset sizes grow or as computational needs (such as parameter sweeps, simulations, or parallel data processing) increase, teams quickly reach the limits of what a single piece of hardware can deliver.&lt;/p&gt;
&lt;section id="the-challenge-scaling-beyond-a-workstation"&gt;
&lt;h2&gt;The challenge: Scaling beyond a workstation&lt;/h2&gt;
&lt;p&gt;Scaling up scientific Python workflows introduces technical barriers that often distract from the actual scientific or analytical task. Installing and synchronizing software environments across different machines creates consistency challenges. Managing cluster orchestration—starting, stopping, and monitoring resources—requires specialized expertise. Teams must troubleshoot network, storage, and reliability issues while dealing with cost controls and resource optimization to avoid unexpected bills.&lt;/p&gt;
&lt;p&gt;For a data science team in biomedical research, e.g., these challenges might manifest when analyzing terabytes of RNA sequencing data. A local workstation might handle preliminary analyses, but running full pipelines, bootstrapped permutations, or deep learning model training on such massive datasets is impractical without access to scalable compute resources.&lt;/p&gt;
&lt;p&gt;The transition to distributed computing is often daunting. Traditional solutions require extensive setup through frameworks like MPI or Kubernetes and frequent code rewrites to adapt to new APIs or cluster environments. As a result, many teams either over-invest in local infrastructure with high up-front costs or underutilize the cloud due to operational friction.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="coiled-a-platform-for-scalable-python-computing-on-the-cloud"&gt;
&lt;h2&gt;Coiled: A platform for scalable Python computing on the cloud&lt;/h2&gt;
&lt;p&gt;Coiled addresses these obstacles by providing a platform that lets teams run their Python code—unchanged—on the cloud, at any scale. The platform enables frictionless development where Python developers can continue using their existing tooling and libraries, with no need to rewrite code for new environments.&lt;/p&gt;
&lt;p&gt;Coiled provides efficient, on-demand cloud computing by provisioning the right resources in the right cloud regions, running jobs, and tearing down resources automatically. &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/sync.html"&gt;Environment synchronization&lt;/a&gt; ensures whatever is installed on your development machine is mirrored in the cloud cluster, maintaining consistency and minimizing dependency errors.&lt;/p&gt;
&lt;p&gt;Integrated dashboards and auto-shutdown prevent runaway costs and provide clarity on performance through built-in observability and cost controls.&lt;/p&gt;
&lt;p&gt;Consider a research group in climate science building an ensemble model to forecast temperature patterns at high spatial resolution. Their workflow starts with basic array processing and data cleaning but quickly escalates to bootstrapped uncertainty estimation and model validation across decades of global data. With Coiled, they simply update their workflow to request cloud resources, and everything else remains familiar. The cluster scales up, the code runs, and results are returned—without the team needing to assemble custom Docker images or coordinate with IT to set up cloud networking.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="integration-with-the-scientific-python-stack"&gt;
&lt;h2&gt;Integration with the scientific Python stack&lt;/h2&gt;
&lt;p&gt;Coiled fits into the existing ecosystem for scientific Python by leveraging key libraries. Through &lt;a class="reference external" href="https://coiled.io/product/dask"&gt;Dask integration&lt;/a&gt;, Coiled enables parallel and distributed computing with minimal code changes. Complex dataframes, arrays, and machine learning computations can be chunked and performed in parallel, using the same high-level APIs as local development.&lt;/p&gt;
&lt;p&gt;Beyond distributed Dask clusters, &lt;a class="reference external" href="https://coiled.io/product/batch"&gt;Coiled Batch&lt;/a&gt; enables parallel processing of any code—whether that’s running hundreds of Python simulations with different parameters or leveraging Julia for high-performance scientific computing. This flexibility means teams can parallelize Monte Carlo simulations, parameter sweeps, or any embarrassingly parallel workload across cloud resources without being limited to specific frameworks.&lt;/p&gt;
&lt;p&gt;Custom environments allow researchers to add dependencies or switch between projects without delay. For example, a genomics team needing a new release of Biopython only needs to install it locally; Coiled syncs the environment automatically when the job is launched on the cloud.&lt;/p&gt;
&lt;p&gt;Coiled works with existing Python packaging, &lt;a class="reference external" href="https://coiled.io/use-cases/prefect"&gt;workflow orchestration tools like Prefect&lt;/a&gt; or Dagster, and any Python-compatible editor or notebook environment. Teams can mix and match open-source tools without vendor lock-in.&lt;/p&gt;
&lt;p&gt;Consider the Lawrence Berkeley National Lab using &lt;a class="reference external" href="https://coiled.io/customers/lbnl"&gt;Xarray to analyze satellite imagery data&lt;/a&gt;. As the area of analysis grows from local watersheds to entire continents, memory limitations become severe. Rather than rewriting their codebase, the team used Coiled to provision a distributed cluster, leveraging cloud resources to handle continent-scale analyses.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-mineral-exploration-at-scale"&gt;
&lt;h2&gt;Example: Mineral exploration at scale&lt;/h2&gt;
&lt;p&gt;KoBold Metals, a company dedicated to discovering new battery metals, depends on evaluating vast regions for signs of subsurface mineral deposits using remote sensing, geophysics, and AI models.&lt;/p&gt;
&lt;p&gt;Early in a project, they analyze limited geochemical samples and low-resolution magnetic maps. But as interest grows in a region, the data volume expands: airborne geophysics, electromagnetic surveys, drilling logs, and hyperspectral imagery all need to be synthesized at fine spatial resolution.&lt;/p&gt;
&lt;p&gt;On a single machine, these tasks are not feasible. Workflows such as computing spatial derivatives via Fourier transforms on gigapixel magnetic map rasters cannot complete in a reasonable timeframe or within available memory.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://coiled.io/customers/kobold"&gt;Using Coiled, KoBold shifts its analysis to a distributed Dask cluster&lt;/a&gt;, using their existing Python codebase. The computation, which previously failed or took days, now completes in hours across the cloud, allowing the exploration team to rapidly test hundreds of geological hypotheses. This approach increases scientific rigor and shortens time-to-decision, letting the business allocate exploration budgets where data suggests the best prospects.&lt;/p&gt;
&lt;p&gt;As KoBold scales its operations, the technical team does not need to build or maintain cloud infrastructure. Scientists and engineers can install the latest analysis libraries as needed, confident that their development environment will be matched in each distributed run.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="observability-and-optimization"&gt;
&lt;h2&gt;Observability and optimization&lt;/h2&gt;
&lt;p&gt;Scientific computing demands not only raw power but also transparency. When data loads or model training jobs fail, researchers need immediate, actionable feedback. Coiled provides detailed dashboards showing cluster health, progress, and cost, with direct links to Python-level logs and errors.&lt;/p&gt;
&lt;p&gt;A data science team training deep learning models on environmental sensor data, using Optuna and Dask to parallelize hyperparameter tuning, might notice that memory usage spikes in certain epochs or that certain data partitions take significantly longer to process. Using Coiled’s observability features, they identify bottlenecks and optimize both their models and cluster resource allocation.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="cost-control-and-cloud-efficiency"&gt;
&lt;h2&gt;Cost control and cloud efficiency&lt;/h2&gt;
&lt;p&gt;Cloud resources can be cost-effective when used conscientiously, or can become unexpectedly expensive through misuse or oversight. Common pitfalls include leaving machines running after computations are complete, unnecessary data transfer between cloud regions, or selecting oversized machine types for small jobs.&lt;/p&gt;
&lt;p&gt;Coiled is &lt;a class="reference external" href="https://docs.coiled.io/user_guide/costs/index.html"&gt;designed to encourage responsible usage&lt;/a&gt;. Clusters auto-shutdown when idle, leaving no orphaned resources. Dashboards make cloud spend visible and actionable. Jobs and resources are configured to match the scale of the task, avoiding both underutilization and overprovisioning.&lt;/p&gt;
&lt;p&gt;For a biomedical research lab processing hundreds of thousands of medical images nightly, this means they can scale up to meet demand but also automatically scale down—maintaining only minimal persistent resources and controlling costs in line with real activity.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="focus-on-science-not-infrastructure"&gt;
&lt;h2&gt;Focus on science, not infrastructure&lt;/h2&gt;
&lt;p&gt;The primary advantage of building a scientific Python stack with Coiled is the shift of focus back to scientific or analytical value. &lt;a class="reference external" href="https://coiled.io/customers"&gt;Many teams report significant reductions in the time engineers spend managing infrastructure&lt;/a&gt;, debugging environments, or adapting workflows to unfamiliar distributed systems.&lt;/p&gt;
&lt;p&gt;Domain experts can iterate on analysis code, experiments, or modeling approaches without waiting for IT or DevOps support. New projects or collaborations can begin immediately, without lengthy setup or onboarding. Teams can experiment more freely, attempting analyses or rescaling projects that would previously have been impractical due to technical hurdles.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-mapping-tropical-forests-at-planetary-scale"&gt;
&lt;h2&gt;Example: Mapping tropical forests at planetary scale&lt;/h2&gt;
&lt;p&gt;Space Intelligence, a 60-person company, tackles one of the most computationally demanding geospatial challenges today: creating detailed maps of the world’s tropical forests to support their protection and restoration. To map a country like Brazil, they process hundreds of terabytes of multi-spectral satellite data at 10-meter resolution across multiple time periods.&lt;/p&gt;
&lt;p&gt;Their workflow combines data acquisition, dimension reduction, machine learning classification, and post-processing—all built on the Python geospatial stack including Xarray, Zarr, Cloud-Optimized GeoTIFFs, and Scikit-learn, orchestrated through Dagster.&lt;/p&gt;
&lt;p&gt;Initially, the team tried local processing on laptops, then moved to AWS Lambda functions, and eventually built their own Kubernetes-based Dask infrastructure. However, their DIY approach suffered from significant inefficiencies. Up to half their compute usage was wasted due to cluster fragmentation, with memorable incidents like keeping 1,000 CPUs running for three days due to a single long-running job.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://coiled.io/customers/space-intelligence"&gt;After transitioning to Coiled&lt;/a&gt;, Space Intelligence found the setup “phenomenally easy” and can now process continental-scale data using up to 300 workers. The platform’s simple VM-based architecture eliminated the fragmentation issues they experienced with Kubernetes, while integrated monitoring provided the high-quality metrics their team needed.&lt;/p&gt;
&lt;p&gt;Most importantly, Coiled’s expert support extended beyond just the platform itself, helping optimize their broader Python geospatial stack and debug performance issues across the entire ecosystem. This shift allowed the team to focus on their core mission: building detailed forest maps that power conservation efforts and carbon credit programs in the fight against climate change.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="business-and-scientific-impact"&gt;
&lt;h2&gt;Business and scientific impact&lt;/h2&gt;
&lt;p&gt;The capabilities provided by Coiled have downstream implications beyond individual project acceleration. Companies can prototype and move to production faster, testing new hypotheses or products in days instead of weeks or months. Engineering and research time is freed up to focus on core innovation. Access to scalable computing means organizations are no longer constrained by prior infrastructure investments or organizational silos.&lt;/p&gt;
&lt;p&gt;In highly competitive domains—whether mineral exploration, pharmaceuticals, or climate analytics—this capability translates to actionable insights, smarter resource allocation, and speed to impact in the market.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Coiled makes large-scale, distributed scientific computing feel as natural as local development. By abstracting complexity, ensuring environment consistency, and embedding intelligent cost controls, teams can step directly from ideas to execution at any scale.&lt;/p&gt;
&lt;p&gt;As the world’s most pressing challenges demand larger and more complex analyses, the ability to build scalable scientific Python stacks without friction becomes essential. Coiled provides this foundation, ensuring computational power is available where it’s needed most.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="faqs"&gt;
&lt;h2&gt;FAQs&lt;/h2&gt;
&lt;section id="what-is-scientific-python"&gt;
&lt;h3&gt;What is scientific Python?&lt;/h3&gt;
&lt;p&gt;Scientific Python refers to a collection of Python libraries and tools specifically designed for scientific computing, data analysis, and visualization. At its core are libraries like NumPy and SciPy for numerical computations, Pandas and Xarray for data manipulation, Matplotlib and Plotly for visualization, and libraries like Scikit-learn, PyTorch, and TensorFlow for machine learning. These tools collectively enable researchers, data scientists, and analysts to perform complex scientific computations efficiently.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-is-python-used-in-science"&gt;
&lt;h3&gt;How is Python used in science?&lt;/h3&gt;
&lt;p&gt;Python is used extensively across various scientific disciplines. Scientists use it for data analysis, simulation, modeling, and visualization. For example, climate scientists use Python to build multi-model ensembles for temperature forecasting, biomedical researchers analyze RNA sequencing data, and geospatial analysts process satellite imagery. Python’s flexibility allows it to &lt;a class="reference external" href="https://docs.coiled.io/"&gt;scale from simple analyses on local workstations to complex distributed computations on cloud platforms&lt;/a&gt; using tools like Dask and Coiled, making it suitable for both small experiments and large-scale scientific projects.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="does-nasa-use-python"&gt;
&lt;h3&gt;Does NASA use Python?&lt;/h3&gt;
&lt;p&gt;Yes, &lt;a class="reference external" href="https://coiled.io/customers/nsidc"&gt;there are a number of different teams at NASA, like the National Snow and Ice Data Center, that extensively use Python&lt;/a&gt;. Python plays a crucial role in processing and analyzing the vast amounts of data collected from space missions, telescopes, and satellites. NASA scientists use Python libraries for tasks ranging from orbital mechanics calculations to image processing of planetary and deep space observations. Python’s accessibility and robust scientific computing capabilities make it valuable for NASA’s research teams working on complex problems in astrophysics, planetary science, and space mission planning.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/building-a-scientific-python-stack.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Aug 26, 2025</summary>
    <published>2025-08-26T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/marimo-on-coiled.html</id>
    <title>Marimo on Coiled</title>
    <updated>2025-08-21T00:00:00+00:00</updated>
    <author>
      <name>Nat Tabris and Alexandre Chabot-Leclerc</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Aug 21, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;5m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="running-marimo-on-the-cloud-with-coiled-and-uv"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Nat Tabris and Alexandre Chabot-Leclerc&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled makes it easy to take anything you can run locally and run it in the cloud. That way, you have access to more machines, bigger machines, or machines that are close to your cloud data.&lt;/p&gt;
&lt;p&gt;Many applications have an architecture where you run a local server, and then use the application in your web browser. Coiled supports this type of interactive use case with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt; &lt;span class="pre"&gt;--interactive&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this post, we’ll walk through how to use this to run &lt;a class="reference external" href="https://marimo.io/"&gt;marimo&lt;/a&gt; on a cloud VM.&lt;/p&gt;
&lt;p&gt;For non-interactive work, or for work that you want to run in parallel across many cloud VMs, we recommend &lt;a class="reference external" href="https://docs.coiled.io/user_guide/batch.html"&gt;using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;section id="running-in-docker-on-your-local-computer"&gt;
&lt;h2&gt;Running in Docker on Your Local Computer&lt;/h2&gt;
&lt;p&gt;First, we’ll see one way to run marimo locally using Docker.&lt;/p&gt;
&lt;p&gt;You could use a Docker image that already has &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; installed, but we’ll use a small &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt;&lt;/code&gt; container and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uvx&lt;/span&gt;&lt;/code&gt; to install &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; on the fly. It’s a great way to get started quickly, and it gives you a lot of flexibility. Also, these &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt;&lt;/code&gt; progress bars are addictive.&lt;/p&gt;
&lt;p&gt;We can run a marimo notebook server locally via Docker using a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt;&lt;/code&gt; container like this:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;:2718&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;astral/uv:debian-slim&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;marimo&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--headless&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This command will print a URL to access your &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; notebook in a web browser. Any computations we execute in that notebook will run on our local computer inside the Docker container that’s running &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When we run marimo, we use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--port&lt;/span&gt; &lt;span class="pre"&gt;2718&lt;/span&gt;&lt;/code&gt; to tell it which port to use for the web server; this needs to match the port we expose on the Docker container. We use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--headless&lt;/span&gt;&lt;/code&gt; to tell marimo not to try automatically opening a browser window (which won’t work from inside Docker). Finally, we use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--host&lt;/span&gt; &lt;span class="pre"&gt;0.0.0.0&lt;/span&gt;&lt;/code&gt; so that &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; doesn’t need to know the IP address that it’s serving on (which will be different inside and outside Docker).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="running-in-the-cloud"&gt;
&lt;h2&gt;Running in the Cloud&lt;/h2&gt;
&lt;p&gt;Now that we know how to run &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; locally in Docker, it’s easy to run the same code (or anything else) in the cloud using Coiled.&lt;/p&gt;
&lt;p&gt;Why run marimo in the cloud? When you’re running on a cloud VM, any compute you execute in the notebook will run on that cloud VM, not your local machine. The cloud gives you easy access to powerful hardware—big CPUs, lots of memory, or GPUs—and makes it easy to run computations close to data stored in the cloud, which can significantly reduce time and cost for moving data between the cloud and your local machine.&lt;/p&gt;
&lt;p&gt;In fact, marimo recently launched &lt;a class="reference external" href="https://marimo.io/blog/announcing-molab"&gt;molab&lt;/a&gt;, a free, hosted notebook workspace that’s fantastic for quickly trying things out without any setup. If you need more flexibility—running on your own cloud, choosing any instance type, or using GPUs—Coiled can provide it.&lt;/p&gt;
&lt;p&gt;As before, we’ll use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;astral/uv:debian-slim&lt;/span&gt;&lt;/code&gt; container with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uvx&lt;/span&gt;&lt;/code&gt; to install &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; on the fly.&lt;/p&gt;
&lt;p&gt;Instead of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;, we’ll use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;, with just a few small tweaks to the arguments. Here’s the full command:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--container&lt;span class="w"&gt; &lt;/span&gt;astral/uv:debian-slim&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;marimo&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--headless&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--proxy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;COILED_CLUSTER_HOSTNAME:2718
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;There’s no need for the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--rm&lt;/span&gt;&lt;/code&gt; flag that we used with Docker to clean up the container on exit. On Coiled, when you exit &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; (either in the browser or by hitting Control-C in your terminal), Coiled will automatically shut down the VM and clean up.&lt;/p&gt;
&lt;p&gt;For the port, we specify a single one with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--port&lt;/span&gt; &lt;span class="pre"&gt;2718&lt;/span&gt;&lt;/code&gt;. Docker needs to know which internal port to map to which external port, but Coiled only needs to know which port to open in the VM’s firewall.&lt;/p&gt;
&lt;p&gt;We’re explicitly specifying to use a container with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--container&lt;/span&gt;&lt;/code&gt; option because, unlike Docker, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt; supports &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/sync.html"&gt;other ways&lt;/a&gt; to ship your software environment to the cloud.&lt;/p&gt;
&lt;p&gt;For the marimo command, we add &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--proxy&lt;/span&gt; &lt;span class="pre"&gt;\$COILED_CLUSTER_HOSTNAME:2718&lt;/span&gt;&lt;/code&gt; so that marimo prints the correct URL for our cloud VM.&lt;/p&gt;
&lt;p&gt;That’s it! Once Coiled prepares your cloud VM, you’ll get a link to use Marimo in your web browser:&lt;/p&gt;
&lt;figure class="align-default" id="marimo-terminal"&gt;
&lt;img alt="Lauching marimo with Coiled from a terminal." src="https://docs.coiled.io/_images/marimo-terminal.png" style="width: 100%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Launching marimo with Coiled from a terminal.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To shut things down, you can either use the shutdown button in the notebook or hit Control-C in your terminal. Once the notebook exits, Coiled will automatically shut down the VM and clean up everything in the cloud.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="sync-files"&gt;
&lt;h2&gt;Sync Files&lt;/h2&gt;
&lt;p&gt;To make the experience even smoother, we can sync our local files with the VM where marimo is running with &lt;a class="reference external" href="https://docs.coiled.io/blog/coiled-notebooks.html#how-to-launch-a-notebook"&gt;the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--sync&lt;/span&gt;&lt;/code&gt; flag&lt;/a&gt;. Coiled will sync files in the current directory with the directory where &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; is running on the VM. That way, the files you edit on the cluster will still be available locally when you shut down the VM. So here’s how we’d sync our local files:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--sync&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--container&lt;span class="w"&gt; &lt;/span&gt;astral/uv:debian-slim&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;marimo&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--headless&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--proxy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;COILED_CLUSTER_HOSTNAME:2718&lt;span class="w"&gt; &lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="install-packages-automatically"&gt;
&lt;h2&gt;Install Packages Automatically&lt;/h2&gt;
&lt;p&gt;We could use Coiled’s support for &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/sync.html"&gt;automatic package synchronization&lt;/a&gt; to install our local Python packages onto the cloud VM—it’s very convenient if you have a local-only package or an editable install&lt;a class="footnote-reference brackets" href="#package-sync-local" id="id1" role="doc-noteref"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;1&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/a&gt;—but it’s not necessary here. Marimo automatically detects the packages required and offers to install them. Because &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt;&lt;/code&gt; is so fast, it’s fine to install them on the fly. (If you’re looking for those &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt;&lt;/code&gt; progress bars, they’ll be in your terminal, not in the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; UI.)&lt;/p&gt;
&lt;/section&gt;
&lt;section id="run-marimo-on-a-vm-with-a-gpu"&gt;
&lt;h2&gt;Run marimo on a VM With a GPU&lt;/h2&gt;
&lt;p&gt;Coiled lets you pick &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/size-and-type.html"&gt;any VM type&lt;/a&gt; your cloud provider offers, which means it’s easy to run &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; on a VM with a GPU. You can specify a VM type by name or specify &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--gpu&lt;/span&gt;&lt;/code&gt; to get the reasonable default machine with a GPU (&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;g4dn.xlarge&lt;/span&gt;&lt;/code&gt; for AWS right now). Coiled (and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt;&lt;/code&gt; in this case) will install the appropriate drivers and libraries to match your hardware.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--sync&lt;span class="w"&gt; &lt;/span&gt;--gpu&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--container&lt;span class="w"&gt; &lt;/span&gt;astral/uv:debian-slim&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;marimo&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--headless&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--proxy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;COILED_CLUSTER_HOSTNAME:2718
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default" id="marimo-browser-gpu"&gt;
&lt;img alt="Screenshot of a marimo notebook showing that GPU ID 0 is a Tesla T4." src="https://docs.coiled.io/_images/marimo-browser-gpu.png" style="width: 100%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Launching with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--gpu&lt;/span&gt;&lt;/code&gt; option gives you a VM with CUDA-compatible GPU.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="wrapping-up"&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;We used &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;, as the replacement for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;, to launch &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;marimo&lt;/span&gt;&lt;/code&gt; in the cloud using a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt;&lt;/code&gt; container. Coiled’s capabilities make the cloud feel like an extension of our local development environment. Combining &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt; with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt;&lt;/code&gt;, is a powerful pattern to execute any Python application on the cloud, like &lt;a class="reference external" href="https://github.com/bentoml/OpenLLM"&gt;OpenLLM&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;hr class="footnotes docutils" /&gt;
&lt;aside class="footnote-list brackets"&gt;
&lt;aside class="footnote brackets" id="package-sync-local" role="doc-footnote"&gt;
&lt;span class="label"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;&lt;a role="doc-backlink" href="#id1"&gt;1&lt;/a&gt;&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;In that case, you’d install marimo in your local environment and skip uv and its container:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--sync&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;marimo&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2718&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--headless&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--proxy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;COILED_CLUSTER_HOSTNAME:2718
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/aside&gt;
&lt;/aside&gt;
</content>
    <link href="https://docs.coiled.io/blog/marimo-on-coiled.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Aug 21, 2025</summary>
    <published>2025-08-21T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/is-aws-batch-right-for-data-professionals.html</id>
    <title>Is AWS Batch right for data professionals?</title>
    <updated>2025-08-19T00:00:00+00:00</updated>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Aug 19, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;8 min read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="is-aws-batch-right-for-data-professionals"&gt;

&lt;p&gt;AWS Batch is a powerful service for batch computing workloads in the cloud. It offers sophisticated job scheduling, automatic resource provisioning, and seamless scaling capabilities that make it an excellent choice for organizations with dedicated cloud infrastructure teams.&lt;/p&gt;
&lt;p&gt;However, if you’re a data scientist, quantitative analyst, or research scientist who wants to focus on analysis rather than infrastructure management, you might find AWS Batch requires more cloud operations expertise than you’d prefer to develop and maintain.&lt;/p&gt;
&lt;p&gt;AWS Batch excels when you have experienced DevOps professionals managing your cloud infrastructure. But for data professionals who want to spend their time on models, analysis, and insights rather than VPC configurations and IAM policies, the operational overhead can be substantial. Let’s explore what this means in practice.&lt;/p&gt;
&lt;section id="navigating-infrastructure-setup-and-configuration"&gt;
&lt;h2&gt;Navigating infrastructure setup and configuration&lt;/h2&gt;
&lt;p&gt;AWS Batch provides comprehensive control over your compute infrastructure, which is excellent for organizations with dedicated cloud teams. However, even with &lt;a class="reference external" href="https://aws.amazon.com/fargate/"&gt;Fargate’s managed infrastructure&lt;/a&gt;, setting up your first job involves several AWS services that may be unfamiliar to data professionals:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Creating and configuring VPCs and subnets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up security groups and network access controls&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Defining IAM roles and policies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configuring compute environments, job queues, and job definitions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Specifying detailed resource requirements and networking parameters&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For data scientists accustomed to running analyses locally, this infrastructure-first approach represents a significant shift in workflow. While this level of control enables powerful use cases, it can feel like a steep learning curve when you just want to parallelize a data processing task.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="understanding-networking-requirements"&gt;
&lt;h2&gt;Understanding networking requirements&lt;/h2&gt;
&lt;p&gt;AWS Batch’s networking model is designed for strict security requirements, but this can create unexpected challenges for data professionals. A common example: jobs may fail with timeout errors like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;failed&lt;/span&gt; &lt;span class="pre"&gt;to&lt;/span&gt; &lt;span class="pre"&gt;resolve&lt;/span&gt; &lt;span class="pre"&gt;ref&lt;/span&gt; &lt;span class="pre"&gt;docker.io/library/amazonlinux:latest:&lt;/span&gt; &lt;span class="pre"&gt;dial&lt;/span&gt; &lt;span class="pre"&gt;tcp&lt;/span&gt; &lt;span class="pre"&gt;44.208.254.194:443:&lt;/span&gt; &lt;span class="pre"&gt;i/o&lt;/span&gt; &lt;span class="pre"&gt;timeout&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The root cause is often a networking configuration detail: Fargate tasks require &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;quot;assignPublicIp&amp;quot;:&amp;quot;ENABLED&amp;quot;&lt;/span&gt;&lt;/code&gt; to access the internet for pulling Docker images. While this security-first approach makes sense in enterprise environments, it can be surprising for data scientists who expect their containers to “just work” like they do in local Docker environments. Additionally, fixing such configuration issues often requires recreating multiple AWS resources, making iteration slower than many data workflows expect.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="managing-complex-iam-permissions"&gt;
&lt;h2&gt;Managing complex IAM permissions&lt;/h2&gt;
&lt;p&gt;AWS Batch implements security through detailed IAM permissions, which provides excellent control for organizations with security teams. However, this creates a complex permission model where different components (compute environments, job definitions, and jobs) each require specific IAM configurations. For example:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Reading data from S3 requires configuring task roles, trust policies, and specific permissions like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;AmazonS3ReadOnlyAccess&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using GPU instances requires switching from Fargate to EC2, which introduces additional IAM requirements like instance profiles and ECS instance roles&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For data professionals accustomed to file-based permissions or simpler cloud services, like AWS Lambda, this granular security model can feel like learning a specialized domain that’s separate from their core data expertise.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="debugging-across-multiple-aws-services"&gt;
&lt;h2&gt;Debugging across multiple AWS services&lt;/h2&gt;
&lt;p&gt;AWS Batch’s distributed architecture means debugging information is spread across multiple AWS services, each with their own logging interfaces:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;CloudWatch Logs for application output&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;EC2 system logs for infrastructure issues&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Container logs for runtime problems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS Batch service logs for job orchestration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s no centralized, easy-access log aggregation. This distributed logging model aligns with AWS’s microservices architecture and provides comprehensive observability for DevOps teams familiar with AWS tooling. However, for data professionals accustomed to seeing all their program output in one terminal or notebook, this can feel fragmented. Job failures at different stages (provisioning, container startup, or execution) require checking different log sources, which can slow down the iterative debugging process that’s common in data work.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="scaling-and-gpu-considerations"&gt;
&lt;h2&gt;Scaling and GPU considerations&lt;/h2&gt;
&lt;p&gt;AWS Batch’s scaling behavior and GPU support are optimized for large-scale, production batch workloads, but may not align with typical data science workflows:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Cold-start provisioning for EC2-based environments can take 5-10 minutes or more, which is might be reasonable for long-running batch jobs but can feel slow during iterative development&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GPU workloads require EC2 compute environments rather than Fargate, necessitating additional infrastructure configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS service quotas (like GPU instance limits) can cause jobs to remain in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;RUNNABLE&lt;/span&gt;&lt;/code&gt; status without clear error messages, requiring familiarity with AWS quota management&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These characteristics work well for planned, production batch workloads but can create friction during the experimental, iterative phases common in data science projects.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="container-first-approach-vs-script-based-workflows"&gt;
&lt;h2&gt;Container-first approach vs. script-based workflows&lt;/h2&gt;
&lt;p&gt;AWS Batch follows a container-first approach. Rather than directly submitting scripts, you embed commands in job definitions or package them into Docker images. For more complex workflows, the typical pattern involves uploading scripts to S3 and configuring job definitions to retrieve and execute them.&lt;/p&gt;
&lt;p&gt;This containerized approach provides excellent reproducibility and environment isolation, which is valuable for production workloads. However, data professionals often prefer the immediacy of script-based workflows and being able to quickly modify a Python file and re-run it. The container-first model introduces additional steps (building images or managing S3 uploads) that can slow down the rapid iteration cycles common in exploratory data analysis.&lt;/p&gt;
&lt;p&gt;For data professionals operating without a dedicated cloud infrastructure or devops team, this can be a prohibitive bottleneck.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="devops-integration-requirements"&gt;
&lt;h2&gt;DevOps integration requirements&lt;/h2&gt;
&lt;p&gt;AWS Batch integrates well with AWS-native tools like CloudFormation and AWS CLI, making it a natural fit for teams already invested in the AWS ecosystem. However, integration with popular third-party tools can require additional effort:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Terraform support exists but may require custom scripting for complex configurations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automated deployment of job definitions works best with AWS-native CI/CD tools, though integration with other systems is possible with additional setup&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For organizations with established AWS DevOps teams, these integrations are manageable. However, for data teams using tools like Dagster, Prefect, GitHub Actions, or other non-AWS tooling may find the integration requires more DevOps expertise than they are able to maintain.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="when-aws-batch-works-well"&gt;
&lt;h2&gt;When AWS Batch works well&lt;/h2&gt;
&lt;p&gt;AWS Batch excels in the right organizational context. Teams find success when they have:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Dedicated DevOps or cloud infrastructure teams to handle setup and ongoing maintenance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Established AWS expertise and infrastructure-as-code practices&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Already fully embedded in the AWS ecosystem&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Long-running, production batch workloads that benefit from AWS’s configurability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Complex security and compliance requirements that benefit from fine-grained IAM controls&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Organizations with these characteristics often appreciate AWS Batch’s comprehensive feature set and deep AWS integration.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="alternatives-for-data-focused-teams"&gt;
&lt;h2&gt;Alternatives for data-focused teams&lt;/h2&gt;
&lt;p&gt;For data professionals who want to focus on analysis rather than infrastructure, several alternatives offer simpler approaches:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes jobs&lt;/strong&gt; provide more transparent control and ecosystem integration, though they still require container expertise&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Lambda&lt;/strong&gt; works well for lightweight, short-duration batch tasks, though with execution time and memory limitations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data-focused platforms like Coiled&lt;/strong&gt; are designed specifically for data science workflows, offering the scale of cloud computing with the simplicity of local development&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These alternatives prioritize different trade-offs. While AWS Batch optimizes for enterprise control and AWS integration, data-focused platforms like Coiled optimize for researcher productivity and rapid iteration. See our blog post on &lt;a class="reference internal" href="blog/choosing-an-aws-batch-alternative.html"&gt;&lt;span class="doc"&gt;Choosing an AWS Batch alternative&lt;/span&gt;&lt;/a&gt; for a detailed comparison.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="batch-jobs-with-coiled"&gt;
&lt;h2&gt;Batch jobs with Coiled&lt;/h2&gt;
&lt;p&gt;If you’re a data professional looking for cloud-scale computing without infrastructure complexity, Coiled offers a different approach. Designed specifically for data science workflows, Coiled automates the provisioning of EC2 instances in your AWS account, &lt;a class="reference internal" href="user_guide/software/sync.html"&gt;&lt;span class="doc"&gt;synchronizes your Python packages&lt;/span&gt;&lt;/a&gt;, and handles cleanup when you’re done. It bridges the gap between local development and cloud scale, letting you go from interactive notebook testing to distributed computing without building Docker containers or managing Kubernetes.&lt;/p&gt;
&lt;p&gt;Importantly, you don’t have to sacrifice security or production-ready scale with this approach. Coiled works equally well for iterative development and large-scale production jobs, meaning your entire team can use one tool throughout the development lifecycle. While researchers can “package sync away” during exploration and prototyping, the same platform seamlessly handles production workloads with Docker containers and enterprise security controls. This unified approach eliminates the friction of switching between development and production tools, allowing teams to move much faster from experimentation to deployment.&lt;/p&gt;
&lt;p&gt;With Coiled, you can:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Launch quickly&lt;/strong&gt;: Spin up VMs in minutes without configuring VPCs or IAM policies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale transparently&lt;/strong&gt;: Adjust resources automatically with clear feedback on provisioning status&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Save with Spot instances&lt;/strong&gt;: Use cost-effective Spot instances with automatic replacement handling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access GPUs simply&lt;/strong&gt;: Enable GPU acceleration without switching compute environments or managing quotas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debug intuitively&lt;/strong&gt;: View centralized logs and performance metrics in familiar interfaces&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To run a batch job, add &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#COILED&lt;/span&gt;&lt;/code&gt; comments to your script to specify the cloud resources you want:&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
Bash Script&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Spin up ten cloud VMs with 32 GB of memory to run their own &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;echo&lt;/span&gt;&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id1"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.sh&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED ntasks 10&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED memory 32GB&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED container ubuntu:latest&lt;/span&gt;
&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Hello&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$COILED_BATCH_TASK_ID&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then launch your script with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;my_script.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;COILED_BATCH_TASK_ID&lt;/span&gt;&lt;/code&gt; is an identifier unique to each task which runs from “0”, “1”, “2”, …, “9” in this case.&lt;/p&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
Python Script&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#&lt;/span&gt; &lt;span class="pre"&gt;COILED&lt;/span&gt;&lt;/code&gt; comments directly in Python scripts. Drop the container directive to use &lt;a class="reference internal" href="user_guide/software/sync.html"&gt;&lt;span class="doc"&gt;package sync&lt;/span&gt;&lt;/a&gt; to automatically replicate your Python environment on remote VMs.&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id2"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.py&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# COILED n-tasks     10&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# COILED memory      8 GiB&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# COILED region      us-east-2&lt;/span&gt;
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;COILED_BATCH_TASK_ID&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;and then launch your script with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;my_script.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It’s easy to &lt;a class="reference internal" href="user_guide/setup/index.html"&gt;&lt;span class="doc std std-doc"&gt;get started&lt;/span&gt;&lt;/a&gt; with Coiled:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;quickstart
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="faqs"&gt;
&lt;h2&gt;FAQs&lt;/h2&gt;
&lt;section id="what-is-aws-batch-used-for"&gt;
&lt;h3&gt;What is AWS Batch used for?&lt;/h3&gt;
&lt;p&gt;AWS Batch is a managed service for running batch computing workloads in the cloud. It automatically provisions compute resources, queues jobs, and scales up or down based on demand. It’s often used for data processing, scientific computing, ML training, and other large-scale, parallelizable workloads.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="why-might-aws-batch-feel-complex-for-data-professionals"&gt;
&lt;h3&gt;Why might AWS Batch feel complex for data professionals?&lt;/h3&gt;
&lt;p&gt;AWS Batch is designed as an enterprise-grade service that requires configuring multiple AWS components (VPCs, subnets, security groups, IAM roles, and job definitions) before running jobs. This comprehensive approach provides control and security, but can feel like a significant learning investment for data professionals who want to focus on their analysis rather than infrastructure management.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="does-aws-batch-support-gpus"&gt;
&lt;h3&gt;Does AWS Batch support GPUs?&lt;/h3&gt;
&lt;p&gt;Yes, AWS Batch supports GPUs through EC2 compute environments. GPU support requires using EC2 rather than Fargate, which involves additional configuration of IAM roles, instance profiles, and ensuring adequate GPU service quotas are in place.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-do-i-submit-scripts-to-aws-batch"&gt;
&lt;h3&gt;How do I submit scripts to AWS Batch?&lt;/h3&gt;
&lt;p&gt;AWS Batch does not natively accept standalone scripts. Commands must be embedded in job definitions as JSON or included in container images. For multi-line scripts, the typical workflow is to upload the script to S3, create a job role with S3 read access, and configure your job to pull and run the script.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-are-common-aws-batch-considerations-for-data-teams"&gt;
&lt;h3&gt;What are common AWS Batch considerations for data teams?&lt;/h3&gt;
&lt;p&gt;Data professionals often encounter these learning areas when adopting AWS Batch:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Networking configurations that affect container internet access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;IAM permission models that differ from file-based systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS service quotas that can affect job scheduling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cold-start provisioning times that may impact iterative workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Distributed logging across multiple AWS services&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="what-are-alternatives-to-aws-batch"&gt;
&lt;h3&gt;What are alternatives to AWS Batch?&lt;/h3&gt;
&lt;p&gt;Different alternatives optimize for different use cases:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes jobs&lt;/strong&gt; offer more transparent control and ecosystem integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Lambda or ECS&lt;/strong&gt; provide simpler container-based execution models&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data-focused platforms like Coiled&lt;/strong&gt; specialize in Python/data science workflows, emphasizing ease of use and rapid iteration over enterprise infrastructure controls&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/is-aws-batch-right-for-data-professionals.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Aug 19, 2025</summary>
    <published>2025-08-19T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/choosing-an-aws-batch-alternative.html</id>
    <title>Choosing an AWS Batch alternative</title>
    <updated>2025-08-05T00:00:00+00:00</updated>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Aug 05, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;6 min read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="choosing-an-aws-batch-alternative"&gt;

&lt;p&gt;AWS Batch is powerful, but not always the right fit.&lt;/p&gt;
&lt;p&gt;AWS Batch is great when you need a fully managed job orchestration system for running containerized workloads at scale. It shines in production environments where DevOps teams are already managing infrastructure, and where strict job queueing are critical to large-scale batch pipelines.&lt;/p&gt;
&lt;p&gt;But, for many data practitioners, it’s overkill. Many people come across AWS Batch when they’ve hit the limits of AWS Lambda and quickly find it’s not nearly as user-friendly. Others are looking for an easy way to run their script on the cloud, but then got stuck configuring job queues and troubleshooting opaque errors in CloudWatch. Then, once you’ve figured out the error, it can take several minutes to rebuild Docker images and try again.&lt;/p&gt;
&lt;p&gt;In this post, we’ll cover some of the challenges of AWS Batch and provide some alternatives.&lt;/p&gt;
&lt;section id="what-makes-aws-batch-challenging"&gt;
&lt;h2&gt;What makes AWS Batch challenging?&lt;/h2&gt;
&lt;p&gt;AWS Batch is powerful, but difficult to use for a number of reasons, including:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Complex setup: You need job definitions, compute environments, and container specs, even for simple jobs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Opaque Debugging: When jobs fail, logs are spread across CloudWatch with minimal helpful context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hard to use Spot: AWS Batch can get expensive quickly, and to save on costs &lt;a class="reference external" href="https://aws.amazon.com/blogs/hpc/aws-batch-best-practices/"&gt;Amazon recommends using EC2 Spot&lt;/a&gt;. This requires additional management to do well, though, including picking the right allocation strategy, configuring subnets for multiple availability zones, making sure your job isn’t running for too long, and ensuring you have automated retries set up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quota limits: One of the more common &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;job&lt;/span&gt; &lt;span class="pre"&gt;stuck&lt;/span&gt; &lt;span class="pre"&gt;in&lt;/span&gt; &lt;span class="pre"&gt;RUNNABLE&lt;/span&gt; &lt;span class="pre"&gt;status&lt;/span&gt;&lt;/code&gt; culprits is if you don’t have the right quota for the resources you want to use (this could be GPUs or Spot or even an instance type with more memory).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the sections below we’ll go into getting started with AWS Batch on Fargate and on EC2.&lt;/p&gt;
&lt;section id="aws-batch-with-fargate"&gt;
&lt;h3&gt;AWS Batch with Fargate&lt;/h3&gt;
&lt;p&gt;You’ll first need to decide whether to use AWS Batch with Fargate or EC2. Amazon claims Fargate is their &lt;a class="reference external" href="https://aws.amazon.com/blogs/aws/new-fully-serverless-batch-computing-with-aws-batch-support-for-aws-fargate/"&gt;easier, serverless option&lt;/a&gt;. There is still a lot of setup required, though, including:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Find default VPC, subnets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identify security groups&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create required IAM roles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a Fargate compute environment with public subnets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a job queue linked to your compute environment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Register a job definition&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All before you can even submit your job. Once you’re ready to actually submit your job, you can’t actually submit a script. Instead, you’ll need to do one of the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Put your script as a JSON argument to Batch (this gets pretty awkward if your script is more than a couple lines)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build it into the Docker image (OK for production, but slow for development)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upload your script to S3 (and create and attach the correct IAM roles and access)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which is a lot of overhead, especially if all you want is to run a script on the cloud. After all that, you also can’t use Fargate with GPUs, so you might instead try EC2.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="aws-batch-with-ec2-for-gpu-access"&gt;
&lt;h3&gt;AWS Batch with EC2 for GPU access&lt;/h3&gt;
&lt;p&gt;If you want to use AWS for GPUs, you’ll need to use EC2. That means you’ll first have to go through the EC2 setup, which includes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Finding (or creating) your VPC, with subnets and security groups&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using (or creating) VPC infrastructure (you may need to create this if you want to use a different region from the default)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating the EC2 compute environment with your GPU instances&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, you’ll be able to start setting up AWS Batch:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Create Batch EC2 compute environment with GPU instances&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create GPU job queue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Register GPU job definition (with your image, GPU resource requirement, and any additional memory your job will need)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After that, you’ll be able to try to run your script.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="what-to-look-for-in-an-aws-batch-alternative"&gt;
&lt;h2&gt;What to look for in an AWS Batch alternative?&lt;/h2&gt;
&lt;p&gt;All of that setup can be a lot of overhead if all you need to do is run a script on the cloud. AWS Fargate and AWS Lambda are both easier to use compared to AWS Batch, but come with their own tradeoffs like not being able to use GPUs and container image size limits.&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table" id="batch-vs-lambda-vs-fargate-vs-coiled"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head"&gt;&lt;p&gt;Feature&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;AWS Batch&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;AWS Fargate&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;AWS Lambda&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Coiled&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Max execution time&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;15 minutes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Setup complexity&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;High&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Medium&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Low&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Low&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Container image size limit&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit (w/ EC2)&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;20GB (200GB w/ extra config)&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;10GB&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Containerization required&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No (&lt;a class="reference internal" href="user_guide/software/sync.html"&gt;&lt;span class="doc"&gt;package sync&lt;/span&gt;&lt;/a&gt; or Docker)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Built-in parallelism&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes (Dask, Coiled Batch, Coiled Functions)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Interactive development&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Spot instances&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes (requires extra config)&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;GPU support&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes (w/ EC2)&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Scale-to-zero&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Coiled is an easier alternative to these AWS tools, making it easy to go from your laptop to the cloud.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="coiled-as-an-aws-batch-alternative-an-easy-way-to-scale-scripts-on-the-cloud"&gt;
&lt;h2&gt;Coiled as an AWS Batch alternative: An easy way to scale scripts on the cloud&lt;/h2&gt;
&lt;p&gt;Coiled is an easier alternative to running your workflows on the cloud. It automates the provisioning of EC2 instances in your AWS account, automatically &lt;a class="reference internal" href="user_guide/software/sync.html"&gt;&lt;span class="doc"&gt;synchronizes your Python packages&lt;/span&gt;&lt;/a&gt;, and turns things off when you’re done. It’s easy to go from testing interactively in a notebook to scaling out to cluster of VMs, without needing to build Docker containers or manage Kubernetes. You can easily take advantage of heavily discounted Spot instances (most &lt;a class="reference internal" href="blog/ten-cents.html"&gt;&lt;span class="doc"&gt;computations end up costing ~$0.10 / TB&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;It makes everything so much easier. We can always use the fastest machines. We don’t have to worry about infrastructure. In production we use Docker, but for the researchers we let them just package sync away and not worry about it. It’s really easy to parallelize things that might have previously taken two or three days.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;a class="reference external" href="https://coiled.io/customers/double-river"&gt;Nelson Griffiths&lt;/a&gt;, Head of Engineering at Double River Investments&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are a few ways to use Coiled, but Coiled Batch is the most natural AWS Batch alternative.&lt;/p&gt;
&lt;section id="coiled-batch-for-single-jobs-to-large-scale-parallelism"&gt;
&lt;h3&gt;Coiled Batch for single jobs to large-scale parallelism&lt;/h3&gt;
&lt;p&gt;Coiled Batch is an easy way to run any script on the cloud and is especially useful for scaling large, independent tasks (ie, embarrassingly parallel workloads) across hundreds of VMs. Unlike other Coiled APIs, it doesn’t rely on Dask for parallelism, which means you can run any code (not just Python).&lt;/p&gt;
&lt;p&gt;You can use Coiled Batch to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Run an arbitrary bash script 100 times in parallel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/batch-gdal.html"&gt;&lt;span class="doc"&gt;Reproject thousands of satellite images with GDAL for $1&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/gpu-job.html"&gt;&lt;span class="doc"&gt;Train a PyTorch model on a GPU for $0.12&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://earthmover.io/blog/exploring-icechunk-scalability"&gt;Stress-testing S3 concurrency&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/distributed-training-accelerate.html"&gt;&lt;span class="doc"&gt;Multi-node GPU LLM fine-tuning&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To run a batch job, add &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#COILED&lt;/span&gt;&lt;/code&gt; comments to your script to specify the cloud resources you want:&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
Bash Script&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Spin up ten cloud VMs with 32 GB of memory to run their own &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;echo&lt;/span&gt;&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id1"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.sh&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED ntasks 10&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED memory 32GB&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED container ubuntu:latest&lt;/span&gt;
&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Hello&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$COILED_BATCH_TASK_ID&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then launch your script with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;my_script.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;COILED_BATCH_TASK_ID&lt;/span&gt;&lt;/code&gt; is an identifier unique to each task which runs from “0”, “1”, “2”, …, “9” in this case.&lt;/p&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
Python Script&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#COILED&lt;/span&gt;&lt;/code&gt; comments directly in Python scripts. Drop the container directive to use &lt;a class="reference internal" href="user_guide/software/sync.html"&gt;&lt;span class="doc"&gt;package sync&lt;/span&gt;&lt;/a&gt; to automatically replicate your Python environment on remote VMs.&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id2"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.py&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED n-tasks     10&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED memory      8 GiB&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED region      us-east-2&lt;/span&gt;
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;COILED_BATCH_TASK_ID&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;and then launch your script with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;my_script.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Under the hood Coiled will:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Inspect your script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spin up appropriate machines as defined by &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#COILED&lt;/span&gt;&lt;/code&gt; comments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Download your software onto them or use the container you specified&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run your script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shut down the machines&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;It’s easy to &lt;a class="reference internal" href="user_guide/setup/index.html"&gt;&lt;span class="doc std std-doc"&gt;get started&lt;/span&gt;&lt;/a&gt; with Coiled:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;quickstart
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Learn more:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/long-running-lambda-challenges-alternatives.html"&gt;&lt;span class="doc std std-doc"&gt;Alternatives to AWS Lambda for long-running jobs&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/save-money-with-spot.html"&gt;&lt;span class="doc std std-doc"&gt;Save money with Spot&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/burstable-vs-nonburstable.html"&gt;&lt;span class="doc std std-doc"&gt;Cost comparison of burstable vs. non-burstable instances&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/choosing-an-aws-batch-alternative.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Aug 05, 2025</summary>
    <published>2025-08-05T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/long-running-lambda-challenges-alternatives.html</id>
    <title>Long-running Lambda workloads: Challenges and alternatives</title>
    <updated>2025-07-16T00:00:00+00:00</updated>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jul 16, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;6 min read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="long-running-lambda-workloads-challenges-and-alternatives"&gt;

&lt;p&gt;AWS Lambda excels at handling short-lived, event-driven tasks, though it quickly runs into limits when your workload needs more than 15 minutes, more memory, or specialized hardware like GPUs. For data practitioners working with large datasets or long-running processes, these constraints can turn a powerful tool into a frustrating bottleneck. In this article we’ll explore:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Common AWS tools for running long-duration tasks such as Step Functions, AWS Batch, and running containerized workflows with Fargate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How alternative options, like &lt;a class="reference external" href="https://coiled.io/"&gt;Coiled&lt;/a&gt;, offer a simpler, more scalable option for running Python in the cloud without the overhead of maintaining cloud infrastructure or managing K8s.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="the-challenge-of-long-running-lambda-tasks"&gt;
&lt;h2&gt;The challenge of long-running Lambda tasks&lt;/h2&gt;
&lt;p&gt;AWS Lambda is designed for short-lived, event-driven functions. And for these for small, event-driven tasks it works really well. This includes things like responding to API Gateway requests, parsing log files, and other short-running Python scripts. Because it’s so lightweight and easier to use than other tools in the AWS ecosystem, it’s not uncommon for data practitioners to find themselves in an anti-pattern where they begin to use AWS Lambda for jobs it wasn’t really designed for. This includes things like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Training long-running machine learning models&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ETL pipelines that churn through large volumes of data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simulations and heavy computations that demand memory, CPU, and time&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any other jobs that exceed the 15-minute timeout, when you’ve likely hit the AWS Lambda &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Task&lt;/span&gt; &lt;span class="pre"&gt;timed&lt;/span&gt; &lt;span class="pre"&gt;out&lt;/span&gt; &lt;span class="pre"&gt;after&lt;/span&gt; &lt;span class="pre"&gt;900&lt;/span&gt; &lt;span class="pre"&gt;seconds&lt;/span&gt;&lt;/code&gt; error message&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, it’s probably time to use something else.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="better-alternatives-to-aws-lambda-for-long-running-tasks"&gt;
&lt;h2&gt;Better alternatives to AWS Lambda for long-running tasks&lt;/h2&gt;
&lt;p&gt;In this post, we’ll go through a number of other options for long-running tasks on AWS:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/long-running-lambda-challenges-alternatives.html#aws-fargate"&gt;&lt;span class="std std-ref"&gt;AWS Fargate&lt;/span&gt;&lt;/a&gt; for running containerized workloads&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/long-running-lambda-challenges-alternatives.html#aws-step-functions"&gt;&lt;span class="std std-ref"&gt;AWS Step Functions&lt;/span&gt;&lt;/a&gt; for chaining together steps that involve a mix of short- and long-running processes (including retry logic, branching, waiting).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/long-running-lambda-challenges-alternatives.html#aws-batch"&gt;&lt;span class="std std-ref"&gt;AWS Batch&lt;/span&gt;&lt;/a&gt; for long-running, compute-heavy batch jobs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/long-running-lambda-challenges-alternatives.html#coiled-aws-alternative"&gt;&lt;span class="std std-ref"&gt;Coiled&lt;/span&gt;&lt;/a&gt; as an alternative to any of the above AWS services for easily running Python (or any language, really) on the cloud&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;section id="fargate-for-serverless-containers"&gt;
&lt;span id="aws-fargate"&gt;&lt;/span&gt;&lt;h3&gt;Fargate for serverless containers&lt;/h3&gt;
&lt;p&gt;Fargate lets you run containers on demand. Similar to AWS Lambda, Fargate abstracts away infrastructure management. You define the CPU, memory, and runtime, and AWS spins up the infrastructure. It’s great for when you’ve already containerized your workloads and they need more than 15 minutes to complete.&lt;/p&gt;
&lt;p&gt;Pros:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;No servers to manage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supports tasks of arbitrary length&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integrates well with ECS, EKS, and AWS Batch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Option to scale up/down&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cons:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Slightly more setup than Lambda&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cold starts can still be an issue for short tasks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you’re not already using Docker, this can add some additional complexity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No GPU support (you’d need to use EC2)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, let’s say a data team needs to process a large hourly batch of raw JSON logs, apply transformation logic, and write to a data lake. The entire workflow runs in ~40 minutes. Instead of trying to chunk the job into multiple Lambdas, they could use Fargate to run a single container.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="step-functions-for-workflow-orchestration"&gt;
&lt;span id="aws-step-functions"&gt;&lt;/span&gt;&lt;h3&gt;Step Functions for workflow orchestration&lt;/h3&gt;
&lt;p&gt;Step Functions let you coordinate multiple AWS tools together when logic, retries, and branching are needed.&lt;/p&gt;
&lt;p&gt;Pros:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Visual state machine interface&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Built-in retries, waits, error handling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Works with both short and long steps&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cons:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Limited to AWS ecosystem&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Costs can climb for high-frequency workflows (pricing is based on state transitions)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, let’s say you have a data pipeline that extracts data from an API, cleans and joins multiple sources, runs a forecasting model, and stores the results. You could use Step Functions to orchestrate this across Lambdas for light steps and Fargate tasks for heavy lifting.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="aws-batch-for-large-scale-compute-jobs"&gt;
&lt;span id="aws-batch"&gt;&lt;/span&gt;&lt;h3&gt;AWS Batch for large-scale compute jobs&lt;/h3&gt;
&lt;p&gt;AWS Batch is designed for submitting, scheduling, and executing a large volume of jobs. It’s often used for compute-heavy or long-running workloads like scientific simulations or model training. It’s especially useful for array jobs, or when you need to run a bunch of independent jobs in parallel with the same definition. It runs on top of Fargate or EC2. Unlike most other AWS services, you don’t pay an extra cost for using Batch.&lt;/p&gt;
&lt;p&gt;Pros:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Fully managed job scheduling and provisioning&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Spot to reduce cloud costs (but need to manually manage with retries)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Powerful tool for embarrassingly parallel workloads&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cons:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;More complex to set up than Lambda or Fargate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inefficient for low-latency tasks (partially due to scheduler overhead)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Long cold-starts for large container images&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A research group might use AWS Batch to run daily simulations that each take 3–5 hours. They can submit hundreds of jobs at once, automatically scaled across hundreds of VMs, to run these in parallel.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="using-coiled-instead-of-aws-services-for-long-running-lambda-workarounds"&gt;
&lt;span id="coiled-aws-alternative"&gt;&lt;/span&gt;&lt;h2&gt;Using Coiled instead of AWS services for long-running Lambda workarounds&lt;/h2&gt;
&lt;p&gt;While AWS tools like Fargate and Batch are powerful, they often introduce friction for data practitioners. You typically need to write Dockerfiles, manage IAM roles, configure queues, and set up infrastructure. For teams used to working in Jupyter notebooks or Python scripts, this overhead can be a major blocker.&lt;/p&gt;
&lt;p&gt;Coiled is an easier alternative to running Python on the cloud. It automates the provisioning of EC2 instances in your AWS account, &lt;a class="reference internal" href="user_guide/software/sync.html"&gt;&lt;span class="doc"&gt;automatically synchronizes your Python packages&lt;/span&gt;&lt;/a&gt;, and turns things off when you’re done. It’s easy to go from testing interactively in a notebook to scaling out to cluster of VMs, without needing to build Docker containers or manage Kubernetes.&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table" id="long-running-lambda-feature-comparison"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head"&gt;&lt;p&gt;Feature&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;AWS Lambda&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;AWS Fargate&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;AWS Batch&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Coiled&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Max execution time&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;15 minutes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Setup complexity&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Low&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Medium&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;High&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Low&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Container image size limit&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;10GB&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;20GB (200GB w/ extra config)&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit (w/ EC2)&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Containerization required&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No (&lt;a class="reference internal" href="user_guide/software/sync.html"&gt;&lt;span class="doc"&gt;package sync&lt;/span&gt;&lt;/a&gt; or Docker)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Built-in parallelism&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes (Dask, Coiled Batch, Coiled Functions)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Interactive development&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Spot instances&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;GPU support&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes (w/ EC2)&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Scale-to-zero&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;In the following sections we’ll go through some ways you can use Coiled as an alternative to other AWS services.&lt;/p&gt;
&lt;section id="coiled-serverless-python-functions"&gt;
&lt;h3&gt;Coiled serverless Python functions&lt;/h3&gt;
&lt;p&gt;If you have a Python function you’d like to run on the cloud, you can use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator. Behind the scenes, Coiled will spin up an EC2 instance in your AWS account, run your function, and then turn things off when you’re done.&lt;/p&gt;
&lt;p&gt;More details in &lt;a class="reference internal" href="user_guide/functions.html"&gt;&lt;span class="doc"&gt;Coiled Functions&lt;/span&gt;&lt;/a&gt; docs.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
Data processing&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# Run close to data&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Read S3 data from EC2&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Alice&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="c1"&gt;# Filter data on cloud&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;                       &lt;span class="c1"&gt;# Return subset&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://my-bucket/data.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Runs remotely&lt;/span&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                    &lt;span class="c1"&gt;# Runs locally&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
Model training&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;g6.2xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# Run on a GPU instance&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                    &lt;span class="c1"&gt;# Runs remotely&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-2" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-2"&gt;
Parallelism&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Run close to the data&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;output_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;.parquet&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output_filename&lt;/span&gt;

&lt;span class="c1"&gt;# result = process(&amp;quot;s3://my-bucket/data.parquet&amp;quot;)  # one file&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# many files in parallel&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Finished&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="coiled-batch-for-single-jobs-to-large-scale-parallelism"&gt;
&lt;h3&gt;Coiled Batch for single jobs to large-scale parallelism&lt;/h3&gt;
&lt;p&gt;Coiled Batch is an easy way to run any script on the cloud, and is especially useful for scaling large, independent tasks (ie, embarrassingly parallel workloads) across hundreds of VMs. You can use Coiled Batch to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Run an arbitrary bash script 100 times in parallel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/gpu-job.html"&gt;&lt;span class="doc"&gt;Train a PyTorch model on a single GPU&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/distributed-training-accelerate.html"&gt;&lt;span class="doc"&gt;Distributed model training across multiple VMs&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/batch-gdal.html"&gt;&lt;span class="doc"&gt;Reproject thousands of satellite images with GDAL&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To run a batch job, add &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#COILED&lt;/span&gt;&lt;/code&gt; comments to your script to specify the cloud resources you want:&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-3" name="sd-tab-set-1" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-3"&gt;
Bash Script&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Spin up ten cloud VMs with 32 GB of memory to run their own &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;echo&lt;/span&gt;&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id1"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.sh&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED ntasks 10&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED memory 32GB&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED container ubuntu:latest&lt;/span&gt;
&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Hello&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$COILED_BATCH_TASK_ID&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then launch your script with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;my_script.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;COILED_BATCH_TASK_ID&lt;/span&gt;&lt;/code&gt; is an identifier unique to each task which runs from “0”, “1”, “2”, …, “9” in this case.&lt;/p&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-4" name="sd-tab-set-1" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-4"&gt;
Python Script&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#COILED&lt;/span&gt;&lt;/code&gt; comments directly in Python scripts. Drop the container directive to use &lt;a class="reference internal" href="user_guide/software/sync.html"&gt;&lt;span class="doc"&gt;package sync&lt;/span&gt;&lt;/a&gt; to automatically replicate your Python environment on remote VMs.&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id2"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.py&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED n-tasks     10&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED memory      8 GiB&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED region      us-east-2&lt;/span&gt;
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;COILED_BATCH_TASK_ID&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;and then launch your script with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;my_script.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Under the hood Coiled will:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Inspect your script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spin up appropriate machines as defined by &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#COILED&lt;/span&gt;&lt;/code&gt; comments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Download your software onto them or use the container you specified&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run your script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shut down the machines&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;It’s easy to &lt;a class="reference internal" href="user_guide/setup/index.html"&gt;&lt;span class="doc std std-doc"&gt;get started&lt;/span&gt;&lt;/a&gt; with Coiled:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;quickstart
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Learn more:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/save-money-with-spot.html"&gt;&lt;span class="doc std std-doc"&gt;Save money with Spot&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/aws-lambda-gpu-alternative.html"&gt;&lt;span class="doc std std-doc"&gt;Alternatives to AWS Lambda for GPUs&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/burstable-vs-nonburstable.html"&gt;&lt;span class="doc std std-doc"&gt;Cost comparison of burstable vs. non-burstable instances&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/long-running-lambda-challenges-alternatives.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jul 16, 2025</summary>
    <published>2025-07-16T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/uv-coiled-cloud-scripts.html</id>
    <title>uv and Coiled for easy Python scripts on the cloud</title>
    <updated>2025-06-30T00:00:00+00:00</updated>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jun 30, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3 min read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="uv-and-coiled-for-easy-python-scripts-on-the-cloud"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;James Bourbeau&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The Python packaging world is (being generous) … vast. One of the more delightful recent developments is &lt;a class="reference external" href="https://docs.astral.sh/uv/"&gt;uv&lt;/a&gt;, a fast Python package and environment manager. uv has the features you’d expect from a Python package manager (project-specific environments, lock files, etc.), but the core reasons folks like uv so much is that it’s easy to use and really, really &lt;a class="reference external" href="https://github.com/astral-sh/uv/blob/main/BENCHMARKS.md"&gt;fast&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post shows how to run Python scripts on the cloud using uv and &lt;a class="reference external" href="https://coiled.io/"&gt;Coiled&lt;/a&gt;, an easy-to-use, UX-focused cloud platform that provides a similarly straightforward approach to running Python scripts on any cloud hardware.&lt;/p&gt;
&lt;section id="uv-for-managing-software"&gt;
&lt;h2&gt;uv for managing software&lt;/h2&gt;
&lt;p&gt;Here’s a script that uses &lt;a class="reference external" href="https://pandas.pydata.org/docs/"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pandas&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; to load a Parquet data file hosted in a public bucket on S3 and print the first few rows:&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id1"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process.py&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-data/uber/part.0.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’ll use this concrete example throughout the rest of the post, but know that the script contents could be &lt;em&gt;any&lt;/em&gt; Python code.&lt;/p&gt;
&lt;p&gt;Running this script requires &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pandas&lt;/span&gt;&lt;/code&gt;, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pyarrow&lt;/span&gt;&lt;/code&gt;, and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3fs&lt;/span&gt;&lt;/code&gt;. uv has a pretty slick way for embedding these dependencies directly in the script using &lt;a class="reference external" href="https://peps.python.org/pep-0723/"&gt;PEP 723&lt;/a&gt; script metadata with the &lt;a class="reference external" href="https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt; &lt;span class="pre"&gt;add&lt;/span&gt; &lt;span class="pre"&gt;--script&lt;/span&gt;&lt;/code&gt; command&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--script&lt;span class="w"&gt; &lt;/span&gt;process.py&lt;span class="w"&gt; &lt;/span&gt;pandas&lt;span class="w"&gt; &lt;/span&gt;pyarrow&lt;span class="w"&gt; &lt;/span&gt;s3fs
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;which adds these comments with the specified dependencies to the script&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id2"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process.py&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# /// script&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# requires-python = &amp;quot;&amp;gt;=3.12&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# dependencies = [&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#   &amp;quot;pandas&amp;quot;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#   &amp;quot;pyarrow&amp;quot;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#   &amp;quot;s3fs&amp;quot;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# ]&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# ///&lt;/span&gt;
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-data/uber/part.0.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We then use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt; to run the script&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;process.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;uv automatically creates a virtual environment, installs the dependencies, and then runs  &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process.py&lt;/span&gt;&lt;/code&gt; in that environment.
Here’s the output we get:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;hvfhs_license_num&lt;/span&gt; &lt;span class="n"&gt;dispatching_base_num&lt;/span&gt; &lt;span class="n"&gt;originating_base_num&lt;/span&gt;    &lt;span class="n"&gt;request_datetime&lt;/span&gt;   &lt;span class="n"&gt;on_scene_datetime&lt;/span&gt;  &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;shared_request_flag&lt;/span&gt; &lt;span class="n"&gt;shared_match_flag&lt;/span&gt;  &lt;span class="n"&gt;access_a_ride_flag&lt;/span&gt;  &lt;span class="n"&gt;wav_request_flag&lt;/span&gt;  &lt;span class="n"&gt;wav_match_flag&lt;/span&gt;
&lt;span class="n"&gt;__null_dask_index__&lt;/span&gt;                                                                                                      &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="mi"&gt;18979859&lt;/span&gt;                       &lt;span class="n"&gt;HV0003&lt;/span&gt;               &lt;span class="n"&gt;B02875&lt;/span&gt;               &lt;span class="n"&gt;B02875&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;  &lt;span class="o"&gt;...&lt;/span&gt;                   &lt;span class="n"&gt;N&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;                 &lt;span class="n"&gt;NaN&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;             &lt;span class="n"&gt;NaN&lt;/span&gt;
&lt;span class="mi"&gt;18979860&lt;/span&gt;                       &lt;span class="n"&gt;HV0003&lt;/span&gt;               &lt;span class="n"&gt;B02875&lt;/span&gt;               &lt;span class="n"&gt;B02875&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;  &lt;span class="o"&gt;...&lt;/span&gt;                   &lt;span class="n"&gt;N&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;                 &lt;span class="n"&gt;NaN&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;             &lt;span class="n"&gt;NaN&lt;/span&gt;
&lt;span class="mi"&gt;18979861&lt;/span&gt;                       &lt;span class="n"&gt;HV0003&lt;/span&gt;               &lt;span class="n"&gt;B02765&lt;/span&gt;               &lt;span class="n"&gt;B02765&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;  &lt;span class="o"&gt;...&lt;/span&gt;                   &lt;span class="n"&gt;N&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;                 &lt;span class="n"&gt;NaN&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;             &lt;span class="n"&gt;NaN&lt;/span&gt;
&lt;span class="mi"&gt;18979862&lt;/span&gt;                       &lt;span class="n"&gt;HV0003&lt;/span&gt;               &lt;span class="n"&gt;B02682&lt;/span&gt;               &lt;span class="n"&gt;B02682&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;  &lt;span class="o"&gt;...&lt;/span&gt;                   &lt;span class="n"&gt;Y&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;                 &lt;span class="n"&gt;NaN&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;             &lt;span class="n"&gt;NaN&lt;/span&gt;
&lt;span class="mi"&gt;18979863&lt;/span&gt;                       &lt;span class="n"&gt;HV0002&lt;/span&gt;               &lt;span class="n"&gt;B03035&lt;/span&gt;               &lt;span class="n"&gt;B03035&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt; &lt;span class="mi"&gt;1970&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;  &lt;span class="o"&gt;...&lt;/span&gt;                   &lt;span class="n"&gt;N&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;                   &lt;span class="n"&gt;N&lt;/span&gt;                 &lt;span class="n"&gt;N&lt;/span&gt;             &lt;span class="n"&gt;NaN&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;What’s nice about this is we didn’t have to think about managing local virtual environments ourselves and the dependencies needed are included directly in the script which makes things nicely self-contained.&lt;/p&gt;
&lt;p&gt;That’s really all we need for running scripts locally. However, there are times when we need resources beyond what’s available on our local workstation, like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Processing large amounts of cloud-hosted data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Needing accelerated hardware like GPUs or a big machine with more memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running the same script with hundreds or thousands of different inputs, in parallel&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In these situations running scripts directly on cloud hardware is often a good solution.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="coiled-for-managing-hardware"&gt;
&lt;h2&gt;Coiled for managing hardware&lt;/h2&gt;
&lt;p&gt;Similar to how uv makes it super straightforward to handle Python dependency management, Coiled makes it easy to handle running code on cloud hardware (if you’re not already familiar with how unergonomic cloud APIs can be, see &lt;a class="reference external" href="https://docs.astral.sh/uv/guides/integration/aws-lambda/"&gt;this example&lt;/a&gt; on using uv with AWS Lambda).&lt;/p&gt;
&lt;p&gt;Let’s start by installing the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt;&lt;/code&gt; Python package and authenticating whatever machine you’re running on with Coiled using the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;login&lt;/span&gt;&lt;/code&gt; CLI (you’ll be prompted to create a Coiled account if you don’t already have one – and it’s totally free to start using Coiled)&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;login
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To have Coiled run our script on a VM on AWS, we’ll add these two comments:&lt;/p&gt;
&lt;div class="literal-block-wrapper docutils container" id="id3"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process.py&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# COILED container ghcr.io/astral-sh/uv:debian-slim&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;# COILED region us-east-2&lt;/span&gt;
&lt;/span&gt;
&lt;span class="c1"&gt;# /// script&lt;/span&gt;
&lt;span class="c1"&gt;# requires-python = &amp;quot;&amp;gt;=3.12&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# dependencies = [&lt;/span&gt;
&lt;span class="c1"&gt;#   &amp;quot;pandas&amp;quot;,&lt;/span&gt;
&lt;span class="c1"&gt;#   &amp;quot;pyarrow&amp;quot;,&lt;/span&gt;
&lt;span class="c1"&gt;#   &amp;quot;s3fs&amp;quot;,&lt;/span&gt;
&lt;span class="c1"&gt;# ]&lt;/span&gt;
&lt;span class="c1"&gt;# ///&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-data/uber/part.0.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;They tell Coiled to use the official &lt;a class="reference external" href="https://github.com/astral-sh/uv/pkgs/container/uv"&gt;uv Docker image&lt;/a&gt; when running our script (this makes sure uv is installed) and to run the script in the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;us-east-2&lt;/span&gt;&lt;/code&gt; region on AWS (where this data file happens to live) to avoid any data egress. There are many other options we could have specified here like VM instance type (the default it an &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.xlarge&lt;/span&gt;&lt;/code&gt; instance on AWS), whether to use spot instance, etc. See the &lt;a class="reference internal" href="user_guide/batch.html#coiled-batch-run"&gt;&lt;span class="std std-ref"&gt;Coiled Batch docs&lt;/span&gt;&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;Finally we use the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/batch.html"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt; CLI&lt;/a&gt; to run our existing &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;uv&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt; command on a cloud VM.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;$&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;process.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The same exact thing that happened locally before now happens on a cloud VM on AWS, only this time the script is faster because we didn’t have to transfer any data from S3 to a local laptop.&lt;/p&gt;
&lt;figure class="align-default" id="id4"&gt;
&lt;img alt="https://docs.coiled.io/_images/uv-coiled.png" src="https://docs.coiled.io/_images/uv-coiled.png" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;View of &lt;a class="reference external" href="https://cloud.coiled.io/"&gt;Coiled UI&lt;/a&gt; with our script running on AWS.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;uv is great. It’s a really pleasant experience for managing dependencies when running Python scripts. It also works well with Coiled when you want to run scripts on cloud hardware.&lt;/p&gt;
&lt;p&gt;Together uv and Coiled make for a powerful, ergonomic cloud Python experience.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/uv-coiled-cloud-scripts.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jun 30, 2025</summary>
    <published>2025-06-30T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/how-to-run-a-python-script-on-ec2.html</id>
    <title>How to run a Python script on EC2</title>
    <updated>2025-06-25T00:00:00+00:00</updated>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jun 25, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;5 min read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="how-to-run-a-python-script-on-ec2"&gt;

&lt;p&gt;There are a lot of ways to run Python on AWS. You might use Lambda for event-driven tasks, Glue for managing data pipelines, or SageMaker for machine learning workloads. Each of these tools offers helpful abstractions, but also comes with constraints. For example:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lambda&lt;/strong&gt; is fast and simple, but capped at 15-minute execution time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;EMR&lt;/strong&gt; can scale data processing across a cluster, but has limited support for custom libraries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SageMaker&lt;/strong&gt; is powerful for ML, but can be heavyweight for general-purpose Python scripts.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s why a lot of engineers will instead use &lt;a class="reference external" href="https://aws.amazon.com/ec2/"&gt;EC2&lt;/a&gt;, which is highly configurable. You get you raw virtual machines anywhere in the world in about 30 seconds, fully under your control, for just pennies.&lt;/p&gt;
&lt;p&gt;While AWS EC2 offers remarkable flexibility, it’s pretty low-level and not the most intuitive. Many data scientists and engineers avoid using it directly because it can feel tedious or overly complex. Worse, it’s often inefficient (like spinning up a machine and accidentally leaving it on all week). Coiled aims to address these issues, without sacrificing that flexibility.&lt;/p&gt;
&lt;p&gt;Coiled makes it easy to run Python scripts on EC2 and scale out to run your jobs in parallel. It provisions machines on your behalf, replicates your local Python environment, and shuts everything down when the job finishes. You get the versatility of EC2, without the constraints of other AWS tools.&lt;/p&gt;
&lt;p&gt;This post walks through two ways to run Python on EC2:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;The manual route (for full control).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Python-native method using Coiled.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;section id="option-1-run-python-on-ec2-manually"&gt;
&lt;h2&gt;Option 1: Run Python on EC2 manually&lt;/h2&gt;
&lt;p&gt;This process gives you full control over how and where your Python script runs in AWS, but it’s a bit involved. Here’s how to do it, correctly and safely.&lt;/p&gt;
&lt;section id="step-1-launch-an-ec2-instance"&gt;
&lt;h3&gt;Step 1: Launch an EC2 instance&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Go to the EC2 Console&lt;/strong&gt;: &lt;a class="reference external" href="https://console.aws.amazon.com/ec2/"&gt;https://console.aws.amazon.com/ec2/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Launch Instance&lt;/strong&gt;: Click “Launch Instance”.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Choose an AMI&lt;/strong&gt;: Pick something like Ubuntu or Amazon Linux.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Select Instance Type&lt;/strong&gt;: &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;t3.medium&lt;/span&gt;&lt;/code&gt; is a good starting point. Use larger instance types for bigger workloads (Vantage has a nice &lt;a class="reference external" href="https://instances.vantage.sh/"&gt;EC2 comparison table&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a Key Pair (RSA)&lt;/strong&gt;: Download the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.pem&lt;/span&gt;&lt;/code&gt; file (keep it private, you’ll need this for SSH access).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure Network Settings&lt;/strong&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Open SSH (port 22) in the security group only to your IP (not 0.0.0.0/0).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optionally, enable HTTP (port 80) or HTTPS (443) if your script serves a web app.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Launch the Instance&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section id="step-2-connect-to-your-ec2-instance"&gt;
&lt;h3&gt;Step 2: Connect to your EC2 instance&lt;/h3&gt;
&lt;p&gt;Once your EC2 instance is running, click “Connect” to see the instructions for how to connect to your instance.&lt;/p&gt;
&lt;p&gt;Ensure the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.pem&lt;/span&gt;&lt;/code&gt; file has the right permissions:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;your-key.pem
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
Ubuntu&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;your-key.pem&lt;span class="w"&gt; &lt;/span&gt;ubuntu@&amp;lt;your-ec2-public-ip&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
Amazon Linux&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;your-key.pem&lt;span class="w"&gt; &lt;/span&gt;ec2-user@&amp;lt;your-ec2-public-ip&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="step-3-set-up-your-python-environment"&gt;
&lt;h3&gt;Step 3: Set up your Python environment&lt;/h3&gt;
&lt;p&gt;Once you’re connected to the instance:&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-2" name="sd-tab-set-1" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-2"&gt;
Ubuntu&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;The Ubuntu AMI should already have Python 3 installed, but if not, you’ll need to install it first.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Update the system&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;upgrade&lt;span class="w"&gt; &lt;/span&gt;-y

&lt;span class="c1"&gt;# Install Python&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;python3-venv&lt;span class="w"&gt; &lt;/span&gt;-y

&lt;span class="c1"&gt;# Set up a virtual environment&lt;/span&gt;
python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;env
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;env/bin/activate
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-3" name="sd-tab-set-1" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-3"&gt;
Amazon Linux&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;The Amazon Linux AMI should already have Python 3 installed, but if not, you’ll need to install it first.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Update the system&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;yum&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;-y

&lt;span class="c1"&gt;# Install Python&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;yum&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-y

&lt;span class="c1"&gt;# Set up a virtual environment&lt;/span&gt;
python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;env
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;env/bin/activate
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="step-4-transfer-your-python-script"&gt;
&lt;h3&gt;Step 4: Transfer your Python script&lt;/h3&gt;
&lt;p&gt;From your local machine, use scp to upload your Python files to EC2:&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-4" name="sd-tab-set-2" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-4"&gt;
Ubuntu&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scp&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;your-key.pem&lt;span class="w"&gt; &lt;/span&gt;script.py&lt;span class="w"&gt; &lt;/span&gt;ubuntu@&amp;lt;your-ec2-ip&amp;gt;:~
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-5" name="sd-tab-set-2" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-5"&gt;
Amazon Linux&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scp&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;your-key.pem&lt;span class="w"&gt; &lt;/span&gt;script.py&lt;span class="w"&gt; &lt;/span&gt;ec2-user@&amp;lt;your-ec2-ip&amp;gt;:~
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can also copy folders or zipped environments if needed.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="step-5-run-your-python-script"&gt;
&lt;h3&gt;Step 5: Run your Python script&lt;/h3&gt;
&lt;p&gt;You should now be able to see the Python script you uploaded on your EC2 instance. You can list the files in your working directory to check with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;ls&lt;/span&gt; &lt;span class="pre"&gt;-l&lt;/span&gt;&lt;/code&gt;. Then, you can run the script:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;script.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="step-6-turn-off-your-instance"&gt;
&lt;h3&gt;Step 6: Turn off your instance&lt;/h3&gt;
&lt;p&gt;Once you’re done, if you don’t turn off your EC2 instance, AWS will keep charging you. You can do this from the AWS console. It’s worth noting that if you have an attached EBS volume, AWS will continue to charge you for storage unless you terminate the instance.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Screenshot of the AWS console showing how to terminate an EC2 instance to avoid runaway costs." src="https://docs.coiled.io/_images/terminate-ec2.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="aws-ec2-best-practices"&gt;
&lt;h3&gt;AWS EC2 best practices&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Use security groups to restrict SSH to your IP to control who can access your instances&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terminate EC2 instances when you’re done to avoid a surprise bill&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use S3 for data storage to decouple your data from ephemeral VMs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since you’ll need to SSH into your remote VM, it’s helpful to at least know some basic shell commands&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This manual approach offers full control but can be cumbersome, especially for scaling or handling complex dependencies.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="option-2-use-coiled-to-run-python-on-ec2"&gt;
&lt;h2&gt;Option 2: Use Coiled to run Python on EC2&lt;/h2&gt;
&lt;p&gt;Coiled is a Python-native platform designed to simplify running Python workloads on the cloud. It allows you to easily manage virtual machines with minimal code. Coiled automates the provisioning of EC2 instances in your AWS account, synchronizes your environment, executes your code, and ensures resources are efficiently managed. Coiled:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Eliminates the need complex cloud console navigation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replicates your local Python environment and packages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supports any hardware available on EC2 (GPUs, ARM, Spot, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatically shuts down instances after execution to save costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Offers support for custom VPCs, subnets, firewalls, and private IPs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="step-1-install-coiled"&gt;
&lt;h3&gt;Step 1: Install Coiled&lt;/h3&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="step-2-configure-aws-access"&gt;
&lt;h3&gt;Step 2: Configure AWS access&lt;/h3&gt;
&lt;p&gt;Coiled requires limited &lt;a class="reference external" href="https://coiled.io/security#iam-roles"&gt;IAM permissions&lt;/a&gt; to provision resources in your AWS account. You can set this up using the Coiled CLI:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;aws
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="step-3-run-a-python-script-on-aws-with-coiled"&gt;
&lt;h3&gt;Step 3: Run a Python script on AWS with Coiled&lt;/h3&gt;
&lt;div class="literal-block-wrapper docutils container" id="id1"&gt;
&lt;div class="code-block-caption"&gt;&lt;span class="caption-text"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.py&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED n-tasks     10&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED memory      8 GiB&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="c1"&gt;#COILED region      us-east-2&lt;/span&gt;
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;COILED_BATCH_TASK_ID&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And then launch your script with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;batch&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;batch&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;my_script.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled makes it easy to scale your Python scripts to run on as many VMs as you need. In this example, we ran &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_script.py&lt;/span&gt;&lt;/code&gt; in parallel on 10 EC2 instances. Under the hood Coiled will:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Inspect your script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spin up the appropriate machines as defined by the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#COILED&lt;/span&gt;&lt;/code&gt; comments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Download your software onto them or use the container you specified&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run your script, automatically scaling out as needed with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#COILED&lt;/span&gt; &lt;span class="pre"&gt;n-tasks&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shut down the machines&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using &lt;a class="reference internal" href="user_guide/batch.html"&gt;&lt;span class="doc std std-doc"&gt;batch jobs&lt;/span&gt;&lt;/a&gt; is one easy way to run a simple Python script, but there are a number of other options:&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-6" name="sd-tab-set-3" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-6"&gt;
Serverless Functions&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Like AWS Lambda, but better for long-running jobs or if you need GPUs. Decorate a function to run it in the cloud. Scale out with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.map&lt;/span&gt;&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Run close to the data&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;output_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;.parquet&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output_filename&lt;/span&gt;

&lt;span class="c1"&gt;# result = process(&amp;quot;s3://my-bucket/data.parquet&amp;quot;)  # one file&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# many files in parallel&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Finished&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-7" name="sd-tab-set-3" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-7"&gt;
Dask Clusters&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Like AWS EMR, but easier to use (plus, &lt;a class="reference internal" href="blog/tpch.html"&gt;&lt;span class="doc std std-doc"&gt;Dask is often faster than Spark&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Use Dask + Pandas together&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://bucket/lots-of-data.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-8" name="sd-tab-set-3" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-8"&gt;
Jupyter Notebooks&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Run Jupyter on large cloud VMs. Synchronize files back to your local hard drive.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;notebook&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;--sync&lt;span class="w"&gt; &lt;/span&gt;--vm-type&lt;span class="w"&gt; &lt;/span&gt;m6i.16xlarge
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="when-would-you-want-to-use-ec2-manually-vs-coiled-to-run-python"&gt;
&lt;h2&gt;When would you want to use EC2 manually vs. Coiled to run Python?&lt;/h2&gt;
&lt;p&gt;Coiled supports a lot, including custom VPCs and firewall rules, but there are some cases where manually running Python on EC2 might be better:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Custom OS or low-level system configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Custom AMI creation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Highly regulated cloud environments with air-gapped requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Advanced networking (beyond custom VPCs), like hybrid clouds&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;It’s easy to &lt;a class="reference internal" href="user_guide/setup/index.html"&gt;&lt;span class="doc std std-doc"&gt;get started&lt;/span&gt;&lt;/a&gt; with Coiled:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;quickstart
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Learn more:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/python-emr.html"&gt;&lt;span class="doc std std-doc"&gt;Why AWS EMR can be challenging for Python jobs&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/aws-lambda-gpu-alternative.html"&gt;&lt;span class="doc std std-doc"&gt;Alternatives to AWS Lambda for GPUs&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/burstable-vs-nonburstable.html"&gt;&lt;span class="doc std std-doc"&gt;Cost comparison of burstable vs. non-burstable instances&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/how-to-run-a-python-script-on-ec2.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jun 25, 2025</summary>
    <published>2025-06-25T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/aws-lambda-gpu-alternative.html</id>
    <title>Coiled: A GPU-accelerated serverless alternative to AWS Lambda</title>
    <updated>2025-06-11T00:00:00+00:00</updated>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jun 11, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4 min read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="coiled-a-gpu-accelerated-serverless-alternative-to-aws-lambda"&gt;

&lt;p&gt;AWS Lambda is widely used for event-driven workloads that require quick, lightweight execution without infrastructure management. But when it comes to tasks that demand GPU acceleration, like training deep learning models, running inference on images, or performing parallel scientific simulations, Lambda falls short.&lt;/p&gt;
&lt;p&gt;With no GPU support, a 15-minute execution limit, and container image size limits, AWS Lambda isn’t designed for compute-heavy or memory-intensive applications.&lt;/p&gt;
&lt;p&gt;In this post, we’ll explore how Coiled provides a serverless Python experience with full GPU support, offering a flexible, Lambda-like alternative for GPU-accelerated computing in your own AWS account.&lt;/p&gt;
&lt;section id="why-lambda-alone-falls-short"&gt;
&lt;h2&gt;Why Lambda alone falls short&lt;/h2&gt;
&lt;p&gt;AWS Lambda was specifically designed for short-lived compute tasks. And for these for small, event-driven tasks it works really well. This includes things like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Responding to API Gateway requests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Parsing log files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Short-running Python scripts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But it falls short for high-performance compute due to a number of &lt;a class="reference external" href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html"&gt;limitations&lt;/a&gt;.&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head"&gt;&lt;p&gt;Feature&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;AWS Lambda&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Coiled&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;GPU support&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Spot instances&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;❌ No&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Max execution time&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;15 minutes&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit, configurable&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Container image size&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Capped at 10 GB, uncompressed&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit, &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/index.html"&gt;package sync or Docker&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Scale-to-zero&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes (by default)&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;✅ Yes (by default)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Memory&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Capped at 10 GB&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit, configurable&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;vCPU&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Proportional to memory, not directly configurable&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;No limit, configurable&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;AWS Lambda imposes several architectural constraints that limit its suitability for GPU-based or high-performance workloads. Memory and vCPU are tied together, maxing out at 10 GB and proportional CPU allocation, with no GPU support. Container images must remain under 10 GB uncompressed, which is often too small for GPU-accelerated Python workloads that bundle frameworks like PyTorch, TensorFlow, CUDA, and cuDNN (not to mention any pre-trained model weights or scientific dependencies). By contrast, Coiled offers additional flexibility: define your software environment using conda or pip, use Docker images of any size, and deploy clusters with GPU-backed instances on demand, all without container size limits or infrastructure overhead.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="coiled-a-serverless-gpu-alternative"&gt;
&lt;h2&gt;Coiled: A serverless GPU alternative&lt;/h2&gt;
&lt;p&gt;With Coiled, you can run Python workloads on-demand in your AWS account with optional GPU support. Coiled spins up EC2 instances as you need them, replicating your local Python environment on remote VMs. With Coiled, you can:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Spin up GPU-backed clusters (e.g., &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;g5.xlarge&lt;/span&gt;&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run Torch, TensorFlow, or custom CUDA workloads&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Spot instances for cost efficiency&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="use-cases-for-gpus-coiled"&gt;
&lt;h3&gt;Use cases for GPUs + Coiled&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Deep learning inference at scale&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Image/video processing pipelines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scientific simulations (e.g., genomics)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ETL pipelines with GPU libraries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Parallel compute tasks in finance or physics&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="step-by-step-guide-running-gpu-accelerated-workloads-on-coiled"&gt;
&lt;h2&gt;Step-by-step guide: Running GPU-accelerated workloads on Coiled&lt;/h2&gt;
&lt;section id="sign-up"&gt;
&lt;h3&gt;Sign up&lt;/h3&gt;
&lt;p&gt;First, &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;sign up for Coiled&lt;/a&gt; using GitHub, Google, or by making your own username and password.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="install-coiled"&gt;
&lt;h3&gt;Install Coiled&lt;/h3&gt;
&lt;p&gt;Next, we’ll install Coiled. We’ll also include PyTorch, since this example will go through training a PyTorch model on a GPU.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
pip&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;torch&lt;span class="w"&gt; &lt;/span&gt;torchvision&lt;span class="w"&gt; &lt;/span&gt;coiled
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
conda&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;conda&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.11&lt;span class="w"&gt; &lt;/span&gt;pytorch&lt;span class="w"&gt; &lt;/span&gt;torchvision&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;conda&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;env
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="train-pytorch-model"&gt;
&lt;h3&gt;Train PyTorch model&lt;/h3&gt;
&lt;p&gt;Next, we’ll define our PyTorch model:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch.nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;nn&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch.nn.functional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;F&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Net&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Net&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conv1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conv2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxPool2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conv2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conv2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conv1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conv2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now that we have our model, let’s train it on our dataset on the hardware of our choosing. Fortunately, PyTorch
makes it straightforward to utilize different types of hardware that are available locally.&lt;/p&gt;
&lt;p&gt;Below is a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;train&lt;/span&gt;&lt;/code&gt; function that takes a &lt;a class="reference external" href="https://pytorch.org/docs/stable/tensor_attributes.html#torch.device"&gt;PyTorch device&lt;/a&gt; as input (defaults to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;/code&gt;), loads the CIFAR10 dataset, trains our model on the dataset on the specified hardware, and returns the trained model.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torchvision&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch.optim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;optim&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torchvision.transforms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;transforms&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Select hardware to run on&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Net&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Load CIFAR10 dataset&lt;/span&gt;
    &lt;span class="n"&gt;trainset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torchvision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datasets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CIFAR10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToTensor&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Normalize&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mf"&gt;0.4914&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.4822&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.4465&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2470&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2435&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2616&lt;/span&gt;&lt;span class="p"&gt;))]),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trainloader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;trainset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;criterion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CrossEntropyLoss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SGD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;momentum&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Epoch &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;epoch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trainloader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Move training data to device&lt;/span&gt;
            &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;criterion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="run-on-a-cloud-gpu"&gt;
&lt;h3&gt;Run on a cloud GPU&lt;/h3&gt;
&lt;p&gt;Our train function can now be run on any hardware that’s available locally. To train our model on a GPU, we’ll use Coiled Functions to lightly annotate our existing train function to run on a GPU-enabled cloud VM.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;g5.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# NVIDIA A10 GPU instance&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Same training code as before&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now when the train function is run, Coiled will automatically handle provisioning a cloud VM (a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;g5.xlarge&lt;/span&gt;&lt;/code&gt; instance on AWS in this case), installing the same software that’s installed locally on the cloud VM, running our train function on the VM, and returning the result back locally.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Train model on cloud GPU&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This takes ~4.5 minutes to run (including VM spinup time) and costs ~$0.08 (running locally would have taken ~1.5 hours).&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="GPU utilization throughout model training." src="https://docs.coiled.io/_images/pytorch-function-gpu-metrics.png" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;GPU utilization throughout model training. Utilization is typically high throughout, meaning we’re utilizing the available hardware well.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="how-does-coiled-compare-to-other-serverless-tools"&gt;
&lt;h2&gt;How does Coiled compare to other serverless tools?&lt;/h2&gt;
&lt;p&gt;Tools like AWS Lambda, Google Cloud Functions, and Azure Functions are optimized for lightweight, fast-response tasks (such as web endpoints or trigger-based coordination), but they are less suited for heavy data processing or GPU-accelerated workloads. We also find these cloud provider-specific tools leave a lot to be desired in terms of developer experience and often require cloud provider-specific knowledge. See &lt;a class="reference external" href="https://docs.coiled.io/user_guide/vs.html#aws-lambda-gcp-cloud-functions-azure-functions"&gt;the full comparison&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;Tools like Modal and Coiled on the other hand, prioritize ease of use, making it easy for anyone to use GPUs on the cloud. Modal, for example, offers cold start times around one second and is an excellent choice for latency-sensitive applications. However, this comes with tradeoffs, like higher cost per compute unit (often 4–5x raw EC2 cost) and less flexibility in hardware and environment setup. Coiled functions, on the other hand, are cheaper at scale (raw EC2 instances are spun up as needed) and offer more security and flexibility (everything runs in your cloud account). For a deeper dive, &lt;a class="reference external" href="https://docs.coiled.io/user_guide/vs.html#vs-modal"&gt;read the full comparison on Coiled vs. Modal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In short:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;If you need sub-second response times for short-lived web requests, tools like Modal are a good fit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you’re doing large-scale, resource-intensive workloads, and have more specific security requirements, Coiled is a better fit.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;Serverless functions aren’t the only way to run GPU-accelerated workloads with Coiled. You can also use:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/batch.html"&gt;Batch jobs&lt;/a&gt;: Like AWS Batch, but a lot easier to use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/notebooks.html"&gt;Jupyter notebooks&lt;/a&gt;: Run a Jupyter Notebook on AWS/GCP/Azure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask.html"&gt;Distributed compute&lt;/a&gt;: Speed up and scale your Python workflows with Dask clusters.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/aws-lambda-gpu-alternative.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jun 11, 2025</summary>
    <published>2025-06-11T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/python-emr.html</id>
    <title>Python is annoying to run on EMR, but you have options</title>
    <updated>2025-05-08T00:00:00+00:00</updated>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;May 7, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;6m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="python-is-annoying-to-run-on-emr-but-you-have-options"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Amazon EMR (Elastic MapReduce) is a powerful managed cluster platform that helps organizations process large amounts of data. While AWS EMR offers significant performance benefits, its cost structure is complex and and many teams fail to consider the additional cost of developer time and energy.&lt;/p&gt;
&lt;p&gt;In this article, we’ll cover:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;The main drivers of EMR cost&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why Python teams face additional challenges with EMR&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Coiled as a Python-native alternative for data processing workloads&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Though EMR can run distributed data processing jobs with a number of open source libraries, this post will primarily focus on running Apache Spark on EMR, as that is the most widely used.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="AWS architecture diagram of running Apache Spark on EMR on EKS." src="https://docs.coiled.io/_images/emr-on-eks.png" style="width: 700px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Architecture diagram of running an Apache Spark cluster on AWS EMR on EKS from the &lt;a class="reference external" href="https://aws.amazon.com/blogs/big-data/run-and-debug-apache-spark-applications-on-aws-with-amazon-emr-on-amazon-eks/"&gt;AWS Big Data Blog&lt;/a&gt;.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;section id="aws-emr-costs-broken-down"&gt;
&lt;h2&gt;AWS EMR costs broken down&lt;/h2&gt;
&lt;p&gt;There are multiple factors to consider when assessing the prospective cost of AWS EMR for your analytics and data workloads. While AWS EMR uses a consumption-based pricing model, you’ll also need to factor in EC2 costs, storage, data transfer, and a number of additional hidden costs.&lt;/p&gt;
&lt;section id="aws-emr-pricing-model"&gt;
&lt;h3&gt;AWS EMR pricing model&lt;/h3&gt;
&lt;p&gt;AWS EMR follows a “pay-as-you-go” consumption-based pricing model where users pay for the resources they consume. Pricing varies based on the deployment mode and instance type selected.&lt;/p&gt;
&lt;p&gt;Primary EMR deployment options include:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;EMR on EC2: Users pay for both the EC2 instances and EMR service fees.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;EMR on EKS (Elastic Kubernetes Service): Costs are based on Kubernetes resources used, including EC2 instances or Fargate compute charges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;EMR Serverless: Users are billed based on the number of compute units and memory usage per second.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="ec2-instance-costs"&gt;
&lt;h3&gt;EC2 instance costs&lt;/h3&gt;
&lt;p&gt;Since EMR clusters run on Amazon EC2 instances, compute costs are a significant portion of EMR expenses. Instance options include:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;On-demand instances: Pay for compute capacity by the second, with no long-term commitments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Burstable instances: Perfect for workloads like web servers which have low CPU utilization most of the time, but occasionally fully utilize the CPU.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spot instances: Significant cost savings (up to 90%) by using spare AWS capacity, but they can be interrupted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ARM instances: Using ARM (Graviton) instances can reduce costs for certain workloads, in some cases &lt;a class="reference external" href="https://aws.amazon.com/about-aws/whats-new/2023/03/amazon-emr-amazon-ec2-c7g-graviton3-instances/"&gt;up to 50%&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reserved instances: Provide discounts in exchange for committing to EC2 capacity for one to three years.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="storage-and-data-transfer-costs"&gt;
&lt;h3&gt;Storage and data transfer costs&lt;/h3&gt;
&lt;p&gt;Storage and data transfer also contribute significantly to AWS EMR costs:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Amazon S3 storage: Many EMR workloads rely on Amazon S3 for data storage. Costs depend on the storage tier (standard, infrequent access, or glacier).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Amazon EBS (Elastic Block Store): If EC2 instances use attached EBS volumes, storage fees apply based on provisioned capacity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data transfer fees: Transferring data within the same AWS region is free, but inter-region and external data transfers incur additional charges.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="emr-is-also-hard-to-use"&gt;
&lt;h2&gt;EMR is also hard to use&lt;/h2&gt;
&lt;p&gt;When evaluating AWS EMR, most organizations focus primarily on direct compute and storage costs, though there are significant hidden costs to consider:&lt;/p&gt;
&lt;section id="cluster-management-and-devops-overhead"&gt;
&lt;h3&gt;1. Cluster management and DevOps overhead&lt;/h3&gt;
&lt;p&gt;Unlike fully serverless solutions, AWS EMR requires ongoing management and tuning to maintain optimal performance. Key challenges include:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Provisioning and scaling clusters&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cluster lifecycle management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monitoring and logging complexity&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="engineering-costs-for-python-based-data-science-teams"&gt;
&lt;h3&gt;2. Engineering costs for Python-based data science teams&lt;/h3&gt;
&lt;p&gt;For teams working primarily in Python, EMR introduces additional engineering overhead:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Tuning Spark performance (efficient memory allocation, partitioning, etc. require Spark expertise)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inefficient debugging and development cycles (Spark clusters are notoriously time-consuming to debug)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PySpark serialization and rewriting code for Spark compatibility&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="vendor-lock-in-risks"&gt;
&lt;h3&gt;3. Vendor lock-in risks&lt;/h3&gt;
&lt;p&gt;Committing to AWS EMR can create dependencies on AWS-specific features and services, making it difficult to migrate workloads to other platforms in the future.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="coiled-a-python-native-alternative-to-aws-emr"&gt;
&lt;h2&gt;Coiled: A Python-native alternative to AWS EMR&lt;/h2&gt;
&lt;p&gt;For teams working in Python, AWS EMR’s reliance on Spark and Hadoop adds unnecessary overhead, making development slower and more expensive. If you haven’t committed to EMR yet or want easier alternatives for new workloads, Coiled offers an alternative.&lt;/p&gt;
&lt;section id="why-choose-coiled-over-aws-emr"&gt;
&lt;h3&gt;Why choose Coiled over AWS EMR?&lt;/h3&gt;
&lt;p&gt;Coiled provides a modern, Python-native alternative that’s easy to use, highly scalable, and cost-effective.&lt;/p&gt;
&lt;section id="python-native"&gt;
&lt;h4&gt;1. Python-native&lt;/h4&gt;
&lt;p&gt;Coiled is designed for Python data developers. For distributed computing Coiled leverages &lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt;, an open source parallel computing library. Dask feels a lot like pandas, making it easy for teams to scale their existing pandas code with minimal modifications.&lt;/p&gt;
&lt;p&gt;EMR, by comparison, commonly relies on Spark for distributed computing. For data teams using primarily Python, migrating to Spark is notoriously challenging.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;A lot of the Spark internals are run on the JVM, so it’s actually not that easy to debug. You spend a lot of time on Stack Overflow trying to figure out what did I do wrong, why am I getting this traceback. It was painful.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Sébastien Arnaud, CDO/CTO at Steppingblocks, on &lt;a class="reference external" href="https://youtu.be/jR0Y7NqKJs8?si=UhhZ6jZjsY4nPqvO&amp;amp;amp;t=413"&gt;why they switched from Spark to Dask&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Since Dask is just Python, you don’t have to waste time debugging JVM errors. Additionally, you can easily see metrics like CPU and memory utilization in the Coiled UI or detailed task-level progress with the Dask dashboard.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="zero-infrastructure-management"&gt;
&lt;h4&gt;2. Zero infrastructure management&lt;/h4&gt;
&lt;p&gt;Coiled eliminates cluster management overhead by automatically handling provisioning, scaling, and maintenance of AWS resources. This means you no longer need to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wait for Docker images or build custom AMIs.&lt;/strong&gt; With Coiled, software is automatically replicated on your cloud VMs with &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/sync.html"&gt;package sync&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rightsize Kubernetes.&lt;/strong&gt; Coiled relies on a simpler architecture, automatically spinning up raw EC2 instances when you need them in ~ a minute.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Worry about VMs left on over the weekend.&lt;/strong&gt; Coiled automatically turn things off when your computation is done and sends out alerts if things have been running longer than usual.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For teams without a dedicated cloud infrastructure team, this frees up key engineers from infrastructure overhead. Instead of spending their time figuring out why EKS isn’t working, they can focus on their area of expertise.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="cost-savings"&gt;
&lt;h4&gt;3. Cost savings&lt;/h4&gt;
&lt;p&gt;The cloud can be quite cheap, &lt;a class="reference external" href="https://docs.coiled.io/blog/ten-cents.html"&gt;~$0.10 / TiB&lt;/a&gt;, &lt;em&gt;if&lt;/em&gt; you’ve configured everything correctly. Coiled makes it easy to use the cloud at scale efficiently:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Auto-scaling based on workload demands&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trivial to use Spot, ARM&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read data in-region to avoid egress fees&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set team- and user-specific resource limits&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Eliminate dead costs with auto-shutdown and clean-up once you’re done&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s also a generous free tier of 500 CPU hours per month.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;               &lt;span class="c1"&gt;# Autoscale as needed&lt;/span&gt;
    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Use spot when available&lt;/span&gt;
    &lt;span class="n"&gt;arm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                          &lt;span class="c1"&gt;# Use ARM&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;# Same region as your data&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="no-vendor-lock-in"&gt;
&lt;h4&gt;4. No vendor lock-in&lt;/h4&gt;
&lt;p&gt;Coiled works with AWS, GCP, or Azure. Access your data wherever it’s stored (Snowflake, S3, Azure Blob, etc.) and use the same libraries you’re already using (Polars, PyTorch, Xarray, even GDAL).&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
S3 + Polars&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Read in Parquet files from S3 with your preferred DataFrame library (pandas, Polars, DuckDB, Dask, …) with Coiled’s serverless functions.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;polars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pl&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;512 GiB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run_query&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://my-bucket/*&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;balance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
Snowflake + Dask&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;With &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask_snowflake&lt;/span&gt;&lt;/code&gt;, you can read from a Snowflake table into a Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_snowflake&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;example_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;    SELECT *&lt;/span&gt;
&lt;span class="s2"&gt;    FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_snowflake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;example_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;connection_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;password&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="when-to-still-use-emr"&gt;
&lt;h3&gt;When to still use EMR?&lt;/h3&gt;
&lt;p&gt;EMR still has its place, especially in certain Spark-heavy environments. Here are some reasons why you might stick with EMR:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;You’re deeply invested in Apache Spark&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;EMR is optimized for Spark, and if your team already has Spark pipelines, libraries, and infrastructure, migrating everything to Dask might be time consuming.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;&lt;p&gt;You need tight AWS service integration&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;If you’re already embedded into the AWS ecosystem and using tools like AWS Glue and Step Functions, EMR fits in naturally.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;&lt;p&gt;Your team already has EMR expertise&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;If your data team already knows how to wrangle EMR clusters, it’s hard to justify switching unless there’s a real gain in productivity or cost.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="getting-started-with-coiled"&gt;
&lt;h2&gt;Getting started with Coiled&lt;/h2&gt;
&lt;p&gt;It’s easy to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;get started&lt;/a&gt; with Coiled:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;quickstart
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Learn more:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/blog/dask-vs-spark.html"&gt;Dask vs. Spark: When to Use Each&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/blog/aws-cost-explorer-tips.html"&gt;AWS Cost Explorer Tips for Data Teams&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/blog/burstable-vs-nonburstable.html"&gt;Cost Comparison of Burstable vs. Non-Burstable Instances&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/python-emr.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;May 7, 2025</summary>
    <published>2025-05-08T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-array-queueing.html</id>
    <title>Reducing Memory Pressure for Xarray + Dask Workloads</title>
    <updated>2025-03-17T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Mar 17, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="reducing-memory-pressure-for-xarray-dask-workloads"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/phofl"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/61934744?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Patrick Hoefler&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Historically, Dask scheduled thousands of data-loading tasks at once, overloading the system. The new queuing mechanism prevents this, making Xarray workloads smoother and more reliable.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Screencast of the Dask dashboard running a satellite data computation." src="https://docs.coiled.io/_images/queuing-stac.gif" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;As of Dask 2025.1.0, Dask-backed Xarray workloads are more memory-efficient. Processing satellite images from Microsoft Planetary Computer, for example, is much smoother.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;section id="the-challenge-overeager-scheduling-of-data-loading-tasks"&gt;
&lt;h2&gt;The Challenge: Overeager Scheduling of Data-Loading Tasks&lt;/h2&gt;
&lt;p&gt;Dask’s scheduler aims to distribute workloads efficiently, but data-loading tasks—such as reading from Zarr, NetCDF, or remote storage—have historically been scheduled too aggressively.&lt;/p&gt;
&lt;p&gt;Previously, Dask would immediately schedule all available data-loading tasks, causing workers to fetch large amounts of data before they were ready to process it. This led to excessive memory use, frequent spilling to disk, and even crashes.&lt;/p&gt;
&lt;p&gt;Dask introduced &lt;a class="reference external" href="https://www.coiled.io/blog/reducing-dask-memory-usage"&gt;root task queuing in 2022&lt;/a&gt; to mitigate this issue by identifying and scheduling these tasks more cautiously. However, the detection mechanism was fragile and often failed in cases like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Opening multiple Zarr stores&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Loading many NetCDF files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Accessing data from Microsoft Planetary Computer&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This meant that for many Xarray workloads, the memory footprint was unpredictable, leading to inefficient resource usage.&lt;/p&gt;
&lt;section id="example-of-the-problem"&gt;
&lt;h3&gt;Example of the Problem&lt;/h3&gt;
&lt;p&gt;In the following example, we open multiple Zarr stores and calculate moving averages:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;da&lt;/span&gt;

&lt;span class="n"&gt;zarr_paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/tmp/1.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/tmp/2.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/tmp/3.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/tmp/4.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/tmp/5.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;arrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_zarr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;zarr_paths&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concatenate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones_like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;avg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Tip&lt;/p&gt;
&lt;p&gt;Run the following snippet to re-create the test dataset used in the example above:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;da&lt;/span&gt;

&lt;span class="n"&gt;zarr_paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/tmp/1.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/tmp/2.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/tmp/3.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/tmp/4.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/tmp/5.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;zarr_paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2560&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_zarr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We can see that Dask is quickly spilling to disk, because it’s overeagerly loading the
source data.&lt;/p&gt;
&lt;div class="sd-card sd-sphinx-override sd-w-75 sd-mt-0 sd-mb-2 sd-ml-auto sd-mr-auto sd-shadow-none sd-text-center wistia-card docutils"&gt;
&lt;div class="sd-card-body docutils"&gt;
&lt;script src="https://fast.wistia.com/player.js" async&gt;&lt;/script&gt;&lt;script src="https://fast.wistia.com/embed/6cyyrii05v.js" async type="module"&gt;&lt;/script&gt;&lt;style&gt;wistia-player[media-id='6cyyrii05v']:not(:defined) { background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/6cyyrii05v/swatch'); display: block; filter: blur(5px); padding-top:52.71%; }&lt;/style&gt; &lt;wistia-player media-id="6cyyrii05v" seo="false" aspect="1.8972332015810276"&gt;&lt;/wistia-player&gt;
&lt;/div&gt;
&lt;div class="sd-card-footer docutils"&gt;
&lt;p class="sd-card-text"&gt;&lt;em&gt;Prior to Dask version 2025.1.0, loading multiple Zarr stores with Xarray caused memory issues. This is apparent in the Dask dashboard, with grey bars in the cluster memory and workers memory graphs indicating Dask is spilling to disk.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Before the recent improvements, Dask would load all chunks into memory at once, slowing down workloads and even crashing due to memory issues. This was especially troublesome for workloads creating multiple datasets from different Zarr stores, like in the example above, or when loading large NetCDF files.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="the-solution-more-robust-root-task-detection"&gt;
&lt;h2&gt;The Solution: More Robust Root Task Detection&lt;/h2&gt;
&lt;p&gt;Dask 2025.01.0 introduces a more reliable mechanism for detecting and scheduling data-loading tasks. Dask explicitly marks data-loading tasks so the scheduler can manage them efficiently. Now, data is only loaded when it can be processed, keeping memory usage stable and preventing unnecessary disk writes.&lt;/p&gt;
&lt;div class="sd-card sd-sphinx-override sd-w-75 sd-mt-0 sd-mb-2 sd-ml-auto sd-mr-auto sd-shadow-none sd-text-center wistia-card docutils"&gt;
&lt;div class="sd-card-body docutils"&gt;
&lt;script src="https://fast.wistia.com/player.js" async&gt;&lt;/script&gt;&lt;script src="https://fast.wistia.com/embed/tf828dlaha.js" async type="module"&gt;&lt;/script&gt;&lt;style&gt;wistia-player[media-id='tf828dlaha']:not(:defined) { background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/tf828dlaha/swatch'); display: block; filter: blur(5px); padding-top:52.71%; }&lt;/style&gt; &lt;wistia-player media-id="tf828dlaha" seo="false" aspect="1.8972332015810276"&gt;&lt;/wistia-player&gt;
&lt;/div&gt;
&lt;div class="sd-card-footer docutils"&gt;
&lt;p class="sd-card-text"&gt;&lt;em&gt;With the fixes implemented in Dask 2025.1.0, the same example takes ~30 seconds to complete vs. over a minute. Data-loading tasks are queued rather than being scheduled immediately. There’s much less memory pressure on the cluster and Dask is no longer spilling to disk.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Running the same example as above, memory on the cluster remains fairly constant and reliably low enough that we don’t run into spilling issues. This particular example also runs twice as fast since Dask is no longer constantly writing to and reading from disk.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="real-world-implications"&gt;
&lt;h2&gt;Real World Implications&lt;/h2&gt;
&lt;p&gt;This change has a number of real-world implications for Xarray users and users of related
Python libraries. Most workloads opening NetCDF files will run into this issue one way or another
at some point when scaling to larger workloads. Additionally, we’ve also seen this regularly
occurring with users of satellite data through &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;odc-stac&lt;/span&gt;&lt;/code&gt;. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;odc-stac&lt;/span&gt;&lt;/code&gt; implements a great
interface to load satellite images, but the graph construction is prone to run into this
problem as well.&lt;/p&gt;
&lt;div class="sd-card sd-sphinx-override sd-w-75 sd-mt-0 sd-mb-2 sd-ml-auto sd-mr-auto sd-shadow-none sd-text-center wistia-card docutils"&gt;
&lt;div class="sd-card-body docutils"&gt;
&lt;script src="https://fast.wistia.com/player.js" async&gt;&lt;/script&gt;&lt;script src="https://fast.wistia.com/embed/o7i778hm7v.js" async type="module"&gt;&lt;/script&gt;&lt;style&gt;wistia-player[media-id='o7i778hm7v']:not(:defined) { background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/o7i778hm7v/swatch'); display: block; filter: blur(5px); padding-top:57.92%; }&lt;/style&gt; &lt;wistia-player media-id="o7i778hm7v" seo="false" aspect="1.7266187050359711"&gt;&lt;/wistia-player&gt;
&lt;/div&gt;
&lt;div class="sd-card-footer docutils"&gt;
&lt;p class="sd-card-text"&gt;&lt;em&gt;Dask dashboard while processing satellite images loaded via &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;odc-stac&lt;/span&gt;&lt;/code&gt;. Data-loading tasks in the bottom right are queued rather than being scheduled immediately. Previously, Dask would schedule tens of thousands of tasks at once, overloading the scheduler.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This improved queuing mechanism removes these issues and provides a smoother and
more reliable user experience when working with large-scale datasets.&lt;/p&gt;
&lt;p&gt;There is no user-side change required. The new behavior is picked up automatically after
upgrading Dask.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
conda&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;conda&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt; &lt;span class="n"&gt;xarray&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
pip&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;upgrade&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dask[complete]&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;xarray&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For other recent improvements to using Xarray with Dask, you might consider:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/geospatial-benchmarks-2.html"&gt;&lt;span class="doc"&gt;Large Scale Geospatial Benchmarks: First Pass&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/dask-detrending.html"&gt;&lt;span class="doc"&gt;Improving GroupBy.map with Dask and Xarray&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/array-quantile.html"&gt;&lt;span class="doc"&gt;Faster Xarray Quantile Computations with Dask&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-array-queueing.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Mar 17, 2025</summary>
    <published>2025-03-17T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/2024-eoy.html</id>
    <title>Coiled 2024 in Review</title>
    <updated>2025-01-02T00:00:00+00:00</updated>
    <author>
      <name>Matthew Rocklin</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jan 2, 2025&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;12m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="coiled-2024-in-review"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://matthewrocklin.com/"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://matthewrocklin.com/_static/profile.jpg" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Matthew Rocklin&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Hello Users, Teammates, and General Well-Wishers!&lt;/p&gt;
&lt;p&gt;It’s that time of year where companies issue year-end summaries, looking back on the past twelve months, acclaiming success, and forecasting incredible growth for the next year.  I thought I’d do something similar for Coiled; it’s been quite a year for us.&lt;/p&gt;
&lt;p&gt;Tone here will be a bit more grounded/realistic than is typical for founders, which readers of my writing may find familiar. Let’s talk through where we are as a company, what we’ve been doing, and what we need to accomplish.&lt;/p&gt;
&lt;section id="how-did-we-start-2024"&gt;
&lt;h2&gt;How did we start 2024?&lt;/h2&gt;
&lt;p&gt;Coiled spent its first couple years (2020-2022) as a confused mess (we call this “the beforetimes” or “dark ages” internally). 2023 was the year when we got our shit together and built a decent product.  We did this by shutting down all other parts of the company (sales, marketing, HR, …) and focusing on product development, linking customer feedback and product direction directly to engineers.  This worked, and by the end of 2023, we had a delightful product and no ability to sell it.&lt;/p&gt;
&lt;p&gt;Here is an excerpt from an e-mail to the company summarizing 2023:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Today we have a good product.  It’s a product that I think we’re all proud of.  Kudos to us 🎉!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;We also have customers (not many yet) and a funnel that actually seems to convert eyeballs into users into dollars (although not well enough yet).  The company, today, seems to actually work.  Given where we started the year I’m impressed.  Thank you all for your efforts in this.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So in 2024 our biggest question changed:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;[x] &lt;s&gt;&lt;em&gt;Can we build a great product?&lt;/em&gt;&lt;/s&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;[ ] &lt;strong&gt;&lt;em&gt;Can we sell this thing?&lt;/em&gt; 🤔&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In 2024 we shifted our focus to figure out sales and marketing.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="sales"&gt;
&lt;h2&gt;Sales&lt;/h2&gt;
&lt;section id="where-we-started"&gt;
&lt;h3&gt;Where we started&lt;/h3&gt;
&lt;p&gt;Advice I got at this time:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advice:&lt;/strong&gt; Great job on product.  Time to hire a proven sales/marketing leader!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; Hell no.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I still felt burnt from the Coiled Dark Ages and was suspicious of Sales/Marketing VP types who had all the answers.  My experience was that experienced players come with more confidence than context in our space, and mostly cause noise and trouble.  My stance at the time was that early stage startups don’t work like other companies, and we’d be better off figuring out things for ourselves.  While intrepid, that thought process mostly didn’t work 🙂&lt;/p&gt;
&lt;p&gt;We started 2024 focusing on a self-serve motion.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User acquisition:&lt;/strong&gt; Content marketing and strategic placement of examples in highly trafficked OSS pages (turns out people click on examples more than on explicit mentions of SaaS products).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User conversion:&lt;/strong&gt; People would increase usage naturally until they exceeded the free tier and then put in a credit card&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This worked, but not well enough.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-we-did-and-how-it-worked"&gt;
&lt;h3&gt;What we did and how it worked&lt;/h3&gt;
&lt;p&gt;In particular we were better at building interest and success than at closing deals, so we decided to hire a couple of sales reps.  Here’s a blog post about this at the time: &lt;a class="reference external" href="https://matthewrocklin.com/hiring-sales.html"&gt;Hiring Sales&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finding reps that were good at operating in a chaotic environment with high uncertainty was hard.  Most sales reps want, you know …&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;A well-defined thing they can sell&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An ideal customer profile&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Knowledge about why that customer typically wants to buy it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A defined pricing scheme&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We had none of those things.  Finding people who are excited about uncertainty is hard but possible.  Eventually we found a couple and they started in June.  One survived our chaos and today we’re thrilled to have &lt;a class="reference external" href="https://www.linkedin.com/in/austin-brown-2a32a573/"&gt;Austin Brown&lt;/a&gt; in the family.  Austin was previously at FlatFile, and before that an entrepreneur himself with cold brew coffee.  (we really value both entrepreneurial experience and experience outside of tech).&lt;/p&gt;
&lt;p&gt;Today we sell things.  We had to figure a lot of stuff out (for example, see &lt;a class="reference external" href="https://matthewrocklin.com/usage-based-pricing.html"&gt;Pivoting away from Usage Based Pricing&lt;/a&gt;), and onboarding sales into a product-focused organization was tough, but today the company feels more balanced and general optimism is a lot higher than it has been.&lt;/p&gt;
&lt;p&gt;Our typical users / platform customers today fit into a few buckets&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;A bunch of free users (students, independent contractors, scientists, etc.) who we’ll probably never get money out of (but who we still feel good about serving, as it’s mostly free for us)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some pay-as-you-go customers paying us ~$1k/month&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Small teams paying around $25k-$50k / year&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Heavy users paying $100-200k / year&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A few larger enterprises paying more&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s a good mix.  We have great customers who give excellent feedback and from whom we’ve learned a great deal.  Our customers force us toward success, and we care about making them happy. For the most part I’m proud to think that we’ve achieved that.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-we-have-to-do-in-2025"&gt;
&lt;h3&gt;What we have to do in 2025&lt;/h3&gt;
&lt;p&gt;Today I still worry about sales, but more in a day-to-day operations way, rather than in a &lt;em&gt;“will we ever $&amp;#64;%ing make this work”&lt;/em&gt; way.  The transition from existential dread to quotidian grind is a delightful change.&lt;/p&gt;
&lt;p&gt;There’s still plenty to do to mechanize sales.  We spend non-trivial effort educating customers on the value we provide, how to use us well, and on how best to work our way through larger enterprise procurement cycles.  This is all hard but straightforward work and I’m confident we have the people in place who can do it.&lt;/p&gt;
&lt;p&gt;2025 in sales is about execution, tuning, and eventually expansion.  I’d love for us to be in a place where we could hire another AE in 6 months and grow beyond that in 12.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="marketing"&gt;
&lt;h2&gt;Marketing&lt;/h2&gt;
&lt;p&gt;Our marketing for most of 2024 was kinda meh. This is because when we hired sales we slowed down marketing; there was just too much work to onboard and align the new function in the company.  New user flow didn’t stop, it just didn’t grow as fast as would have been ideal.  We’ve refocused on this for the last couple months and are seeing glimmers of hope.&lt;/p&gt;
&lt;p&gt;Generally though, it turns out that marketing is hard, especially today.&lt;/p&gt;
&lt;p&gt;I mostly grew Dask’s awareness through four mechanisms:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;A personal blog&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Twitter&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Relationships with other PyData/SciPy core developers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Speaking at Python conferences&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That was ten years ago, and the same tricks don’t seem to work today.  The world has become a lot noisier since.&lt;/p&gt;
&lt;p&gt;We’re trying lots of things and generally getting smarter about marketing, but this is still a big unknown for us.  Figuring this out here is a key objective for us for 2025.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask"&gt;
&lt;h2&gt;Dask&lt;/h2&gt;
&lt;section id="id1"&gt;
&lt;h3&gt;Where we started&lt;/h3&gt;
&lt;aside class="system-message"&gt;
&lt;p class="system-message-title"&gt;System Message: INFO/1 (&lt;span class="docutils literal"&gt;/home/runnerx/actions-runner/_work/platform/platform/python-api/docs/source/blog/2024-eoy.md&lt;/span&gt;, line 123); &lt;em&gt;&lt;a href="#id1"&gt;backlink&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Duplicate implicit target name: “where we started”.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Two years ago Dask was well-loved but had stability issues.  It would break from time to time, which eroded confidence among larger users.  Those were mostly fixed in 2023.&lt;/p&gt;
&lt;p&gt;In 2024, Dask was stable, but still difficult to use at large scale.  This isn’t unusual, ask anyone who has driven Spark at the 10+TiB scale and you’ll hear similar stories.  Still though, as a company we target lots of small groups without deep distributed computing expertise.  We want to stretch the boundaries of allowing increasingly novice users to attempt increasingly complex workloads.  In 2024 we wanted to make Dask workloads smooth at the 10-100 TiB scale.&lt;/p&gt;
&lt;p&gt;This is especially hard in the Dask/PyData space because performance doesn’t just depend on Dask, but rather on the interplay of dozens of different libraries.  We weren’t developing Dask, we had to develop the ecosystem.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-we-did"&gt;
&lt;h3&gt;What we did&lt;/h3&gt;
&lt;p&gt;To address this we built, ran, and optimized large-scale benchmark suites that targeted full lifecycle workloads.&lt;/p&gt;
&lt;section id="dataframes"&gt;
&lt;h4&gt;Dataframes&lt;/h4&gt;
&lt;p&gt;We started with TPC-H, a standard benchmark suite for tabular/dataframe/SQL computations, and compared ourselves to DuckDB, Polars, and Spark between scales of 10 GiB to 10 TiB.  Over the course of several months and with many exciting new features (query optimization, parquet rewrite, shuffle rewrite, copy-on-write pandas memory semantics) we climbed to a point where Dask Dataframe was on-par across a variety of scales and exceeding the others at large scale.  For more information, see &lt;a class="reference external" href="https://docs.coiled.io/blog/tpch.html"&gt;DataFrames at Scale Comparison: TPC-H&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is great, and our dataframe customers are generally much happier on the new system today.  We also spend far less time supporting them.  Dask dataframe today “just works” far more often than it used to and is technically well positioned to displace Spark.&lt;/p&gt;
&lt;p&gt;However, we didn’t do a good job marketing this success (the dataframe space is noisy right now, and we were distracted by integrating the Sales function) and so we missed an opportunity here.  This might be something for us to come back to in 2025.&lt;/p&gt;
&lt;p&gt;We had a technical win here, but didn’t convert that into a business win.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="geo"&gt;
&lt;h4&gt;Geo&lt;/h4&gt;
&lt;p&gt;After wrapping up the dataframe technical effort the team switched to geospatial workloads (a non-trivial fraction of our current customer base deals with geospatial data).  Unfortunately, there was no standard benchmark in this space, so we started by building one. Here are some blogs on the subject:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/blog/geospatial-benchmarks.html"&gt;Large Scale Geospatial Benchmarks — Call for Benchmarks&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/blog/geospatial-benchmarks-2.html"&gt;Large Scale Geospatial Benchmarks — First Results&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/tree/main/tests/geospatial"&gt;Geospatial Benchmarks on GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This technical work is ongoing and includes work across Dask, Xarray, Zarr, GeoPandas, S3FS, and other libraries.  We’ve climbed from “works reliably on 10 GiB” to “works reliably on 100 GiB with improvements on 1 TiB” but still have more work to do as we climb to the 10-100 TiB scale.&lt;/p&gt;
&lt;p&gt;Geo is a fun place for us at our current stage; it’s a good place for us to figure ourselves out.  Geo is a tighter community where word travels fast, and without as much noise.  It’s been a fun vertical for the team to focus on.  It’s still early, but uptake and commercials look good.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="what-we-need-to-do-in-2025"&gt;
&lt;h3&gt;What we need to do in 2025&lt;/h3&gt;
&lt;p&gt;The Dask team at Coiled is delivering broad technical wins fairly reliably.  This is good for everyone.  Repeating for Geo what we did for Dataframes is a good focus for the first couple quarters of the year.&lt;/p&gt;
&lt;p&gt;However, we need to do a better job of connecting this technical success into commercial success.  For Geo we’re being more mindful of this than we were for dataframes.&lt;/p&gt;
&lt;p&gt;I hope that once the geo effort is feeling complete we can return to the topic of dataframes and stride into that conflict more purposefully, and with more maturity.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="cloud-platform"&gt;
&lt;h2&gt;Cloud Platform&lt;/h2&gt;
&lt;p&gt;The product is good.  Usage is up.  Service is stable.  People have a great time.&lt;/p&gt;
&lt;p&gt;We’re thinking about where to go from here.&lt;/p&gt;
&lt;section id="usage-is-up"&gt;
&lt;h3&gt;Usage is up&lt;/h3&gt;
&lt;p&gt;Here’s a chart showing usage, grouped by organization, over the last twelve months (each block is a different org).&lt;/p&gt;
&lt;figure class="align-default" id="id2"&gt;
&lt;img alt="https://docs.coiled.io/_images/usage-2024.png" src="https://docs.coiled.io/_images/usage-2024.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Compute hours managed by Coiled over time, grouped by organization.  Growth is
steady. There are a few large organizations and many many small ones.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;More importantly though, we now charge for more than just usage (see &lt;a class="reference external" href="https://matthewrocklin.com/usage-based-pricing.html"&gt;Pivoting away from Usage Based Pricing&lt;/a&gt;) and so revenue is no longer tied to this chart (it’s quite a bit better).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="whats-next"&gt;
&lt;h3&gt;What’s next?&lt;/h3&gt;
&lt;p&gt;Coiled, originally conceived as a Dask SaaS product, accidentally became a great system to run general purpose computing tasks.  It turns out that to launch Dask clusters, you first need to figure out how to launch general Python jobs in the cloud, and we did that well, arguably better than many cloud services.&lt;/p&gt;
&lt;p&gt;This presents some open opportunities we’re considering:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;More Dask UX&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;More charts, more automated warnings, automated hardware selection, etc..&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Jupyter Notebooks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Almost every Jupyter Notebook startup that has gone down this route has failed (excepting Hex?), but who knows, we might fare better.  The number of users and institutions interested in this feature (despite the flood of startups offering it) is unnervingly high.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Batch jobs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We &lt;a class="reference external" href="https://docs.coiled.io/user_guide/batch.html"&gt;recently released&lt;/a&gt; a feature letting people run arbitrary programs (not just Dask or even Python) on Coiled, competing with tools like AWS/Azure Batch.  This took a couple days to put together and has had surprisingly positive response in a short time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prefect serverless&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.prefect.io/v3/deploy/infrastructure-examples/serverless"&gt;Prefect’s serverless push work pool&lt;/a&gt; ends up being a surprisingly nice match for Coiled and would be of use to a non-trivial fraction of our current customers.&lt;/p&gt;
&lt;p&gt;Probably we could do something with Dagster/Airflow as well.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’m torn between focusing on fine-tuning our already strong Dask experience, or broadening out to “general-purpose easy-to-use large-scale computing”.  Usually I like solving focused problems (like Dask), and view the general purpose problem to be more the purview of the clouds themselves.  However, after engaging with their offerings (AWS Batch, Lambda, Sagemaker, etc.) my sense is that they’re not serving the Python data community well, mostly because of a developer experience that doesn’t fit well with the taste of most data developers.  (See post &lt;a class="reference external" href="https://matthewrocklin.com/cloud-is-broken.html"&gt;Cloud Computing is Broken&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The way we answer this question is likely by observing users.  Today our users use Coiled for all sorts of things we didn’t expect, including:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Running geophysical inversion codes to detect where to mine for minerals&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Starting up Jupyter notebooks for interactive GPU computation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Augmenting workflow managers like Prefect and Dagster with large machines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bulk conversion of millions of geotiff images&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Benchmarking GPU codes on all possible GPU types available on the cloud&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It ends up being easier to misuse Coiled beyond its intended purpose than to use the traditional tools created by the commercial clouds.  This atypical usage isn’t currently enough of our revenue to pivot the company today, but it’s an enticing possibility for the future, and one that we’ll likely explore with baby steps in 2025.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="funding"&gt;
&lt;h2&gt;Funding&lt;/h2&gt;
&lt;p&gt;Ever since the Dark Ages we’ve run a lean shop and so our runway is both generous and growing now that we’ve unlocked sales.  I have no interest today in taking on additional outside capital.&lt;/p&gt;
&lt;p&gt;Internally though, we should probably start to hire.  I think that we have a better idea of how to effectively put our existing capital to use and it’s time to (very gently) push on the gas.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="personal"&gt;
&lt;h2&gt;Personal&lt;/h2&gt;
&lt;p&gt;In my e-mail to the company at the end of 2023 I wrote the following:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Personally I find this job to be challenging (I don’t think of myself as being naturally suited to this role) but there have been times in the last few months when I’ve actually found myself enjoying it.  Thank you all for that.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The trend away from “Leading a startup feels like chewing glass” and towards “I could maybe enjoy this job some day” has continued.  I find myself significantly more calm today than I was a year ago.  Much of this has to do with everyone in the company growing themselves and learning how to work together more effectively.&lt;/p&gt;
&lt;p&gt;Working with smart people who share a common mission makes all the difference.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;We’re doing ok.  In 2024 we unblocked sales and accumulated enough additional exposure to user pain that we have some ideas that might be good.&lt;/p&gt;
&lt;p&gt;In 2025 if all we do is execute and tune and mechanize then that’ll be fine.  However, I’m excited about increasing creativity, especially in marketing and a broadening of our product offering.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/2024-eoy.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jan 2, 2025</summary>
    <published>2025-01-02T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/array-quantile.html</id>
    <title>Faster Xarray Quantile Computations with Dask</title>
    <updated>2024-12-17T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Dec 17, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;2m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="faster-xarray-quantile-computations-with-dask"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/phofl"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/61934744?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Patrick Hoefler&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;There have been a number of engineering &lt;a class="reference external" href="https://docs.dask.org/en/stable/changelog.html#v2024-11-2"&gt;improvements to Dask Array&lt;/a&gt; like consistent chunksizes in Xarray rolling-constructs and improved efficiency in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map_overlap&lt;/span&gt;&lt;/code&gt;. Notably, as of Dask version 2024.11.2, calculating quantiles is much faster and more reliable.&lt;/em&gt;&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Screencast of the Dask dashboard running a quantile computation." src="https://docs.coiled.io/_images/quantile-fast.gif" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Dask dashboard of the new quantile implementation, which is ~20x faster for this microbenchmark.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;section id="calculating-quantiles-with-xarray"&gt;
&lt;h2&gt;Calculating Quantiles with Xarray&lt;/h2&gt;
&lt;p&gt;Calculating quantiles is a common operation for geospatial data. Quantiles show how a dataset is distributed over time, allowing you to identify trends, anomalies, and variation within specific grid cells or regions. These calculations are typically performed either for distinct groups within the dataset or across the dataset as a whole.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-problem-np-quantile-can-be-slow"&gt;
&lt;h2&gt;The Problem: &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;np.quantile&lt;/span&gt;&lt;/code&gt; Can Be Slow&lt;/h2&gt;
&lt;p&gt;The data we often encounter typically has a relatively short time axis, consisting of only a few hundred to a few thousand values, while the latitude and longitude dimensions are extensive.&lt;/p&gt;
&lt;p&gt;Previously, the quantile calculation for every coordinate was done on a pretty small array with the NumPy implementation &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;np.quantile&lt;/span&gt;&lt;/code&gt; or &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;np.nanquantile&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask lacked an efficient efficient multidimensional implementation for calculating quantiles, which meant calling the one-dimensional NumPy implementation millions of times in Python. This is very slow and also blocks the GIL (looking forward to a free-threaded Python world 😅). This caused large slowdowns on workers with more than one thread and could lead to runtimes over 200s per chunk.&lt;/p&gt;
&lt;p&gt;Running the following computation previously took over 3 minutes to complete:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xarray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xr&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;da&lt;/span&gt;

&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3_000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;auto&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;auto&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;darr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dims&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;darr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sd-card sd-sphinx-override sd-w-75 sd-mt-0 sd-mb-2 sd-ml-auto sd-mr-auto sd-shadow-none sd-text-center wistia-card docutils"&gt;
&lt;div class="sd-card-body docutils"&gt;
&lt;script src="https://fast.wistia.com/player.js" async&gt;&lt;/script&gt;&lt;script src="https://fast.wistia.com/embed/9erk2qeo5l.js" async type="module"&gt;&lt;/script&gt;&lt;style&gt;wistia-player[media-id='9erk2qeo5l']:not(:defined) { background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/9erk2qeo5l/swatch'); display: block; filter: blur(5px); padding-top:72.08%; }&lt;/style&gt; &lt;wistia-player media-id="9erk2qeo5l" seo="false"&gt;&lt;/wistia-player&gt;
&lt;/div&gt;
&lt;div class="sd-card-footer docutils"&gt;
&lt;p class="sd-card-text"&gt;&lt;em&gt;Computing quantiles used to be painfully slow. The computation takes ~3 minutes to complete.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="the-solution-a-new-dask-array-quantile"&gt;
&lt;h2&gt;The Solution: A New &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.array.quantile&lt;/span&gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;As of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask=2024.11.2&lt;/span&gt;&lt;/code&gt;, we’ve added a high-level quantile API to Dask that uses top level NumPy functions to extract the quantile for each time slice. The operations are more expensive than an optimal quantile implementation, but the vectorized, multidimensional nature of each call makes it a lot faster in aggregate. And we don’t block the GIL anymore, so you can run with proper parallelism on your Dask workers.&lt;/p&gt;
&lt;div class="sd-card sd-sphinx-override sd-w-75 sd-mt-0 sd-mb-2 sd-ml-auto sd-mr-auto sd-shadow-none sd-text-center wistia-card docutils"&gt;
&lt;div class="sd-card-body docutils"&gt;
&lt;script src="https://fast.wistia.com/player.js" async&gt;&lt;/script&gt;&lt;script src="https://fast.wistia.com/embed/lq8sj9yaf1.js" async type="module"&gt;&lt;/script&gt;&lt;style&gt;wistia-player[media-id='lq8sj9yaf1']:not(:defined) { background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/lq8sj9yaf1/swatch'); display: block; filter: blur(5px); padding-top:72.08%; }&lt;/style&gt; &lt;wistia-player media-id="lq8sj9yaf1" seo="false"&gt;&lt;/wistia-player&gt;
&lt;/div&gt;
&lt;div class="sd-card-footer docutils"&gt;
&lt;p class="sd-card-text"&gt;&lt;em&gt;Using the new implementation, this same microbenchmark takes ~9 seconds, a 20x speedup.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The new quantile implementation reduces runtime to ~1s per chunk, independent of the number of threads. This means we’re able to calculate quantiles hundreds of times faster than before! You can expect the speedups to scale with the size of your quantile axis.&lt;/p&gt;
&lt;p&gt;You can try out the new implementation by upgrading to the latest versions of Dask and Xarray.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
conda&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;conda&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt; &lt;span class="n"&gt;xarray&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
pip&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;upgrade&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dask[complete]&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;xarray&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For other recent improvements to using Xarray with Dask, you might consider:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/dask-detrending.html"&gt;&lt;span class="doc"&gt;Improving GroupBy.map with Dask and Xarray&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/geospatial-benchmarks-2.html"&gt;&lt;span class="doc"&gt;Large Scale Geospatial Benchmarks: First Pass&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/array-quantile.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Dec 17, 2024</summary>
    <published>2024-12-17T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-detrending.html</id>
    <title>Improving GroupBy.map with Dask and Xarray</title>
    <updated>2024-11-21T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Nov 21, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="improving-groupby-map-with-dask-and-xarray"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/phofl"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/61934744?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Patrick Hoefler&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;This post was originally published on the &lt;a class="reference external" href="https://xarray.dev/blog/dask-detrending"&gt;Xarray blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Running large-scale GroupBy-Map patterns with Xarray that are backed by &lt;a class="reference external" href="https://docs.dask.org/en/stable/array.html"&gt;Dask arrays&lt;/a&gt; is
an essential part of a lot of typical geospatial workloads. Detrending is a very common
operation where this pattern is needed.&lt;/p&gt;
&lt;p&gt;In this post, we will explore how and why this caused so many pitfalls for Xarray users in
the past and how we improved performance and scalability with a few changes to how Dask
subselects data.&lt;/p&gt;
&lt;section id="what-is-groupby-map"&gt;
&lt;h2&gt;What is GroupBy.map?&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.xarray.dev/en/stable/generated/xarray.core.groupby.DatasetGroupBy.map.html"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;GroupBy.map&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; lets you apply a User Defined Function (UDF)
that accepts and returns Xarray objects. The UDF will receive an Xarray object (either a Dataset or a DataArray) containing Dask arrays corresponding to one single group.
&lt;a class="reference external" href="https://docs.xarray.dev/en/stable/generated/xarray.core.groupby.DatasetGroupBy.reduce.html"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Groupby.reduce&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; is quite similar
in that it applies a UDF, but in this case the UDF will receive the underlying Dask arrays, &lt;em&gt;not&lt;/em&gt; Xarray objects.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-application"&gt;
&lt;h2&gt;The Application&lt;/h2&gt;
&lt;p&gt;Consider a typical workflow where you want to apply a detrending step. You may want to smooth out
the data by removing the trends over time. This is a common operation in climate science
and normally looks roughly like this:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;detrending_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DataArray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DataArray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# important: the rolling operation is applied within a group&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rolling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_periods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time.dayofyear&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;detrending_step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We are grouping by the day of the year and then are calculating the rolling average over
30-year windows for a particular day.&lt;/p&gt;
&lt;p&gt;Our example will run on a 1 TiB array, 64 years worth of data and the following structure:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Python repr output of 1 TiB Dask array with shape (1801, 3600, 233376) split into 5460, 250 MiB chunks of (300, 300, 365)" src="https://docs.coiled.io/_images/input-array.png" /&gt;&lt;/p&gt;
&lt;p&gt;The array isn’t overly huge and the chunks are reasonably sized.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-problem"&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The general application seems straightforward. Group by the day of the year and apply a UDF
to every group. There are a few pitfalls in this application that can make the result of
this operation unusable. Our array is sorted by time, which means that we have to pick
entries from many different areas in the array to create a single group (corresponding to a single day of the year).
Picking the same day of every year is basically a slicing operation with a step size of 365.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Schematic showing an array sorted by time, where data is selected from many different areas in the array to create a single group (corresponding to a specific day of the year)." src="https://docs.coiled.io/_images/indexing-data-selection.png" /&gt;&lt;/p&gt;
&lt;p&gt;Our example has a year worth of data in a single chunk along the time axis. The general problem
exists for any workload where you have to access random entries of data. This
particular access pattern means that we have to pick one value per chunk, which is pretty
inefficient. The right side shows the individual groups that we are operating on.&lt;/p&gt;
&lt;p&gt;One of the main issues with this pattern is that Dask will create a single output chunk per time
entry, e.g. each group will consist of as many chunks as we have year.&lt;/p&gt;
&lt;p&gt;This results in a huge increase in the number of chunks:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Python repr output of a 1 TiB Dask array with nearly 2 million, 700 kiB chunks." src="https://docs.coiled.io/_images/output-array-old.png" /&gt;&lt;/p&gt;
&lt;p&gt;This simple operation increases the number of chunks from 5000 to close to 2 million. Each
chunk only has a few hundred kilobytes of data. &lt;strong&gt;This is pretty bad!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dask computations generally scale along the number of chunks you have. Increasing the chunks by such
a large factor is catastrophic. Each follow-up operation, as simple as &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;a-b&lt;/span&gt;&lt;/code&gt; will create 2 million
additional tasks.&lt;/p&gt;
&lt;p&gt;The only workaround for users was to rechunk to something more sensible afterward, but it
still keeps the incredibly expensive indexing operation in the graph.&lt;/p&gt;
&lt;p&gt;Note this is the underlying problem that is &lt;a class="reference external" href="https://xarray.dev/blog/flox"&gt;solved by flox&lt;/a&gt; for aggregations like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.mean()&lt;/span&gt;&lt;/code&gt;
using parallel-native algorithms to avoid the expense of indexing out each group.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="improvements-to-the-data-selection-algorithm"&gt;
&lt;h2&gt;Improvements to the Data Selection algorithm&lt;/h2&gt;
&lt;p&gt;The method of how Dask selected the data was objectively pretty bad.
A rewrite of the underlying algorithm enabled us to achieve a much more robust result. The new
algorithm is a lot smarter about how to pick values from each individual chunk, but most importantly,
it will try to preserve the input chunksize as closely as possible.&lt;/p&gt;
&lt;p&gt;For our initial example, it will put every group into a single chunk. This means that we will
end up with the number of chunks along the time axis being equal to the number of groups, i.e. 365.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Python repr output of a 1 TiB Dask array with 31164, 43 MiB chunks" src="https://docs.coiled.io/_images/output-array-new.png" /&gt;&lt;/p&gt;
&lt;p&gt;The algorithm reduces the number of chunks from 2 million to roughly 30 thousand, which is a huge improvement
and a scale that Dask can easily handle. The graph is now much smaller, and the follow-up operations
will run a lot faster as well.&lt;/p&gt;
&lt;p&gt;This improvement will help every operation that we listed above and make the scale a lot more
reliably than before. The algorithm is used very widely across Dask and Xarray and thus, influences
many methods.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-s-next"&gt;
&lt;h2&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;Xarray selects one group at a time for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;groupby(...).map(...)&lt;/span&gt;&lt;/code&gt;, i.e. this requires one operation
per group. This will hurt scalability if the dataset has a very large number of groups, because
the computation will create a very expensive graph. There is currently an effort to implement alternative
APIs that are shuffle-based to circumvent that problem. A current PR is available &lt;a class="reference external" href="https://github.com/pydata/xarray/pull/9320"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The fragmentation of the output chunks by indexing is something that will hurt every workflow that is selecting data in a random
pattern. This also includes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.sel&lt;/span&gt;&lt;/code&gt; if you aren’t using slices explicitly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.isel&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.sortby&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;groupby(...).quantile()&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and many more.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We expect all of these workloads to be substantially improved now.&lt;/p&gt;
&lt;p&gt;Additionally, &lt;a class="reference external" href="https://docs.dask.org/en/stable/changelog.html#v2024-11-1"&gt;Dask improved a lot of things&lt;/a&gt; related to either increasing chunksizes or fragmentation
of chunks over the cycle of a workload with more improvements to come. This will help a lot of
users to get better and more reliable performance.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-detrending.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Nov 21, 2024</summary>
    <published>2024-11-21T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/slurm-job-arrays.html</id>
    <title>SLURM-Style Job Arrays on the Cloud with Coiled</title>
    <updated>2024-11-19T00:00:00+00:00</updated>
    <author>
      <name>Matthew Rocklin</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Nov 19, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="slurm-style-job-arrays-on-the-cloud-with-coiled"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://matthewrocklin.com/"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://matthewrocklin.com/_static/profile.jpg" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Matthew Rocklin&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Update Jan. 9th, 2025: We’ve added &lt;a class="reference external" href="https://docs.coiled.io/user_guide/batch.html"&gt;documentation&lt;/a&gt; for Coiled Batch.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;SLURM and other job schedulers have made it easy to run scripts on HPC systems for decades.  Job scripts are straightforward and solve a common problem in a way that is familiar to researchers who have had exposure to HPC systems.  Heck, I personally used them as a graduate student long before I knew anything about parallel computing.&lt;/p&gt;
&lt;p&gt;Unfortunately, the cloud doesn’t offer anything similar with the same level of ease.  (It’s oddly difficult to do this on the cloud).  We wanted to fix that, and so just released this new feature with Coiled reinventing the SLURM-style job script for the cloud.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: we just pushed this out last week and it’s not polished yet, doesn’t have docs, etc..  It felt easy enough though that we felt safe talking about it early in hope of getting more feedback.&lt;/em&gt;&lt;/p&gt;
&lt;section id="example-hello-world"&gt;
&lt;h2&gt;Example: Hello world!&lt;/h2&gt;
&lt;p&gt;We’ll give lots more examples &lt;a class="reference internal" href="#cloud-job-script-examples"&gt;below&lt;/a&gt; but for now let’s do a trivial Hello world example parallel job script&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Coiled batch script in a terminal window" src="https://docs.coiled.io/_images/coiled-batch-run-hello.png" style="width: 90%;" /&gt;
&lt;/figure&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled batch run hello.sh
Running job array of 10 tasks on 10 VMs...
Cluster Details: https://cloud.coiled.io/clusters/661309?account=dask-engineering

To check status, run:
  coiled batch status 661309

$ coiled batch status 661309
                    Batch Jobs for Cluster 661309 (stopped)
                               Command: hello.sh
┏━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━┓
┃          ┃ Assigned   ┃       ┃ Start     ┃           ┃          ┃           ┃
┃ Array ID ┃ To         ┃ State ┃ Time      ┃ Stop Time ┃ Duration ┃ Exit Code ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━┩
│ 0        │ 10.0.46.1… │ done  │ 15:59:46  │ 15:59:46  │ 0s       │ 0         │
│ 1        │ 10.0.46.1… │ done  │ 15:59:46  │ 15:59:46  │ 0s       │ 0         │
│ 2        │ 10.0.46.1… │ done  │ 15:59:46  │ 15:59:46  │ 0s       │ 0         │
│ 3        │ 10.0.46.1… │ done  │ 15:59:46  │ 15:59:47  │ 0s       │ 0         │
│ 4        │ 10.0.46.1… │ done  │ 15:59:47  │ 15:59:47  │ 0s       │ 0         │
│ 5        │ 10.0.46.1… │ done  │ 15:59:47  │ 15:59:47  │ 0s       │ 0         │
│ 6        │ 10.0.46.1… │ done  │ 15:59:47  │ 15:59:47  │ 0s       │ 0         │
│ 7        │ 10.0.46.1… │ done  │ 15:59:48  │ 15:59:48  │ 0s       │ 0         │
│ 8        │ 10.0.37.50 │ done  │ 15:59:48  │ 15:59:48  │ 0s       │ 0         │
│ 9        │ 10.0.46.1… │ done  │ 15:59:48  │ 15:59:48  │ 0s       │ 0         │
└──────────┴────────────┴───────┴───────────┴───────────┴──────────┴───────────┘
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And we can easily go and get logs from all the machines at once&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled logs 661309 | grep Hello
2024-11-19 15:59:46.131000 Hello from 0 !
2024-11-19 15:59:46.419000 Hello from 1 !
2024-11-19 15:59:46.743000 Hello from 2 !
2024-11-19 15:59:47.019000 Hello from 3 !
2024-11-19 15:59:47.324000 Hello from 4 !
2024-11-19 15:59:47.587000 Hello from 5 !
2024-11-19 15:59:47.862000 Hello from 6 !
2024-11-19 15:59:48.256000 Hello from 7 !
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="what-does-this-do"&gt;
&lt;h2&gt;What Does This Do?&lt;/h2&gt;
&lt;p&gt;We …&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inspect your script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spin up appropriate machines as defined by the comments (this takes about 30s)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Download your software onto them (maybe another 30-60s)&lt;/p&gt;
&lt;p&gt;(in this case a Docker image, but &lt;a class="reference external" href="https://docs.coiled.io/user_guide/why.html#how-environment-synchronization"&gt;Coiled’s environment synchronization&lt;/a&gt; works here too)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run your script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shut down the machines.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your script can do anything.  It doesn’t have to be Dask related, or even Python related (indeed the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;echo&lt;/span&gt;&lt;/code&gt; program used above predates the Python language).&lt;/p&gt;
&lt;p&gt;This is very simple, but also very powerful.  Let’s look at a few more examples:&lt;/p&gt;
&lt;/section&gt;
&lt;section id="cloud-job-script-examples"&gt;
&lt;span id="id1"&gt;&lt;/span&gt;&lt;h2&gt;Cloud Job Script Examples&lt;/h2&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
Hello (bash)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Our example from above, for completeness’ sake.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c1"&gt;#COILED n-tasks     10&lt;/span&gt;
&lt;span class="c1"&gt;#COILED memory      8 GiB&lt;/span&gt;
&lt;span class="c1"&gt;#COILED container   ubuntu:latest&lt;/span&gt;
&lt;span class="c1"&gt;#COILED region      us-east-2&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Hello&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$COILED_BATCH_TASK_ID&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
Hello (python)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;We can submit python scripts too&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;
&lt;span class="c1"&gt;#COILED n-tasks     10&lt;/span&gt;
&lt;span class="c1"&gt;#COILED memory      8 GiB&lt;/span&gt;
&lt;span class="c1"&gt;#COILED region      us-east-2&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello from&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;COILED_BATCH_TASK_ID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here we also drop the container directive, and rely on Coiled’s environment synchronization to copy all of our Python libraries to the remote machines automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-2" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-2"&gt;
Process Many Files&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;A common pattern is to list many files, get the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;i&lt;/span&gt;&lt;/code&gt;th file, and run some command on that file.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;
&lt;span class="c1"&gt;#COILED n-tasks     1000&lt;/span&gt;
&lt;span class="c1"&gt;#COILED memory      8 GiB&lt;/span&gt;
&lt;span class="c1"&gt;#COILED region      us-east-2&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;s3fs&lt;/span&gt;

&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S3FileSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Get the i&amp;#39;th file to process&lt;/span&gt;
&lt;span class="n"&gt;task_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;COILED_BATCH_TASK_ID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my-bucket&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Process that file&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Store result&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# put result back in S3 somewhere&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-3" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-3"&gt;
GPUs&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;We can ask for any hardware on any cloud, including a collection of GPUs machines&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;
&lt;span class="c1"&gt;#COILED n-tasks     100&lt;/span&gt;
&lt;span class="c1"&gt;#COILED vm-type     g5.xlarge&lt;/span&gt;
&lt;span class="c1"&gt;#COILED region      us-east-2&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When you use a GPU machine with Coiled’s &lt;a class="reference external" href="https://docs.coiled.io/user_guide/why.html#how-environment-synchronization"&gt;environment sync&lt;/a&gt; feature, it will automatically install GPU equivalents to local packages like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pytorch&lt;/span&gt;&lt;/code&gt;, along with all the necessary NVIDIA CUDA drivers.  So you can develop locally on your CPU or Apple hardware:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ python train.py               # Uses your local CPU
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And when you’re ready to run at full scale you can launch your script to run on many GPU machines:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled batch run train.py     # Uses many cloud GPUs
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-4" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-4"&gt;
Video&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/cRWXfWgb4Z0?si=hFLox3U67Um8iZx_" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="status-and-logs"&gt;
&lt;h2&gt;Status and Logs&lt;/h2&gt;
&lt;p&gt;Just like on HPC, it’s easy to get the status of all jobs in your Coiled workspace.
This also includes the approximate cloud cost for each job (we’ve seen people be curious about this in the past).&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled batch status
┏━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
┃ Cluster ID ┃  State  ┃ Tasks Done ┃           Submitted ┃            Finished ┃ Approx Cloud Cost ┃ User Command        ┃
┡━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
│ 661309     │  done   │   10 /  10 │            15:58:50 │            15:59:48 │             $0.01 │ hello.sh            │
│ 659567     │  done   │   10 /  10 │ 2024-11-18 17:25:08 │ 2024-11-18 17:26:07 │             $0.01 │ hello.sh            │
│ 658315     │  done   │   10 /  10 │ 2024-11-17 16:24:27 │ 2024-11-17 16:25:20 │             $0.01 │ hello.sh            │
│ 658292     │  done   │   20 /  20 │ 2024-11-17 15:28:44 │ 2024-11-17 15:29:39 │             $0.02 │ hello.sh            │
│ 658288     │  done   │    0 /  20 │ 2024-11-17 15:24:32 │                     │             $0.02 │ hello.sh            │
│ 657615     │  done   │    0 /  10 │ 2024-11-16 20:32:46 │                     │             $0.01 │ hello.sh            │
│ 656571     │  done   │   10 /  10 │ 2024-11-15 14:32:35 │ 2024-11-15 14:33:35 │             $0.01 │ hello.sh            │
│ 655848     │  done   │   20 /  20 │ 2024-11-14 23:56:06 │ 2024-11-14 23:58:01 │             $0.01 │ echo &amp;#39;Hello, world&amp;#39; │
└────────────┴─────────┴────────────┴─────────────────────┴─────────────────────┴───────────────────┴─────────────────────┘
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Shoutout to Python’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;rich&lt;/span&gt;&lt;/code&gt; library for the nice formatting 🙂&lt;/p&gt;
&lt;p&gt;And while you can always look at jobs on the Cloud UI, we also provide a convenient way to get logs given a cluster ID
(this is true of all Coiled jobs)&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled logs 661309 | grep Hello
2024-11-19 15:59:46.131000 Hello from 0 !
2024-11-19 15:59:46.419000 Hello from 1 !
2024-11-19 15:59:46.743000 Hello from 2 !
2024-11-19 15:59:47.019000 Hello from 3 !
2024-11-19 15:59:47.324000 Hello from 4 !
2024-11-19 15:59:47.587000 Hello from 5 !
2024-11-19 15:59:47.862000 Hello from 6 !
2024-11-19 15:59:48.256000 Hello from 7 !
2024-11-19 15:59:48.315000 Hello from 8 !
2024-11-19 15:59:48.508000 Hello from 9 !
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="making-common-things-easy"&gt;
&lt;h2&gt;Making Common Things Easy&lt;/h2&gt;
&lt;p&gt;This kind of embarrassingly parallel workload is super common.&lt;/p&gt;
&lt;p&gt;We saw Python users struggling with the services provided by cloud providers like AWS Batch, GCP Cloud Run, or Azure Batch and thought that this little utility on top of Coiled might make it easier for these users get use out of the cloud.&lt;/p&gt;
&lt;p&gt;We hope you like it!  As always, if you want to try out Coiled, you can get started at &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;coiled.io/start&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/slurm-job-arrays.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Nov 19, 2024</summary>
    <published>2024-11-19T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/airflow-dask.html</id>
    <title>Airflow, Dask, &amp; Coiled: Adding Big Data Processing to Your Cloud Toolkit</title>
    <updated>2024-11-12T00:00:00+00:00</updated>
    <author>
      <name>Franco Bosetti</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Nov 12, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;6m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="airflow-dask-coiled-adding-big-data-processing-to-your-cloud-toolkit"&gt;

&lt;figure class="align-left"&gt;
&lt;img alt="Siemens logo" src="https://docs.coiled.io/_images/siemens-logo.png" style="width: 125px;" /&gt;
&lt;/figure&gt;
&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/stephen-schneider-04a556101/"&gt;Stephen Schneider&lt;/a&gt;, &lt;a class="reference external" href="https://www.linkedin.com/in/franco-bosetti-22a977143/"&gt;Franco Bosetti&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;In this article, we showcase how Coiled technology enabled our data engineering and analytics team at Siemens to tackle complex data processing challenges. For an internal customer, we needed to analyze a large dataset of training records for an employee learning platform. By seamlessly integrating with our existing Python code and allowing us to deploy scalable &lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; clusters in the cloud, Coiled reduced our ETL execution time by over 80%. This optimization not only benefited our internal Siemens customers, but also expanded our own data engineering portfolio for future projects.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Schematic using Airflow with Coiled and Dask to parallelize ETL workflows on the cloud." src="https://docs.coiled.io/_images/airflow-dask.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;We run Airflow locally and use Coiled to easily start a remote Dask cluster on AWS.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;As a data engineering and analytics team that works mainly in AWS, we frequently look to expand our technical toolkit and the portfolio of services we offer our customers. Besides using AWS Lambda functions for small event driven tasks, such as file deliveries and ingestions, we recently migrated to Apache Airflow as our main ETL tool.&lt;/p&gt;
&lt;p&gt;We typically use Airflow &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;PythonOperators&lt;/span&gt;&lt;/code&gt; to connect to various systems and perform tasks in a designed ETL workflow called a DAG. These tasks could be fetching data, pulling that data into a pandas DataFrame, and then pushing that DataFrame to a target destination. For efficiency, we execute most of our heavy ETL logic (data cleansing, joins, aggregations, calculations, etc.) in SQL and rely on our databases to handle the manipulation of large datasets.&lt;/p&gt;
&lt;p&gt;Recently, however, we encountered a project where we needed to use a fuzzy algorithm to identify patterns and correlations in a dataset of employee training records. This algorithm is too complex to perform using BI tools or SQL. We needed to execute our logic directly in Python using a specific library and the resources available in our Airflow environment. For simple logic and small to medium sized datasets, this would usually not be a problem. In this case, however, applying the algorithm to a large dataset was very time consuming. Even after provisioning additional resources to Airflow, our job took at least an hour.&lt;/p&gt;
&lt;p&gt;This was not optimal for our customer. We needed a different solution for processing big data more efficiently.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="aws-glue-vs-emr-vs-coiled"&gt;
&lt;h2&gt;AWS Glue vs. EMR vs. Coiled&lt;/h2&gt;
&lt;p&gt;After consulting our wise mentor ChatGPT, we determined we had a few options. In AWS some popular services for processing big data include AWS Glue and Elastic MapReduce (EMR) with Apache Spark. After some research, we realized most of these solutions involved a steep learning curve: managing new infrastructure, and/or essentially completely rewriting our existing Python code. After ruling out these options, another solution caught our eye: Coiled. ChatGPT summarized Coiled as “a managed Dask service that allows you to deploy Dask clusters in the cloud without the need to manage the underlying infrastructure.”&lt;/p&gt;
&lt;p&gt;Not worrying about infrastructure sounded enticing. But would we have to rewrite our Python code to work with the Dask library? We pressed ChatGPT again. It assured us that since Dask DataFrame mimics pandas, we could easily scale to large datasets with minimal code changes.&lt;/p&gt;
&lt;p&gt;This sounded too good to be true. We could use Coiled without having to worry about managing infrastructure in AWS and we wouldn’t need to completely rewrite our existing code. If this characterization of Coiled was accurate, we worried this could be expensive. Luckily, Coiled provides a &lt;a class="reference external" href="https://coiled.io/pricing"&gt;free tier&lt;/a&gt; that was enough to cover our needs.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="getting-started-with-coiled"&gt;
&lt;h2&gt;Getting started with Coiled&lt;/h2&gt;
&lt;p&gt;We decided to give Coiled a shot.&lt;/p&gt;
&lt;p&gt;We began by connecting our AWS account to Coiled. The setup in our AWS environment was simple; Coiled offers a link to CloudFormation to create a preformatted stack of the resources. This stack now allowed Coiled to create Dask clusters on our behalf. We then generated a new API token for authorizing our Airflow environment.&lt;/p&gt;
&lt;section id="creating-the-coiled-software-environment"&gt;
&lt;h3&gt;Creating the Coiled Software Environment&lt;/h3&gt;
&lt;p&gt;When you start a Coiled cluster and do not specify any packages, the Coiled environment &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/sync.html"&gt;mirrors the library versions that exist in your local environment by default&lt;/a&gt;. It’s easier and faster than using Docker, and meant that we didn’t have to worry about Python package versions.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="authentication"&gt;
&lt;h3&gt;Authentication&lt;/h3&gt;
&lt;p&gt;To authenticate Coiled from Airflow, we set the Coiled token with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.config.set({&amp;quot;coiled.token&amp;quot;:&lt;/span&gt; &lt;span class="pre"&gt;&amp;lt;our-token&amp;gt;})&lt;/span&gt;&lt;/code&gt;. We also set the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;/code&gt;, and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;/code&gt; environment variables to allow Coiled to interact with AWS resources on our behalf.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="coiled-cluster"&gt;
&lt;h3&gt;Coiled Cluster&lt;/h3&gt;
&lt;p&gt;Starting the cluster is as easy as calling &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.Cluster()&lt;/span&gt;&lt;/code&gt;, however, there are a number of customization options that allow us to tailor the cluster to our specific workload, optimizing both performance and cost. There are no standard rules here, so we tested different configurations to see how quickly our job ran using a varying number of workers and instance types in the cluster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c5.large&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="c1"&gt;# VM for compute-intensive workloads&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;       &lt;span class="c1"&gt;# Connect to cluster&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;                                 &lt;span class="c1"&gt;# Dask computations&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                  &lt;span class="c1"&gt;# Cleanup resources&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Once the cluster is set up, we connect to it. This connection is necessary to submit tasks and manage computations across the cluster. The client acts as a gateway between our Airflow environment and the distributed Dask workers.&lt;/p&gt;
&lt;p&gt;Once all tasks are completed, we shutdown the cluster (Coiled will also do this automatically after 20 minutes of inactivity).&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="putting-it-all-together"&gt;
&lt;h2&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;Once the cluster is up and running, we can execute our data processing logic on a Dask DataFrame, a parallelized version of pandas. After connecting to the cluster, we load our data into a Dask DataFrame and apply some group-by logic and the fuzzy algorithm to achieve our final dataset. The beauty of using Dask within this setup is that it automatically optimizes and distributes these operations across the cluster, making the processing highly efficient and easy to implement.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Airflow DAG from the Airflow UI" src="https://docs.coiled.io/_images/airflow-dag.png" style="width: 800px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;We used the Coiled UI to track a number of performance metrics and also keep track of uptimes, cloud costs, tasks processed, etc. During testing and set-up, we were regularly contacted by the Coiled team. They generously offered technical assistance and answered any questions we had. In one instance, the Dask library was updated in our Airflow environment, which caused a mismatch between Airflow and the Coiled software environment. A Coiled engineer promptly reached out after noticing our job had failed and pointed out the library discrepancies, which ultimately led to us using &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/sync.html"&gt;package sync&lt;/a&gt; to automatically handle the Python environment instead.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The results of utilizing Coiled to run our job on a Dask cluster were remarkable. The execution time of our code shrank from over 1 hour to around 10 minutes, a reduction of over 80%. Our customer was thrilled, and our group was able to confidently add big data processing to our portfolio of data engineering services. If you are like us, passionate programmers who have little interest in slogging through cloud infrastructure, consider our use case. Forget outdated solutions like Apache Spark or low-code services like AWS Glue. Call in the big guns with a few lines of code. Use Coiled.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/airflow-dask.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Nov 12, 2024Siemens logo</summary>
    <published>2024-11-12T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/geospatial-benchmarks-2.html</id>
    <title>Large Scale Geospatial Benchmarks: First Pass</title>
    <updated>2024-10-16T00:00:00+00:00</updated>
    <author>
      <name>Matthew Rocklin</name>
    </author>
    <content type="html">&lt;section id="large-scale-geospatial-benchmarks-first-pass"&gt;

&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;We implement several large-scale geo benchmarks.  Most break.  Fun!&lt;/p&gt;
&lt;p&gt;This article describes those benchmarks, what they attempt, how they break, and the technical work necessary to make them run smoothly.&lt;/p&gt;
&lt;section id="context"&gt;
&lt;h3&gt;Context&lt;/h3&gt;
&lt;p&gt;Last month we published &lt;a class="reference external" href="https://docs.coiled.io/blog/geospatial-benchmarks.html"&gt;Large Scale Geoscience: Solicitation&lt;/a&gt; a community call for geo benchmarks that reflected common workloads in the 100 TiB range.  We wanted to source benchmarks that were simple enough to understand, complex enough to be real, and generally very big.&lt;/p&gt;
&lt;script src="https://fast.wistia.com/embed/medias/ipapc4vkx3.jsonp" async&gt;&lt;/script&gt;
&lt;script src="https://fast.wistia.com/assets/external/E-v1.js" async&gt;&lt;/script&gt;
&lt;div class="wistia_responsive_padding" style="padding:56.25% 0 0 0;position:relative;width:90%;margin:auto"&gt;
  &lt;div class="wistia_responsive_wrapper" style="height:100%;left:0;position:absolute;top:0;width:100%;"&gt;
    &lt;div class="wistia_embed wistia_async_ipapc4vkx3 seo=true videoFoam=true" style="height:100%;position:    relative;width:100%"&gt;
    &amp;nbsp;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Many people contributed to &lt;a class="reference external" href="https://github.com/coiled/benchmarks/discussions/1545"&gt;this lively GitHub discussion&lt;/a&gt;.  Since then we’ve implemented several of those benchmarks and looked at how they run at scale.  This post walks through the benchmarks implemented so far, how they perform at scale, and outlines directions for future improvement.&lt;/p&gt;
&lt;p&gt;Now let’s go through what has been implemented so far, benchmark by benchmark.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="rechunking"&gt;
&lt;span id="id1"&gt;&lt;/span&gt;&lt;h2&gt;Rechunking&lt;/h2&gt;
&lt;aside class="system-message"&gt;
&lt;p class="system-message-title"&gt;System Message: INFO/1 (&lt;span class="docutils literal"&gt;/home/runnerx/actions-runner/_work/platform/platform/python-api/docs/source/blog/geospatial-benchmarks-2.md&lt;/span&gt;, line 35); &lt;em&gt;&lt;a href="#id1"&gt;backlink&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Duplicate implicit target name: “rechunking”.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Many applications’ performance depends strongly on data layout. Data laid out in one way (for example partitioned by time) may be inefficient for certain applications (like querying all data at a specific location) and vice versa. To resolve this we often rechunk datasets to have a more friendly structure for the computation at hand.&lt;/p&gt;
&lt;figure class="align-default" id="id7"&gt;
&lt;img alt="https://docs.coiled.io/_images/rechunking-diagram.png" src="https://docs.coiled.io/_images/rechunking-diagram.png" style="width: 80%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Rechunking from time-slice partitioned to spatially partitioned&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/ac48ae267ee047ca7ea40ce407b0f66a6413d748/tests/geospatial/test_rechunking.py"&gt;This rechunking benchmark&lt;/a&gt; rechunks the &lt;a class="reference external" href="https://weatherbench2.readthedocs.io/en/latest/data-guide.html#era5"&gt;ERA5&lt;/a&gt; global climate reanalysis dataset from time-oriented chunks to spatial chunks. This rechunking pattern frequently occurs with Earth Observation (EO) data.  Additionally, in this benchmark Xarray/Dask automatically chose to increase chunk sizes from 4 MiB to 100 MiB, which works better with cloud storage.&lt;/p&gt;
&lt;p&gt;However, as this workflow scales up we encounter the following issues:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large task graphs:&lt;/strong&gt; As the data volume increases, the graph size increases, resulting in two issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Initial Delay:&lt;/strong&gt; Sending the large graphs from the client to the cluster scheduler can take minutes. This is bad both because workers idle during this time, and also because users are confused why there isn’t any progress.  This delay is psychologically uncomfortable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scheduler Memory:&lt;/strong&gt; Each task takes up about 1kB in RAM.  Normally this is fine, but as graph sizes climb to hundreds of millions this can require schedulers with more memory.  This is fine in principle (it’s easy to get bigger schedulers) but it’s a common stumbling point for beginners.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Right-sizing disk size&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Historically, rechunking used to require a large amount of memory&lt;/strong&gt;.  This was a common pain point for novice users.  Fortunately, this has been fixed and Dask array’s newer rechunking algorithm (made default just last month) now uses disk efficiently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Today we’re running out of disk&lt;/strong&gt; because these benchmarks are so large.  This was easy to fix (we can ask either for more workers, or for more storage per worker) but this is a stumbling point for beginners, or those for whom configuring distributed clusters is hard.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="align-default" id="id8"&gt;
&lt;img alt="https://docs.coiled.io/_images/disk-overload.png" src="https://docs.coiled.io/_images/disk-overload.png" style="width: 95%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Cluster running out of disk space and crashing.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;These challenges of large graph sizes and right-sizing clusters are common concerns to anyone familiar with these tools at this scale. We’re not surprised to see them in a large rechunking workload like this. There are workarounds here that experts use.  We’re intentionally not invoking those workarounds.  We’re looking forward to working to remove these concerns in a way that will impact less-sophisticated users.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="climatology"&gt;
&lt;span id="id2"&gt;&lt;/span&gt;&lt;h2&gt;Climatology&lt;/h2&gt;
&lt;aside class="system-message"&gt;
&lt;p class="system-message-title"&gt;System Message: INFO/1 (&lt;span class="docutils literal"&gt;/home/runnerx/actions-runner/_work/platform/platform/python-api/docs/source/blog/geospatial-benchmarks-2.md&lt;/span&gt;, line 65); &lt;em&gt;&lt;a href="#id2"&gt;backlink&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Duplicate implicit target name: “climatology”.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Calculating climatologies or seasonal averages is a standard operation when working with weather/climate data. Climatologies are both useful in their own right and also in related computations like anomalies (deviations from the average).&lt;/p&gt;
&lt;figure class="align-default" id="id9"&gt;
&lt;img alt="https://docs.coiled.io/_images/climatology-1.png" src="https://docs.coiled.io/_images/climatology-1.png" style="width: 80%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;One slice of ERA5 to form a climatology&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/d2ec39f058eddea79b1e3eae74d64d7c80e14aca/tests/geospatial/test_climatology.py"&gt;This climatology benchmark&lt;/a&gt; computes seasonal averages of the &lt;a class="reference external" href="https://weatherbench2.readthedocs.io/en/latest/data-guide.html#era5"&gt;ERA5&lt;/a&gt; global climate reanalysis dataset.  There are a couple of ways to do this.&lt;/p&gt;
&lt;p&gt;Ideally we would just write down normal Xarray code, but that fails hard.  Instead it has become common-place for users to rechunk the data to align with the climatology one wants to compute, and then do full-chunk calculations.  This approach scales better, but &lt;em&gt;also&lt;/em&gt; fails.  There’s some work to do here as we scale up.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weighted rolling reductions scale poorly&lt;/strong&gt;  in Xarray (see &lt;a class="reference external" href="https://github.com/pydata/xarray/issues/9550"&gt;this issue&lt;/a&gt; for more details).&lt;/p&gt;
&lt;p&gt;Chunk sizes unexpectedly grew from MBs to GBs, which can cause out-of-memory errors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rechunking feels unnatural&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This type of computation today is often reframed using Xarray’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map_blocks&lt;/span&gt;&lt;/code&gt; as a workaround for the poor rolling performance. However, this approach is tedious and can feel unnatural, especially for less sophisticated Xarray users.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Anomalies keep data in memory&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This workload is often combined with calculating anomalies.  When combining these two we find that Dask doesn’t know if it should drop intermediate data that is used twice (first for calculating the average, and then again for calculating the anomaly) or keep it.  This results in a lot of disk engagement, which can be slow, especially on older versions of Dask.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="netcdf-to-zarr"&gt;
&lt;span id="id3"&gt;&lt;/span&gt;&lt;h2&gt;NetCDF to Zarr&lt;/h2&gt;
&lt;p&gt;NetCDF data is ubiquitous, but often slow and expensive to access in the cloud. Because of this, people commonly convert netCDF data to Zarr, a cloud-optimized format. These workflows often also include a modest rechunking step to improve chunking for cloud access (cloud systems tend to prefer larger reads than POSIX filesystems).&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/main/tests/geospatial/test_cloud_optimize.py"&gt;This cloud-optimization benchmark&lt;/a&gt; processes the &lt;a class="reference external" href="https://registry.opendata.aws/nex-gddp-cmip6/"&gt;NASA Earth Exchange Global Daily Downscaled Projections (NEX-GDDP-CMIP6)&lt;/a&gt; dataset (which is stored as thousands of NetCDF files) by loading the dataset with Xarray, rechunking from time-oriented chunks to spatial chunks, and then writing the result to a Zarr dataset.&lt;/p&gt;
&lt;figure class="align-default" id="id10"&gt;
&lt;img alt="https://docs.coiled.io/_images/cmip6-earthdata.png" src="https://docs.coiled.io/_images/cmip6-earthdata.png" style="width: 80%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;See &lt;a class="reference external" href="https://cmip6.surge.sh/discoveries/crossingyear/global"&gt;https://cmip6.surge.sh/discoveries/crossingyear/global&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;As this workflow scales up, we encounter the following issues:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Xarray’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;open_mfdataset&lt;/span&gt;&lt;/code&gt; doesn’t scale well&lt;/strong&gt; when reading many files.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Xarray today mostly calls &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;open_dataset&lt;/span&gt;&lt;/code&gt; many times, and then concatenates those results. You’d think that would work well, but it doesn’t.  The resulting task graph ends up being a bit unnatural.  We can address this by writing a bit of custom code for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;open_mfdataset&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It can take non-trivial time to read the metadata of all the NetCDF files.  Advanced users know to work around this with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;parallel=True&lt;/span&gt;&lt;/code&gt; keyword argument, which parallelizes the metadata reading on the workers.  Unfortunatley, novice users stumble on this.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As above, this workload inherits the issues around rechunking and right-sizing clusters.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Improving how &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;xarray.open_mfdataset&lt;/span&gt;&lt;/code&gt; performs on several thousand files will have a broad impact beyond this benchmark on any large scale workload processing data formats like NetCDF.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="atmospheric-circulation"&gt;
&lt;span id="id4"&gt;&lt;/span&gt;&lt;h2&gt;Atmospheric circulation&lt;/h2&gt;
&lt;p&gt;This atmospheric circulation benchmark, &lt;a class="reference external" href="https://github.com/coiled/benchmarks/discussions/1545#discussioncomment-10675652"&gt;proposed by&lt;/a&gt; Deepak Cherian, computes an Eulerian mean, depending on windspeed and temperature variables.&lt;/p&gt;
&lt;div class="highlight-py notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;zonal_means&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;anomaly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;zonal_means&lt;/span&gt;

&lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;uv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;
&lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;
&lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;uw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;W&lt;/span&gt;

&lt;span class="n"&gt;temdiags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zonal_means&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anomaly&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;uv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;vt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;uw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This benchmark runs smoothly in small memory (mostly), but graph generation time is painfully slow, waiting almost ten minutes before anything happens.&lt;/p&gt;
&lt;figure class="align-default" id="id11"&gt;
&lt;img alt="https://docs.coiled.io/_images/rechunking-workers.png" src="https://docs.coiled.io/_images/rechunking-workers.png" style="width: 95%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Worker CPU and Memory usage.  There’s a long delay before anything occurs.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="align-default" id="id12"&gt;
&lt;img alt="https://docs.coiled.io/_images/rechunking-scheduler.png" src="https://docs.coiled.io/_images/rechunking-scheduler.png" style="width: 95%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;During this period the scheduler is very busy.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;All of this points to an inefficiently constructed task graph.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="regridding"&gt;
&lt;span id="id5"&gt;&lt;/span&gt;&lt;h2&gt;Regridding&lt;/h2&gt;
&lt;aside class="system-message"&gt;
&lt;p class="system-message-title"&gt;System Message: INFO/1 (&lt;span class="docutils literal"&gt;/home/runnerx/actions-runner/_work/platform/platform/python-api/docs/source/blog/geospatial-benchmarks-2.md&lt;/span&gt;, line 146); &lt;em&gt;&lt;a href="#id5"&gt;backlink&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Duplicate implicit target name: “regridding”.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Regridding, or remapping, is another common operation when working with geospatial data. It is the process of interpolating data when changing the underlying grid. This may involve a change in the grid resolution or transitioning to a different type of grid. To complicate matters, there is no single standard regridding library people seem to rely on. To learn more about the state-of-the-art, see &lt;a class="reference external" href="https://discourse.pangeo.io/t/pangeo-showcase-geospatial-reprojection-in-python-2024-whats-available-and-whats-next/4531"&gt;Max Jones’ Pangeo Showcase&lt;/a&gt;. For our benchmark, we have chosen &lt;a class="reference external" href="https://github.com/pangeo-data/xESMF"&gt;xESMF&lt;/a&gt;, which appears to be somewhat common.&lt;/p&gt;
&lt;figure class="align-default" id="id13"&gt;
&lt;img alt="https://docs.coiled.io/_images/regridding.png" src="https://docs.coiled.io/_images/regridding.png" style="width: 70%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Regridded dataset between fine and coarse resolutions&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While this benchmark is embarrassingly parallel along the time dimension, it has problems when scaling the input or output grid due to their effect on the weight matrix used to interpolate the data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sequential Weight Matrix Calculation:&lt;/strong&gt; The cost of computing the weight matrix is high, and this step precedes the submission of the computation to the cluster. When scaling to higher grid resolutions, this causes the benchmark to stall for minutes before ever submitting work to the cluster, leaving resources underutilized.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large Weight Matrices:&lt;/strong&gt; The size of the weight matrix grows significantly with larger grid sizes. While the matrix for regridding from a 1440×721 resolution to 240×121 takes up around 2 MiB, the matrix for regridding from 1440×721 to 3600×1801 takes up 400 MiB. This matrix is embedded in the task graph, both increasing the size of the graph and the time necessary for submitting it to the cluster.&lt;/p&gt;
&lt;p&gt;Probably we should consider doing this work on the cluster instead.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="satellite-image-processing"&gt;
&lt;span id="satellite"&gt;&lt;/span&gt;&lt;h2&gt;Satellite Image Processing&lt;/h2&gt;
&lt;p&gt;Until now most of these benchmarks have been weather/climate/Xarray focused. However, we often encounter completely different use cases and problems in groups mostly focused on satellite imagery.  Think of switching out Zarr for COG here.&lt;/p&gt;
&lt;p&gt;People working on satellite imagery often don’t need big parallel Xarray jobs, instead they need an easy and efficient way to run a Python function many times on many individual files, each of which fits comfortably in memory.  This is common with GeoTIFF data, or with NetCDF files storing L1 or L2 semi-processed satellite data.  This user group cares about cloud computing and distributed computing, but the standard challenges around Xarray and Dask don’t apply as strongly.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/main/tests/geospatial/test_satellite_filtering.py"&gt;This benchmark&lt;/a&gt;
looks through Sentinel-2 data stored on the Microsoft Planetary Computer with the &lt;a class="reference external" href="https://odc-stac.readthedocs.io/en/latest/"&gt;ODC-STAC&lt;/a&gt; package, computes a humidity index, and then computes a monthly average.&lt;/p&gt;
&lt;p&gt;Additionally we have &lt;a class="reference external" href="https://docs.coiled.io/blog/processing-terabyte-scale-nasa-cloud-datasets-with-coiled.html"&gt;this historical workload&lt;/a&gt;, which lists tens of thousands of small netCDF files, and then does a simple data access computation on each one independently.  For this we use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function&lt;/span&gt;&lt;/code&gt; rather than Xarray (although any serverless function solution should work fine).&lt;/p&gt;
&lt;div class="highlight-py notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;As this workflow scales up, we encounter a different class of issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limited Availability:&lt;/strong&gt; We can do lots and lots of work here in parallel, but there are various limits stopping us from getting more workers.  We encountered in order:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Account quota&lt;/strong&gt;: the account we were running on had a limited quota&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spot availability&lt;/strong&gt;: as mentioned below, this often becomes a cost optimization game, and even when we had quota we started running out of spot instances&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instance type availability&lt;/strong&gt;: if we’re willing to go beyond spot and pay full price, there are limits to any particular instance type&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Aggressive spinup:&lt;/strong&gt; it’s not immediately clear how many machines we can use efficiently in parallel.&lt;/p&gt;
&lt;p&gt;By default we’re using Dask’s adaptive scaling heuristics, which run a few tasks and then based on that runtime information scale up accordingly.  This is fine, but does mean that it takes a few minutes before the full army of workers arrives, which can be psychologically a little frurstrating.  Maybe we can accelerate this a bit and be more aggressive.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This benchmark exposes more about the underlying cloud architecture we use, rather than the open source Python tooling.  This makes it a little different than the benchmarks above, but still something that our team is interested in (and we encourage teams of similar deployment projects to take an interest as well).  This benchmark is relatively small compared to the others (barely a terabyte) and we’d especially welcome more ideas and contributions in this direction.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="geophysical-inversions"&gt;
&lt;span id="id6"&gt;&lt;/span&gt;&lt;h2&gt;GeoPhysical Inversions&lt;/h2&gt;
&lt;p&gt;Similarly distinct from the Xarray workloads we’ve run into users running thousands of geophysical inversions using &lt;a class="reference external" href="https://simpeg.xyz/"&gt;SimPEG&lt;/a&gt;.  This problem is common in mining and resource extraction, or wherever we have sparse measurements within a physically constrained system (is there oil under this rock?  Is there water under this soil?  Is there cancer under this muscle?).  Increasingly as statistical methods become common we find that people want to do not one inversion problem, but thousands, which becomes computationally expensive.&lt;/p&gt;
&lt;p&gt;We &lt;em&gt;think&lt;/em&gt; that it might be possible to accelerate these workloads with GPUs, so we’re trying that in close collaboration with the SimPEG development team, and teams at NVIDIA.  Sorry, no standard benchmark on this one just yet.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="performance-optimization"&gt;
&lt;h2&gt;Performance Optimization&lt;/h2&gt;
&lt;p&gt;Once we’ve finished up an initial set of benchmarks (there are still more to do) we’ll start optimizing.  This involves running benchmarks, identifying major bottlenecks, and then fixing those bottlenecks wherever we happen to find them in the various open-source projects (Xarray, Dask, s3fs, Zarr, …)&lt;/p&gt;
&lt;p&gt;We’ve already identified several areas for improvement above.  Let’s list some of them below:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Moving Large Graphs&lt;/strong&gt;, which can be resolved both by making our graph representation more compact, or by moving symbolic representations of the graphs instead and expanding them only once they’re on the scheduler.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reimplementations of core methods&lt;/strong&gt; like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;xarray.open_mfdataset&lt;/span&gt;&lt;/code&gt; or rolling methods to be a bit more Dask-native where appropriate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better hinting of hardware failures&lt;/strong&gt; like when rechunking runs out of disk so that users get more sensible error messages and can ask for different hardware&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;More aggressive autoscaling&lt;/strong&gt; so that people feel less compelled to right-size clusters ahead of time and can trust adaptive scaling more (adaptive scaling seems like a nice idea in theory, but most people manually specify sizes still today).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Well-crafted sane defaults for keywords&lt;/strong&gt;: Today Xarray experts often specify a mix of keywords when working at larger scales.  We hope to be able to replace some of these with heuristics to help more novice users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Less reliance on manual rechunking&lt;/strong&gt; so that users can focus on expressing what they want to compute without worrying about the distributed memory layout or the footprint of individual steps. Instead, Xarray should become better at automatically dealing with chunking schemes that are not optimal.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="feedback"&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/help-dask-help-you.png" src="https://docs.coiled.io/_images/help-dask-help-you.png" style="width: 40%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Feedback from the community is critical to ensuring these benchmarks reflect real-world use cases that users care about.&lt;/p&gt;
&lt;p&gt;Is this the right set of benchmarks to use? Are key workloads missing?&lt;/p&gt;
&lt;p&gt;If you have feedback on these benchmarks or would like to propose a new benchmark, please engage in &lt;a class="reference external" href="https://github.com/coiled/benchmarks/discussions/1545"&gt;this GitHub discussion&lt;/a&gt;.&lt;/p&gt;
&lt;section id="acknowledgments"&gt;
&lt;h3&gt;Acknowledgments&lt;/h3&gt;
&lt;p&gt;Finally, thank you to everyone who has engaged in this effort so far. In particular, thank you to Deepak Cherian, Stephan Hoyer, Max Jones, Guillaume Eynard-Bontemps, and Kirill Kouzoubov for contributing benchmarks and discussions.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/geospatial-benchmarks-2.html"/>
    <summary>We implement several large-scale geo benchmarks.  Most break.  Fun!</summary>
    <published>2024-10-16T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-hugging-face.html</id>
    <title>Scaling AI-Based Data Processing with Hugging Face + Dask</title>
    <updated>2024-10-09T00:00:00+00:00</updated>
    <author>
      <name>Daniel van Strien</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Oct 9, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="scaling-ai-based-data-processing-with-hugging-face-dask"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Sarah Johnson, James Bourbeau, &lt;a class="reference external" href="https://huggingface.co/lhoestq"&gt;Quentin Lhoest&lt;/a&gt;, &lt;a class="reference external" href="https://huggingface.co/davanstrien"&gt;Daniel van Strien&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;This post was originally published on the &lt;a class="reference external" href="https://huggingface.co/blog/dask-scaling"&gt;Hugging Face blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/dask-hf.png" src="https://docs.coiled.io/_images/dask-hf.png" style="width: 500px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;The Hugging Face platform has many datasets and pre-trained models that make using and training state-of-the-art machine learning models increasingly accessible. However, it can be hard to scale AI tasks because AI datasets are often large (100s GBs to TBs) and using Hugging Face transformers for model inference can sometimes be computationally expensive.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt;, a Python library for distributed computing, can handle out-of-core computing (processing data that doesn’t fit in memory) by breaking datasets into manageable chunks. This makes it easy to do things like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Efficient data loading and preprocessing of TB-scale datasets with an easy to use API that mimics pandas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Parallel model inference (with the option of multi-node GPU inference)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post we show an example of data processing from the FineWeb dataset, using the FineWeb-Edu classifier to identify web pages with high educational value. We’ll show:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;How to process 100 rows locally with pandas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scaling to 211 million rows with Dask across multiple GPUs on the cloud&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="processing-100-rows-with-pandas"&gt;
&lt;h2&gt;Processing 100 Rows with Pandas&lt;/h2&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://huggingface.co/datasets/HuggingFaceFW/fineweb"&gt;FineWeb dataset&lt;/a&gt; consists of 15 trillion tokens of English web data from &lt;a class="reference external" href="http://commoncrawl.org/"&gt;Common Crawl&lt;/a&gt;, a non-profit that hosts a public web crawl dataset updated monthly. This dataset is often used for a variety of tasks such as large language model training, classification, content filtering, and information retrieval across a variety of sectors.&lt;/p&gt;
&lt;p&gt;It can take &amp;gt; 1 minute to download and read in a single file with pandas on a laptop.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;hf://datasets/HuggingFaceFW/fineweb/data/CC-MAIN-2024-10/000_00000.parquet&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Next, we’ll use the HF &lt;a class="reference external" href="https://huggingface.co/HuggingFaceFW/fineweb-edu-classifier"&gt;FineWeb-Edu classifier&lt;/a&gt; to judge the educational value of the web pages in our dataset. Web pages are ranked on a scale from 0 to 5, with 0 being not educational and 5 being highly educational. We can use pandas to do this on a smaller, 100-row subset of the data, which takes ~10 seconds on a M1 Mac with a GPU.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;transformers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;compute_scores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;

    &lt;span class="c1"&gt;# Select which hardware to use&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backends&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mps&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;text-classification&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HuggingFaceFW/fineweb-edu-classifier&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_list&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;# Choose batch size based on data size and hardware&lt;/span&gt;
        &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;longest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;truncation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;function_to_apply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;none&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Series&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;score&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;min_edu_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edu-classifier-score&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compute_scores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edu-classifier-score&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;min_edu_score&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Note that we also added a step to check the available hardware inside the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;compute_scores&lt;/span&gt;&lt;/code&gt; function, because it will be distributed when we scale with Dask in the next step. This makes it easy to go from testing locally on a single machine (either on a CPU or maybe you have a MacBook with an Apple silicon GPU) to distributing across multiple machines (like NVIDIA GPUs).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="scaling-to-211-million-rows-with-dask"&gt;
&lt;h2&gt;Scaling to 211 Million Rows with Dask&lt;/h2&gt;
&lt;p&gt;The entire 2024 February/March crawl is 432 GB on disk, or ~715 GB in memory, split up across 250 Parquet files. Even on a machine with enough memory for the whole dataset, this would be prohibitively slow to do serially.&lt;/p&gt;
&lt;p&gt;To scale up, we can use &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe.html"&gt;Dask DataFrame&lt;/a&gt;, which helps you process large tabular data by parallelizing pandas. It closely resembles the pandas API, making it easy to go from testing on a single dataset to scaling out to the full dataset. Dask works well with Parquet, the default format on Hugging Face datasets, to enable rich data types, efficient columnar filtering, and compression.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# Load the full dataset lazily with Dask&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;hf://datasets/HuggingFaceFW/fineweb/data/CC-MAIN-2024-10/*.parquet&amp;quot;&lt;/span&gt; 
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’ll apply the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;compute_scores&lt;/span&gt;&lt;/code&gt; function for text classification in parallel on our Dask DataFrame using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map_partitions&lt;/span&gt;&lt;/code&gt;, which applies our function in parallel on each pandas DataFrame in the larger Dask DataFrame. The &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;meta&lt;/span&gt;&lt;/code&gt; argument is specific to Dask, and indicates the data structure (column names and data types) of the output.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;transformers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;compute_scores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;

    &lt;span class="c1"&gt;# Select which hardware to use&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backends&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mps&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;text-classification&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HuggingFaceFW/fineweb-edu-classifier&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_list&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;longest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;truncation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;function_to_apply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;none&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Series&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;score&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;min_edu_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edu-classifier-score&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map_partitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compute_scores&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Series&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edu-classifier-score&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;min_edu_score&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Note that we’ve picked a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;batch_size&lt;/span&gt;&lt;/code&gt; that works well for this example, but you’ll likely want to customize this depending on the hardware, data, and model you’re using in your own workflows (see the &lt;a class="reference external" href="https://huggingface.co/docs/transformers/en/main_classes/pipelines#pipeline-batching"&gt;HF docs on pipeline batching&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Now that we’ve identified the rows of the dataset we’re interested in, we can save the result for other downstream analyses. Dask DataFrame automatically supports &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe-parquet.html"&gt;distributed writing to Parquet&lt;/a&gt;. However, since Hugging Face uses commits to track dataset changes, we needed a custom function to allow writing a Dask DataFrame in parallel to Hugging Face storage.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_hf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;to_parquet&lt;/span&gt;

&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;hf://datasets/&amp;lt;your-hf-user&amp;gt;/&amp;lt;data-dir&amp;gt;&amp;quot;&lt;/span&gt;  &lt;span class="c1"&gt;# Update with your dataset location&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In the future, we’ll create a direct integration with Dask and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;huggingface_hub&lt;/span&gt;&lt;/code&gt;, but for now, you can copy + paste &lt;a class="reference external" href="https://gist.github.com/lhoestq/8f73187a4e4b97b9bb40b561e35f6ccb"&gt;this custom function&lt;/a&gt; for your own use. In this example, we’ve saved it to a file called &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask_hf.py&lt;/span&gt;&lt;/code&gt;, which is the file referenced in the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;import&lt;/span&gt;&lt;/code&gt; clause above.&lt;/p&gt;
&lt;section id="multi-gpu-parallel-model-inference"&gt;
&lt;h3&gt;Multi-GPU Parallel Model Inference&lt;/h3&gt;
&lt;p&gt;There are a number of ways to &lt;a class="reference external" href="https://docs.dask.org/en/stable/deploying.html"&gt;deploy Dask&lt;/a&gt; on a variety of hardware. Here, we’ll use &lt;a class="reference external" href="https://docs.coiled.io/user_guide/ml.html"&gt;Coiled&lt;/a&gt; to deploy Dask on the cloud so we can spin up VMs as needed, and then clean them up when we’re done.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;# Same region as data&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      
    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# Use spot instances, if available&lt;/span&gt;
    &lt;span class="n"&gt;worker_vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;g5.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# NVIDIA A10 Tensor Core GPU&lt;/span&gt;
    &lt;span class="n"&gt;worker_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nthreads&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Under the hood Coiled handles:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Provisioning cloud VMs with GPU hardware. In this case, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;g5.xlarge&lt;/span&gt;&lt;/code&gt; &lt;a class="reference external" href="https://aws.amazon.com/ec2/instance-types/g5/"&gt;instances on AWS&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up the appropriate NVIDIA drivers, CUDA runtime, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatically installing the same packages you have locally on the cloud VM with &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/sync.html"&gt;package sync&lt;/a&gt;. This includes Python files in your working directory, so we can import directly from &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask_hf.py&lt;/span&gt;&lt;/code&gt; on the remote cluster.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The workflow took ~5 hours to complete and we had good GPU hardware utilization.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Median GPU utilization is 100% and median memory usage is 21.5 GB, just under the 24 GB available on the GPU." src="https://docs.coiled.io/_images/gpu-util-hf.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;GPU utilization and memory usage are both near their maximum capacity, which means we’re utilizing the available hardware well.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Putting it all together, here is the complete workflow:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;transformers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_hf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;to_parquet&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;g5.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nthreads&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_private_envs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HF_TOKEN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;your-hf-token&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;             &lt;span class="c1"&gt;#  Send credentials over encrypted connection&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;hf://datasets/HuggingFaceFW/fineweb/data/CC-MAIN-2024-10/*.parquet&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;compute_scores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;

    &lt;span class="c1"&gt;# Select which hardware to use&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backends&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mps&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;text-classification&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HuggingFaceFW/fineweb-edu-classifier&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_list&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;longest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;truncation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;function_to_apply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;none&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Series&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;score&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;min_edu_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edu-classifier-score&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map_partitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compute_scores&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Series&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edu-classifier-score&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;min_edu_score&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;hf://datasets/&amp;lt;your-hf-user&amp;gt;/&amp;lt;data-dir&amp;gt;&amp;quot;&lt;/span&gt;               &lt;span class="c1"&gt;# Replace with your HF user and directory&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hugging Face + Dask is a powerful combination. In this example, we scaled up our classification task from 100 rows to 211 million rows by using Dask + Coiled to run the workflow in parallel across multiple GPUs on the cloud.&lt;/p&gt;
&lt;p&gt;This same type of workflow can be used for other use cases like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Filtering genomic data to select genes of interest&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extracting information from unstructured text and turning them into structured datasets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cleaning text data scraped from the internet or Common Crawl&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running multimodal model inference to analyze large audio, image, or video datasets&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-hugging-face.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Oct 9, 2024</summary>
    <published>2024-10-09T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/geospatial-benchmarks.html</id>
    <title>Large Scale Geospatial Benchmarks</title>
    <updated>2024-09-09T00:00:00+00:00</updated>
    <author>
      <name>James Bourbeau</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Sep 9, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="large-scale-geospatial-benchmarks"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;James Bourbeau, Matt Rocklin&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; We need your help creating a geospatial benchmark suite. Please propose your workload on &lt;a class="reference external" href="https://github.com/coiled/benchmarks/discussions/1545"&gt;this GitHub discussion&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;People love the Xarray/Dask/… software stack for geospatial workloads, but only up to about the terabyte scale. At the terabyte scale this stack can struggle, requiring expertise to work well and frustrating users and developers alike.&lt;/p&gt;
&lt;p&gt;To address this, we want to build a large-scale geospatial benchmark suite of end-to-end examples to ensure that these tools operate smoothly up to the 100-TB scale.&lt;/p&gt;
&lt;p&gt;We need your help.&lt;/p&gt;
&lt;section id="benchmarks"&gt;
&lt;h2&gt;Benchmarks&lt;/h2&gt;
&lt;p&gt;Dask developers have addressed similar issues of robust performance at scale for dataframes and tabular use cases by running existing industry standard benchmarks (see &lt;a class="reference internal" href="blog/tpch.html"&gt;&lt;span class="doc"&gt;TPC-H benchmarks&lt;/span&gt;&lt;/a&gt;). Having these representative workloads was invaluable and enabled us to improve Dask DataFrame with much greater confidence.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="https://docs.coiled.io/_images/tpch-ab-tests.png" src="https://docs.coiled.io/_images/tpch-ab-tests.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;&lt;a class="reference external" href="https://github.com/dask/dask-expr/pull/305#issuecomment-1727961472"&gt;GitHub comment&lt;/a&gt; with A/B test results for a Dask update on TPC-H benchmark performance.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This process has led to Dask DataFrame experiencing an average of 20x performance improvements at scale over the last year, which has been transformative to DataFrame users.&lt;/p&gt;
&lt;figure class="align-default" id="id2"&gt;
&lt;img alt="https://docs.coiled.io/_images/dask-improvement.png" src="https://docs.coiled.io/_images/dask-improvement.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Performance improvements for Dask DataFrames&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Over the last month we’ve shifted focus to arrays, particularly supporting Xarray geospatial users, in hopes of getting similar speedups and robustness at large scale. To aid in this endeavor, we’d like to develop a similar suite of large-scale, representative geospatial workflows, and we’d like your help.&lt;/p&gt;
&lt;p&gt;The rest of this post will go through what we’re looking for.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-makes-a-good-benchmark"&gt;
&lt;h2&gt;What makes a good benchmark&lt;/h2&gt;
&lt;p&gt;There are many kinds of benchmarks ranging from microbenchmarks like the following:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;to full, end-to-end benchmarks like the TPC-H benchmark suite:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;date1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-10-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;date2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-07-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;line_item_ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;lineitem&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;orders_ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;orders&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;lsel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line_item_ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;l_commitdate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;line_item_ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;l_receiptdate&lt;/span&gt;
&lt;span class="n"&gt;osel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders_ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;o_orderdate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders_ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;o_orderdate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;flineitem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line_item_ds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lsel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;forders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders_ds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;osel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;jn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forders&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;flineitem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;leftsemi&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;o_orderkey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;l_orderkey&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;jn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;o_orderpriority&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;order_count&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;o_orderpriority&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’ve learned that microbenchmarks, or even normal-sized benchmarks, don’t provide the same high-quality direction that full end-to-end benchmarks provide. Historically we’ve done benchmarks that focused on specific parts of a workflow like rechunking, aggregations, slicing, and so on. However, while these simple benchmarks help us to accelerate parts of the system, they end up not having that big of an effect overall (performance at scale ends up being less about doing one thing well, and much more about doing nothing poorly).&lt;/p&gt;
&lt;p&gt;What was transformative about the TPC-H DataFrame benchmarks for us was that they covered all aspects of a computation at once, from data loading, to a sequence of complex calculations, to collecting and plotting results, putting everything into perspective. These holistic benchmarks focused us in surprising directions, and uncovered flaws we wouldn’t have thought to look for. For example we spent far more time working on data ingestion than we thought, uncovered complex interactions between seemingly unrelated operations, and spent far less time working on in-memory performance than we expected.&lt;/p&gt;
&lt;p&gt;As well as being full end-to-end, we think that benchmarks should be…&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Relatively simple&lt;/strong&gt; and easy to understand without sacrificing fidelity
(we want other users to look at these benchmarks and see their own applications reflected within them)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comprehensive&lt;/strong&gt; and cover the space fairly well&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Moderately independent&lt;/strong&gt; (we don’t want many of the same kind of workflow)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Big&lt;/strong&gt; (we’re mostly focused on TB or larger computations today)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="initial-ideas"&gt;
&lt;h2&gt;Initial ideas&lt;/h2&gt;
&lt;p&gt;We have a couple of end-to-end examples today that we like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;National Water Model (&lt;a class="reference internal" href="blog/coiled-xarray.html"&gt;&lt;span class="doc"&gt;blog&lt;/span&gt;&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/coiled/examples/blob/main/national-water-model/xarray-water-model.py"&gt;script&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filtering satellite images (&lt;a class="reference internal" href="blog/processing-terabyte-scale-nasa-cloud-datasets-with-coiled.html"&gt;&lt;span class="doc"&gt;blog&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;…&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We like that they operate on real data, that they’re large (250TB and 1TB, respectively), and that we’ve seen lots of people do similar things (we share these blogposts frequently).&lt;/p&gt;
&lt;p&gt;Each has also helped point us towards important rough edges at large scale that we weren’t previously aware of, improving a variety of tools in the PyData ecosystem including Xarray, Dask, and s3fs.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="we-want-your-help"&gt;
&lt;h2&gt;We want your help&lt;/h2&gt;
&lt;p&gt;We want your help to build a catalog of large scale, end-to-end, representative benchmarks. What does this help look like? We can use:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Ideas of what are the most common workflows in this space like:
“People often need to take netCDF files that are generated hourly, rechunk them to be arranged spatially, and then update that dataset every day”.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Real datasets to work on. We want to work with real data, not fake data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Real code that does it. We don’t know the space well enough to write like a user here.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a big ask, we know, but we hope that if a few people can contribute something meaningfully then we’ll be able to contribute code changes that accelerate those workflows (and others) considerably.&lt;/p&gt;
&lt;p&gt;We’d welcome contributions as comments to &lt;a class="reference external" href="https://github.com/coiled/benchmarks/discussions/1545"&gt;this GitHub discussion&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/geospatial-benchmarks.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Sep 9, 2024</summary>
    <published>2024-09-09T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/tpch.html</id>
    <title>DataFrames at Scale Comparison: TPC-H</title>
    <updated>2024-05-14T00:00:00+00:00</updated>
    <author>
      <name>Matthew Rocklin</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;May 14, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;14m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="dataframes-at-scale-comparison-tpc-h"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Hendrik Makait, Sarah Johnson, Matthew Rocklin&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We run benchmarks derived from the TPC-H benchmark suite on a variety of scales, hardware architectures, and dataframe projects, notably Apache Spark, Dask, DuckDB, and Polars.  No project wins.&lt;/p&gt;
&lt;p&gt;This post analyzes results within each project and between projects.  To start, here is the raw data of the results:&lt;/p&gt;
&lt;div class="sd-tab-set docutils" id="results"&gt;
&lt;input id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
Local (Macbook Pro)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-2" name="sd-tab-set-1" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-2"&gt;
10 GB&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-0"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "scale": {"bandPaddingInner": 0.1, "bandWithNestedOffsetPaddingInner": 0.4}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "vconcat": [{"mark": {"type": "bar"}, "encoding": {"color": {"field": "library", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask", "DuckDB", "Polars", "PySpark"], "range": ["#440154", "#3b528b", "#5ec962", "#21918c"]}, "type": "nominal"}, "opacity": {"condition": {"param": "param_1", "value": 1}, "value": 0.3}, "tooltip": [{"field": "library", "title": "Library"}, {"field": "duration", "format": ".2f", "title": "Runtime"}], "x": {"axis": {"labelAngle": 0}, "field": "query", "scale": {"domain": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]}, "title": "Query", "type": "ordinal"}, "xOffset": {"field": "library", "type": "nominal"}, "y": {"field": "duration", "title": "Runtime (s)", "type": "quantitative"}}, "name": "view_1", "width": 660}, {"mark": {"type": "rect"}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success"], "range": ["#bbc"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "library", "title": "Library"}}, "width": 660}], "data": {"name": "data-7f696d5b320d67af3a3b1f420317d591"}, "datasets": {"data-7f696d5b320d67af3a3b1f420317d591": [{"completion": "Success", "duration": 7.349830866, "library": "Dask", "num_cpus": 8, "query": 1}, {"completion": "Success", "duration": 2.55384922, "library": "Dask", "num_cpus": 8, "query": 2}, {"completion": "Success", "duration": 5.646444082, "library": "Dask", "num_cpus": 8, "query": 3}, {"completion": "Success", "duration": 3.504451036, "library": "Dask", "num_cpus": 8, "query": 4}, {"completion": "Success", "duration": 10.40760875, "library": "Dask", "num_cpus": 8, "query": 5}, {"completion": "Success", "duration": 0.5020279884, "library": "Dask", "num_cpus": 8, "query": 6}, {"completion": "Success", "duration": 6.85581708, "library": "Dask", "num_cpus": 8, "query": 7}, {"completion": "Success", "duration": 8.888586998, "library": "Dask", "num_cpus": 8, "query": 8}, {"completion": "Success", "duration": 19.46338892, "library": "Dask", "num_cpus": 8, "query": 9}, {"completion": "Success", "duration": 4.938610077, "library": "Dask", "num_cpus": 8, "query": 10}, {"completion": "Success", "duration": 1.485491991, "library": "Dask", "num_cpus": 8, "query": 11}, {"completion": "Success", "duration": 3.892226934, "library": "Dask", "num_cpus": 8, "query": 12}, {"completion": "Success", "duration": 2.799449921, "library": "Dask", "num_cpus": 8, "query": 13}, {"completion": "Success", "duration": 1.251161814, "library": "Dask", "num_cpus": 8, "query": 14}, {"completion": "Success", "duration": 1.104604006, "library": "Dask", "num_cpus": 8, "query": 15}, {"completion": "Success", "duration": 1.686655998, "library": "Dask", "num_cpus": 8, "query": 16}, {"completion": "Success", "duration": 11.01577783, "library": "Dask", "num_cpus": 8, "query": 17}, {"completion": "Success", "duration": 15.22865224, "library": "Dask", "num_cpus": 8, "query": 18}, {"completion": "Success", "duration": 3.0573771, "library": "Dask", "num_cpus": 8, "query": 19}, {"completion": "Success", "duration": 6.429955006, "library": "Dask", "num_cpus": 8, "query": 20}, {"completion": "Success", "duration": 26.02494717, "library": "Dask", "num_cpus": 8, "query": 21}, {"completion": "Success", "duration": 1.39294982, "library": "Dask", "num_cpus": 8, "query": 22}, {"completion": "Success", "duration": 1.281682014, "library": "DuckDB", "num_cpus": 8, "query": 1}, {"completion": "Success", "duration": 0.2963340282, "library": "DuckDB", "num_cpus": 8, "query": 2}, {"completion": "Success", "duration": 0.9297618866, "library": "DuckDB", "num_cpus": 8, "query": 3}, {"completion": "Success", "duration": 0.6533739567, "library": "DuckDB", "num_cpus": 8, "query": 4}, {"completion": "Success", "duration": 0.9363222122, "library": "DuckDB", "num_cpus": 8, "query": 5}, {"completion": "Success", "duration": 0.8786520958, "library": "DuckDB", "num_cpus": 8, "query": 6}, {"completion": "Success", "duration": 2.507023811, "library": "DuckDB", "num_cpus": 8, "query": 7}, {"completion": "Success", "duration": 1.138770103, "library": "DuckDB", "num_cpus": 8, "query": 8}, {"completion": "Success", "duration": 1.861751795, "library": "DuckDB", "num_cpus": 8, "query": 9}, {"completion": "Success", "duration": 1.114753008, "library": "DuckDB", "num_cpus": 8, "query": 10}, {"completion": "Success", "duration": 0.2095198631, "library": "DuckDB", "num_cpus": 8, "query": 11}, {"completion": "Success", "duration": 0.9625020027, "library": "DuckDB", "num_cpus": 8, "query": 12}, {"completion": "Success", "duration": 1.186424255, "library": "DuckDB", "num_cpus": 8, "query": 13}, {"completion": "Success", "duration": 0.6937360764, "library": "DuckDB", "num_cpus": 8, "query": 14}, {"completion": "Success", "duration": 1.482141256, "library": "DuckDB", "num_cpus": 8, "query": 15}, {"completion": "Success", "duration": 0.2064943314, "library": "DuckDB", "num_cpus": 8, "query": 16}, {"completion": "Success", "duration": 0.8086111546, "library": "DuckDB", "num_cpus": 8, "query": 17}, {"completion": "Success", "duration": 3.025156021, "library": "DuckDB", "num_cpus": 8, "query": 18}, {"completion": "Success", "duration": 0.9999239445, "library": "DuckDB", "num_cpus": 8, "query": 19}, {"completion": "Success", "duration": 0.7370512486, "library": "DuckDB", "num_cpus": 8, "query": 20}, {"completion": "Success", "duration": 2.408084869, "library": "DuckDB", "num_cpus": 8, "query": 21}, {"completion": "Success", "duration": 0.2457511425, "library": "DuckDB", "num_cpus": 8, "query": 22}, {"completion": "Success", "duration": 4.360592842, "library": "Polars", "num_cpus": 8, "query": 1}, {"completion": "Success", "duration": 0.1655430794, "library": "Polars", "num_cpus": 8, "query": 2}, {"completion": "Success", "duration": 1.907729864, "library": "Polars", "num_cpus": 8, "query": 3}, {"completion": "Success", "duration": 1.566069126, "library": "Polars", "num_cpus": 8, "query": 4}, {"completion": "Success", "duration": 4.885493994, "library": "Polars", "num_cpus": 8, "query": 5}, {"completion": "Success", "duration": 0.8894209862, "library": "Polars", "num_cpus": 8, "query": 6}, {"completion": "Success", "duration": 16.81775498, "library": "Polars", "num_cpus": 8, "query": 7}, {"completion": "Success", "duration": 3.476551056, "library": "Polars", "num_cpus": 8, "query": 8}, {"completion": "Success", "duration": 12.63604569, "library": "Polars", "num_cpus": 8, "query": 9}, {"completion": "Success", "duration": 2.146543026, "library": "Polars", "num_cpus": 8, "query": 10}, {"completion": "Success", "duration": 0.2651031017, "library": "Polars", "num_cpus": 8, "query": 11}, {"completion": "Success", "duration": 4.376965761, "library": "Polars", "num_cpus": 8, "query": 12}, {"completion": "Success", "duration": 4.744642019, "library": "Polars", "num_cpus": 8, "query": 13}, {"completion": "Success", "duration": 1.114660978, "library": "Polars", "num_cpus": 8, "query": 14}, {"completion": "Success", "duration": 0.8606438637, "library": "Polars", "num_cpus": 8, "query": 15}, {"completion": "Success", "duration": 0.3548252583, "library": "Polars", "num_cpus": 8, "query": 16}, {"completion": "Success", "duration": 6.278069019, "library": "Polars", "num_cpus": 8, "query": 17}, {"completion": "Success", "duration": 8.19382596, "library": "Polars", "num_cpus": 8, "query": 18}, {"completion": "Success", "duration": 2.845896006, "library": "Polars", "num_cpus": 8, "query": 19}, {"completion": "Success", "duration": 1.591340065, "library": "Polars", "num_cpus": 8, "query": 20}, {"completion": "Success", "duration": 9.262224913, "library": "Polars", "num_cpus": 8, "query": 21}, {"completion": "Success", "duration": 0.3273956776, "library": "Polars", "num_cpus": 8, "query": 22}, {"completion": "Success", "duration": 4.720607996, "library": "PySpark", "num_cpus": 8, "query": 1}, {"completion": "Success", "duration": 4.625711918, "library": "PySpark", "num_cpus": 8, "query": 2}, {"completion": "Success", "duration": 8.122514009, "library": "PySpark", "num_cpus": 8, "query": 3}, {"completion": "Success", "duration": 5.649055004, "library": "PySpark", "num_cpus": 8, "query": 4}, {"completion": "Success", "duration": 12.46462893, "library": "PySpark", "num_cpus": 8, "query": 5}, {"completion": "Success", "duration": 1.111974239, "library": "PySpark", "num_cpus": 8, "query": 6}, {"completion": "Success", "duration": 16.20508289, "library": "PySpark", "num_cpus": 8, "query": 7}, {"completion": "Success", "duration": 5.777259827, "library": "PySpark", "num_cpus": 8, "query": 8}, {"completion": "Success", "duration": 11.96828294, "library": "PySpark", "num_cpus": 8, "query": 9}, {"completion": "Success", "duration": 7.470014095, "library": "PySpark", "num_cpus": 8, "query": 10}, {"completion": "Success", "duration": 2.344013929, "library": "PySpark", "num_cpus": 8, "query": 11}, {"completion": "Success", "duration": 4.445052147, "library": "PySpark", "num_cpus": 8, "query": 12}, {"completion": "Success", "duration": 7.763844967, "library": "PySpark", "num_cpus": 8, "query": 13}, {"completion": "Success", "duration": 2.389064312, "library": "PySpark", "num_cpus": 8, "query": 14}, {"completion": "Success", "duration": 4.566293001, "library": "PySpark", "num_cpus": 8, "query": 15}, {"completion": "Success", "duration": 4.728442907, "library": "PySpark", "num_cpus": 8, "query": 16}, {"completion": "Success", "duration": 13.30754304, "library": "PySpark", "num_cpus": 8, "query": 17}, {"completion": "Success", "duration": 22.15371799, "library": "PySpark", "num_cpus": 8, "query": 18}, {"completion": "Success", "duration": 4.826957941, "library": "PySpark", "num_cpus": 8, "query": 19}, {"completion": "Success", "duration": 3.707264185, "library": "PySpark", "num_cpus": 8, "query": 20}, {"completion": "Success", "duration": 29.30386209, "library": "PySpark", "num_cpus": 8, "query": 21}, {"completion": "Success", "duration": 3.394886017, "library": "PySpark", "num_cpus": 8, "query": 22}]}, "params": [{"name": "param_1", "select": {"type": "point", "fields": ["library"], "toggle": "true"}, "bind": "legend", "value": [{"library": "DuckDB"}, {"library": "Polars"}], "views": ["view_1"]}], "resolve": {"scale": {"color": "independent"}}, "title": {"text": "Query Runtime", "anchor": "middle", "dy": -18, "fontSize": 22.5, "subtitle": "Local | Scale: 10 GB | CPUs: 8", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-0', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input id="sd-tab-item-3" name="sd-tab-set-1" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-3"&gt;
100 GB&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-1"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "scale": {"bandPaddingInner": 0.1, "bandWithNestedOffsetPaddingInner": 0.4}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "vconcat": [{"mark": {"type": "bar"}, "encoding": {"color": {"field": "library", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask", "DuckDB", "Polars", "PySpark"], "range": ["#440154", "#3b528b", "#5ec962", "#21918c"]}, "type": "nominal"}, "opacity": {"condition": {"param": "param_2", "value": 1}, "value": 0.3}, "tooltip": [{"field": "library", "title": "Library"}, {"field": "duration", "format": ".2f", "title": "Runtime"}], "x": {"axis": {"labelAngle": 0}, "field": "query", "scale": {"domain": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]}, "title": "Query", "type": "ordinal"}, "xOffset": {"field": "library", "type": "nominal"}, "y": {"field": "duration", "title": "Runtime (s)", "type": "quantitative"}}, "name": "view_2", "width": 660}, {"mark": {"type": "rect"}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success", "Failure / Timeout"], "range": ["#bbc", "#f676a3"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "library", "title": "Library"}}, "width": 660}], "data": {"name": "data-baae8b801adc1ea3293b006bbe1ad210"}, "datasets": {"data-baae8b801adc1ea3293b006bbe1ad210": [{"completion": "Success", "duration": 60.90016198, "library": "Dask", "num_cpus": 8, "query": 1}, {"completion": "Success", "duration": 18.94189692, "library": "Dask", "num_cpus": 8, "query": 2}, {"completion": "Success", "duration": 39.09827995, "library": "Dask", "num_cpus": 8, "query": 3}, {"completion": "Success", "duration": 32.45486116, "library": "Dask", "num_cpus": 8, "query": 4}, {"completion": "Success", "duration": 83.68057489, "library": "Dask", "num_cpus": 8, "query": 5}, {"completion": "Success", "duration": 3.798516989, "library": "Dask", "num_cpus": 8, "query": 6}, {"completion": "Success", "duration": 74.01650906, "library": "Dask", "num_cpus": 8, "query": 7}, {"completion": "Success", "duration": 98.89304376, "library": "Dask", "num_cpus": 8, "query": 8}, {"completion": "Success", "duration": 213.3178937, "library": "Dask", "num_cpus": 8, "query": 9}, {"completion": "Success", "duration": 48.15138793, "library": "Dask", "num_cpus": 8, "query": 10}, {"completion": "Success", "duration": 12.69428396, "library": "Dask", "num_cpus": 8, "query": 11}, {"completion": "Success", "duration": 46.74272895, "library": "Dask", "num_cpus": 8, "query": 12}, {"completion": "Success", "duration": 19.64744687, "library": "Dask", "num_cpus": 8, "query": 13}, {"completion": "Success", "duration": 10.04501629, "library": "Dask", "num_cpus": 8, "query": 14}, {"completion": "Success", "duration": 8.132129192, "library": "Dask", "num_cpus": 8, "query": 15}, {"completion": "Success", "duration": 11.52594686, "library": "Dask", "num_cpus": 8, "query": 16}, {"completion": "Success", "duration": 84.78956509, "library": "Dask", "num_cpus": 8, "query": 17}, {"completion": "Success", "duration": 141.6443441, "library": "Dask", "num_cpus": 8, "query": 18}, {"completion": "Success", "duration": 22.33940196, "library": "Dask", "num_cpus": 8, "query": 19}, {"completion": "Success", "duration": 53.86000085, "library": "Dask", "num_cpus": 8, "query": 20}, {"completion": "Success", "duration": 291.059638, "library": "Dask", "num_cpus": 8, "query": 21}, {"completion": "Success", "duration": 12.36513805, "library": "Dask", "num_cpus": 8, "query": 22}, {"completion": "Success", "duration": 11.39102077, "library": "DuckDB", "num_cpus": 8, "query": 1}, {"completion": "Success", "duration": 2.274616003, "library": "DuckDB", "num_cpus": 8, "query": 2}, {"completion": "Success", "duration": 8.691268921, "library": "DuckDB", "num_cpus": 8, "query": 3}, {"completion": "Success", "duration": 5.548564911, "library": "DuckDB", "num_cpus": 8, "query": 4}, {"completion": "Success", "duration": 7.715730906, "library": "DuckDB", "num_cpus": 8, "query": 5}, {"completion": "Success", "duration": 8.041278124, "library": "DuckDB", "num_cpus": 8, "query": 6}, {"completion": "Success", "duration": 70.74886203, "library": "DuckDB", "num_cpus": 8, "query": 7}, {"completion": "Success", "duration": 10.11947918, "library": "DuckDB", "num_cpus": 8, "query": 8}, {"completion": "Success", "duration": 193.7541733, "library": "DuckDB", "num_cpus": 8, "query": 9}, {"completion": "Success", "duration": 9.441447973, "library": "DuckDB", "num_cpus": 8, "query": 10}, {"completion": "Success", "duration": 1.599359989, "library": "DuckDB", "num_cpus": 8, "query": 11}, {"completion": "Success", "duration": 8.093062162, "library": "DuckDB", "num_cpus": 8, "query": 12}, {"completion": "Success", "duration": 9.572007895, "library": "DuckDB", "num_cpus": 8, "query": 13}, {"completion": "Success", "duration": 6.330237865, "library": "DuckDB", "num_cpus": 8, "query": 14}, {"completion": "Success", "duration": 12.08940697, "library": "DuckDB", "num_cpus": 8, "query": 15}, {"completion": "Success", "duration": 1.673762083, "library": "DuckDB", "num_cpus": 8, "query": 16}, {"completion": "Success", "duration": 7.404533863, "library": "DuckDB", "num_cpus": 8, "query": 17}, {"completion": "Success", "duration": 86.2370832, "library": "DuckDB", "num_cpus": 8, "query": 18}, {"completion": "Success", "duration": 9.395542145, "library": "DuckDB", "num_cpus": 8, "query": 19}, {"completion": "Success", "duration": 6.671203136, "library": "DuckDB", "num_cpus": 8, "query": 20}, {"completion": "Success", "duration": 22.361938, "library": "DuckDB", "num_cpus": 8, "query": 21}, {"completion": "Success", "duration": 2.254997969, "library": "DuckDB", "num_cpus": 8, "query": 22}, {"completion": "Success", "duration": 36.2593329, "library": "Polars", "num_cpus": 8, "query": 1}, {"completion": "Success", "duration": 1.759801865, "library": "Polars", "num_cpus": 8, "query": 2}, {"completion": "Success", "duration": 30.13716674, "library": "Polars", "num_cpus": 8, "query": 3}, {"completion": "Success", "duration": 17.38823175, "library": "Polars", "num_cpus": 8, "query": 4}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 5}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 6}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 7}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 8}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 9}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 10}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 11}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 12}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 13}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 14}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 15}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 16}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 17}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 18}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 19}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 20}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 21}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 8, "query": 22}, {"completion": "Success", "duration": 20.2693634, "library": "PySpark", "num_cpus": 8, "query": 1}, {"completion": "Success", "duration": 23.13561916, "library": "PySpark", "num_cpus": 8, "query": 2}, {"completion": "Success", "duration": 42.62711716, "library": "PySpark", "num_cpus": 8, "query": 3}, {"completion": "Success", "duration": 38.63358712, "library": "PySpark", "num_cpus": 8, "query": 4}, {"completion": "Success", "duration": 127.281121, "library": "PySpark", "num_cpus": 8, "query": 5}, {"completion": "Success", "duration": 8.065573215, "library": "PySpark", "num_cpus": 8, "query": 6}, {"completion": "Success", "duration": 67.28805804, "library": "PySpark", "num_cpus": 8, "query": 7}, {"completion": "Success", "duration": 94.18793964, "library": "PySpark", "num_cpus": 8, "query": 8}, {"completion": "Success", "duration": 186.382829, "library": "PySpark", "num_cpus": 8, "query": 9}, {"completion": "Success", "duration": 66.63047218, "library": "PySpark", "num_cpus": 8, "query": 10}, {"completion": "Success", "duration": 25.69180894, "library": "PySpark", "num_cpus": 8, "query": 11}, {"completion": "Success", "duration": 36.51077175, "library": "PySpark", "num_cpus": 8, "query": 12}, {"completion": "Success", "duration": 58.940418, "library": "PySpark", "num_cpus": 8, "query": 13}, {"completion": "Success", "duration": 24.0981319, "library": "PySpark", "num_cpus": 8, "query": 14}, {"completion": "Success", "duration": 49.37723517, "library": "PySpark", "num_cpus": 8, "query": 15}, {"completion": "Success", "duration": 19.34053993, "library": "PySpark", "num_cpus": 8, "query": 16}, {"completion": "Success", "duration": 152.5400431, "library": "PySpark", "num_cpus": 8, "query": 17}, {"completion": "Success", "duration": 140.4973431, "library": "PySpark", "num_cpus": 8, "query": 18}, {"completion": "Success", "duration": 22.37548995, "library": "PySpark", "num_cpus": 8, "query": 19}, {"completion": "Success", "duration": 24.32947493, "library": "PySpark", "num_cpus": 8, "query": 20}, {"completion": "Success", "duration": 183.6782579, "library": "PySpark", "num_cpus": 8, "query": 21}, {"completion": "Success", "duration": 22.52031684, "library": "PySpark", "num_cpus": 8, "query": 22}]}, "params": [{"name": "param_2", "select": {"type": "point", "fields": ["library"], "toggle": "true"}, "bind": "legend", "value": [{"library": "Dask"}, {"library": "DuckDB"}], "views": ["view_2"]}], "resolve": {"scale": {"color": "independent"}}, "title": {"text": "Query Runtime", "anchor": "middle", "dy": -18, "fontSize": 22.5, "subtitle": "Local | Scale: 100 GB | CPUs: 8", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-1', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input checked="checked" id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
Cloud (AWS)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input id="sd-tab-item-4" name="sd-tab-set-2" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-4"&gt;
100 GB&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-2"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "scale": {"bandPaddingInner": 0.1, "bandWithNestedOffsetPaddingInner": 0.4}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "vconcat": [{"mark": {"type": "bar"}, "encoding": {"color": {"field": "library", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask", "DuckDB", "Polars", "PySpark"], "range": ["#440154", "#3b528b", "#5ec962", "#21918c"]}, "type": "nominal"}, "opacity": {"condition": {"param": "param_3", "value": 1}, "value": 0.3}, "tooltip": [{"field": "library", "title": "Library"}, {"field": "duration", "format": ".2f", "title": "Runtime"}], "x": {"axis": {"labelAngle": 0}, "field": "query", "scale": {"domain": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]}, "title": "Query", "type": "ordinal"}, "xOffset": {"field": "library", "type": "nominal"}, "y": {"field": "duration", "scale": {"clamp": true, "domain": [0, 100]}, "title": "Runtime (s)", "type": "quantitative"}}, "name": "view_3", "width": 660}, {"mark": {"type": "rect"}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success", "Failure / Timeout"], "range": ["#bbc", "#f676a3"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "library", "title": "Library"}}, "width": 660}], "data": {"name": "data-b600e665d13bc566217cd840065e10ad"}, "datasets": {"data-b600e665d13bc566217cd840065e10ad": [{"completion": "Success", "duration": 13.08541107, "library": "Dask", "num_cpus": 32, "query": 1}, {"completion": "Success", "duration": 13.0250113, "library": "Dask", "num_cpus": 32, "query": 2}, {"completion": "Success", "duration": 17.07788038, "library": "Dask", "num_cpus": 32, "query": 3}, {"completion": "Success", "duration": 13.14008904, "library": "Dask", "num_cpus": 32, "query": 4}, {"completion": "Success", "duration": 27.15731239, "library": "Dask", "num_cpus": 32, "query": 5}, {"completion": "Success", "duration": 3.839656353, "library": "Dask", "num_cpus": 32, "query": 6}, {"completion": "Success", "duration": 26.9510994, "library": "Dask", "num_cpus": 32, "query": 7}, {"completion": "Success", "duration": 28.33784914, "library": "Dask", "num_cpus": 32, "query": 8}, {"completion": "Success", "duration": 46.79080462, "library": "Dask", "num_cpus": 32, "query": 9}, {"completion": "Success", "duration": 17.83082557, "library": "Dask", "num_cpus": 32, "query": 10}, {"completion": "Success", "duration": 8.247535229, "library": "Dask", "num_cpus": 32, "query": 11}, {"completion": "Success", "duration": 13.90035963, "library": "Dask", "num_cpus": 32, "query": 12}, {"completion": "Success", "duration": 9.61814642, "library": "Dask", "num_cpus": 32, "query": 13}, {"completion": "Success", "duration": 6.797837496, "library": "Dask", "num_cpus": 32, "query": 14}, {"completion": "Success", "duration": 5.414925337, "library": "Dask", "num_cpus": 32, "query": 15}, {"completion": "Success", "duration": 9.043009281, "library": "Dask", "num_cpus": 32, "query": 16}, {"completion": "Success", "duration": 23.5033083, "library": "Dask", "num_cpus": 32, "query": 17}, {"completion": "Success", "duration": 41.20109415, "library": "Dask", "num_cpus": 32, "query": 18}, {"completion": "Success", "duration": 11.27082205, "library": "Dask", "num_cpus": 32, "query": 19}, {"completion": "Success", "duration": 32.05219102, "library": "Dask", "num_cpus": 32, "query": 20}, {"completion": "Success", "duration": 52.23819804, "library": "Dask", "num_cpus": 32, "query": 21}, {"completion": "Success", "duration": 7.376028776, "library": "Dask", "num_cpus": 32, "query": 22}, {"completion": "Success", "duration": 17.10677695, "library": "DuckDB", "num_cpus": 32, "query": 1}, {"completion": "Success", "duration": 6.535497665, "library": "DuckDB", "num_cpus": 32, "query": 2}, {"completion": "Success", "duration": 12.04966021, "library": "DuckDB", "num_cpus": 32, "query": 3}, {"completion": "Success", "duration": 7.508094311, "library": "DuckDB", "num_cpus": 32, "query": 4}, {"completion": "Success", "duration": 10.71976447, "library": "DuckDB", "num_cpus": 32, "query": 5}, {"completion": "Success", "duration": 5.702061415, "library": "DuckDB", "num_cpus": 32, "query": 6}, {"completion": "Success", "duration": 15.1275773, "library": "DuckDB", "num_cpus": 32, "query": 7}, {"completion": "Success", "duration": 12.55208158, "library": "DuckDB", "num_cpus": 32, "query": 8}, {"completion": "Success", "duration": 16.20959187, "library": "DuckDB", "num_cpus": 32, "query": 9}, {"completion": "Success", "duration": 10.69723749, "library": "DuckDB", "num_cpus": 32, "query": 10}, {"completion": "Success", "duration": 3.759761095, "library": "DuckDB", "num_cpus": 32, "query": 11}, {"completion": "Success", "duration": 6.359490156, "library": "DuckDB", "num_cpus": 32, "query": 12}, {"completion": "Success", "duration": 7.898227453, "library": "DuckDB", "num_cpus": 32, "query": 13}, {"completion": "Success", "duration": 7.344327211, "library": "DuckDB", "num_cpus": 32, "query": 14}, {"completion": "Success", "duration": 12.856745, "library": "DuckDB", "num_cpus": 32, "query": 15}, {"completion": "Success", "duration": 3.143310308, "library": "DuckDB", "num_cpus": 32, "query": 16}, {"completion": "Success", "duration": 9.722416639, "library": "DuckDB", "num_cpus": 32, "query": 17}, {"completion": "Success", "duration": 15.86706591, "library": "DuckDB", "num_cpus": 32, "query": 18}, {"completion": "Success", "duration": 8.74607873, "library": "DuckDB", "num_cpus": 32, "query": 19}, {"completion": "Success", "duration": 8.793946266, "library": "DuckDB", "num_cpus": 32, "query": 20}, {"completion": "Success", "duration": 19.12657571, "library": "DuckDB", "num_cpus": 32, "query": 21}, {"completion": "Success", "duration": 3.014013529, "library": "DuckDB", "num_cpus": 32, "query": 22}, {"completion": "Success", "duration": 45.08502316, "library": "Polars", "num_cpus": 32, "query": 1}, {"completion": "Success", "duration": 3.602696896, "library": "Polars", "num_cpus": 32, "query": 2}, {"completion": "Success", "duration": 28.85828161, "library": "Polars", "num_cpus": 32, "query": 3}, {"completion": "Success", "duration": 22.71827841, "library": "Polars", "num_cpus": 32, "query": 4}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 32, "query": 5}, {"completion": "Success", "duration": 19.84522343, "library": "Polars", "num_cpus": 32, "query": 6}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 32, "query": 7}, {"completion": "Success", "duration": 58.37454653, "library": "Polars", "num_cpus": 32, "query": 8}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 32, "query": 9}, {"completion": "Success", "duration": 43.15112185, "library": "Polars", "num_cpus": 32, "query": 10}, {"completion": "Success", "duration": 5.001587391, "library": "Polars", "num_cpus": 32, "query": 11}, {"completion": "Success", "duration": 271.2024908, "library": "Polars", "num_cpus": 32, "query": 12}, {"completion": "Failure / Timeout", "duration": null, "library": "Polars", "num_cpus": 32, "query": 13}, {"completion": "Success", "duration": 30.25685501, "library": "Polars", "num_cpus": 32, "query": 14}, {"completion": "Success", "duration": 20.58765459, "library": "Polars", "num_cpus": 32, "query": 15}, {"completion": "Success", "duration": 7.338037968, "library": "Polars", "num_cpus": 32, "query": 16}, {"completion": "Success", "duration": 79.70577836, "library": "Polars", "num_cpus": 32, "query": 17}, {"completion": "Success", "duration": 474.0999227, "library": "Polars", "num_cpus": 32, "query": 18}, {"completion": "Success", "duration": 65.16217041, "library": "Polars", "num_cpus": 32, "query": 19}, {"completion": "Success", "duration": 23.00307131, "library": "Polars", "num_cpus": 32, "query": 20}, {"completion": "Success", "duration": 57.21958852, "library": "Polars", "num_cpus": 32, "query": 21}, {"completion": "Success", "duration": 5.887981176, "library": "Polars", "num_cpus": 32, "query": 22}, {"completion": "Success", "duration": 32.69119287, "library": "PySpark", "num_cpus": 32, "query": 1}, {"completion": "Success", "duration": 23.59120202, "library": "PySpark", "num_cpus": 32, "query": 2}, {"completion": "Success", "duration": 29.17188096, "library": "PySpark", "num_cpus": 32, "query": 3}, {"completion": "Success", "duration": 20.9779532, "library": "PySpark", "num_cpus": 32, "query": 4}, {"completion": "Success", "duration": 47.25027418, "library": "PySpark", "num_cpus": 32, "query": 5}, {"completion": "Success", "duration": 9.055701494, "library": "PySpark", "num_cpus": 32, "query": 6}, {"completion": "Success", "duration": 28.13473654, "library": "PySpark", "num_cpus": 32, "query": 7}, {"completion": "Success", "duration": 38.10869575, "library": "PySpark", "num_cpus": 32, "query": 8}, {"completion": "Success", "duration": 62.83931398, "library": "PySpark", "num_cpus": 32, "query": 9}, {"completion": "Success", "duration": 24.95479441, "library": "PySpark", "num_cpus": 32, "query": 10}, {"completion": "Success", "duration": 16.46976733, "library": "PySpark", "num_cpus": 32, "query": 11}, {"completion": "Success", "duration": 19.06706977, "library": "PySpark", "num_cpus": 32, "query": 12}, {"completion": "Success", "duration": 25.16990542, "library": "PySpark", "num_cpus": 32, "query": 13}, {"completion": "Success", "duration": 10.6557653, "library": "PySpark", "num_cpus": 32, "query": 14}, {"completion": "Success", "duration": 19.55093384, "library": "PySpark", "num_cpus": 32, "query": 15}, {"completion": "Success", "duration": 11.71789575, "library": "PySpark", "num_cpus": 32, "query": 16}, {"completion": "Success", "duration": 50.49014592, "library": "PySpark", "num_cpus": 32, "query": 17}, {"completion": "Success", "duration": 47.21154618, "library": "PySpark", "num_cpus": 32, "query": 18}, {"completion": "Success", "duration": 14.08572721, "library": "PySpark", "num_cpus": 32, "query": 19}, {"completion": "Success", "duration": 17.3655417, "library": "PySpark", "num_cpus": 32, "query": 20}, {"completion": "Success", "duration": 64.43185472, "library": "PySpark", "num_cpus": 32, "query": 21}, {"completion": "Success", "duration": 10.75746822, "library": "PySpark", "num_cpus": 32, "query": 22}]}, "params": [{"name": "param_3", "select": {"type": "point", "fields": ["library"], "toggle": "true"}, "bind": "legend", "value": [{"library": "Dask"}, {"library": "DuckDB"}], "views": ["view_3"]}], "resolve": {"scale": {"color": "independent"}}, "title": {"text": "Query Runtime", "anchor": "middle", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 100 GB | CPUs: 32", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-2', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input checked="checked" id="sd-tab-item-5" name="sd-tab-set-2" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-5"&gt;
1 TB&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-3"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "scale": {"bandPaddingInner": 0.1, "bandWithNestedOffsetPaddingInner": 0.4}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "vconcat": [{"mark": {"type": "bar"}, "encoding": {"color": {"field": "library", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask", "DuckDB", "Polars", "PySpark"], "range": ["#440154", "#3b528b", "#5ec962", "#21918c"]}, "type": "nominal"}, "opacity": {"condition": {"param": "param_4", "value": 1}, "value": 0.3}, "tooltip": [{"field": "library", "title": "Library"}, {"field": "duration", "format": ".2f", "title": "Runtime"}], "x": {"axis": {"labelAngle": 0}, "field": "query", "scale": {"domain": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]}, "title": "Query", "type": "ordinal"}, "xOffset": {"field": "library", "type": "nominal"}, "y": {"field": "duration", "title": "Runtime (s)", "type": "quantitative"}}, "name": "view_4", "width": 660}, {"mark": {"type": "rect"}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success", "Failure / Timeout", "Not Available"], "range": ["#bbc", "#f676a3", "#ddd"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "library", "title": "Library"}}, "width": 660}], "data": {"name": "data-45c0d03d085e44839c8020d35a3b50e2"}, "datasets": {"data-45c0d03d085e44839c8020d35a3b50e2": [{"completion": "Success", "duration": 29.75949216, "library": "Dask", "num_cpus": 128, "query": 1}, {"completion": "Success", "duration": 39.30828714, "library": "Dask", "num_cpus": 128, "query": 2}, {"completion": "Success", "duration": 58.97342896, "library": "Dask", "num_cpus": 128, "query": 3}, {"completion": "Success", "duration": 41.72662759, "library": "Dask", "num_cpus": 128, "query": 4}, {"completion": "Success", "duration": 94.1947875, "library": "Dask", "num_cpus": 128, "query": 5}, {"completion": "Success", "duration": 8.280158997, "library": "Dask", "num_cpus": 128, "query": 6}, {"completion": "Success", "duration": 97.14927673, "library": "Dask", "num_cpus": 128, "query": 7}, {"completion": "Success", "duration": 120.9598186, "library": "Dask", "num_cpus": 128, "query": 8}, {"completion": "Success", "duration": 163.0944493, "library": "Dask", "num_cpus": 128, "query": 9}, {"completion": "Success", "duration": 65.59146667, "library": "Dask", "num_cpus": 128, "query": 10}, {"completion": "Success", "duration": 16.60672617, "library": "Dask", "num_cpus": 128, "query": 11}, {"completion": "Success", "duration": 46.0263226, "library": "Dask", "num_cpus": 128, "query": 12}, {"completion": "Success", "duration": 27.56986189, "library": "Dask", "num_cpus": 128, "query": 13}, {"completion": "Success", "duration": 22.74466801, "library": "Dask", "num_cpus": 128, "query": 14}, {"completion": "Success", "duration": 17.07904959, "library": "Dask", "num_cpus": 128, "query": 15}, {"completion": "Success", "duration": 24.20406914, "library": "Dask", "num_cpus": 128, "query": 16}, {"completion": "Success", "duration": 77.78345537, "library": "Dask", "num_cpus": 128, "query": 17}, {"completion": "Success", "duration": 130.208168, "library": "Dask", "num_cpus": 128, "query": 18}, {"completion": "Success", "duration": 33.90354919, "library": "Dask", "num_cpus": 128, "query": 19}, {"completion": "Success", "duration": 54.9333415, "library": "Dask", "num_cpus": 128, "query": 20}, {"completion": "Success", "duration": 162.1645887, "library": "Dask", "num_cpus": 128, "query": 21}, {"completion": "Success", "duration": 21.92724085, "library": "Dask", "num_cpus": 128, "query": 22}, {"completion": "Success", "duration": 37.63626432, "library": "DuckDB", "num_cpus": 128, "query": 1}, {"completion": "Success", "duration": 21.2910831, "library": "DuckDB", "num_cpus": 128, "query": 2}, {"completion": "Success", "duration": 167.1154599, "library": "DuckDB", "num_cpus": 128, "query": 3}, {"completion": "Success", "duration": 32.81221771, "library": "DuckDB", "num_cpus": 128, "query": 4}, {"completion": "Success", "duration": 44.95286083, "library": "DuckDB", "num_cpus": 128, "query": 5}, {"completion": "Success", "duration": 19.7028451, "library": "DuckDB", "num_cpus": 128, "query": 6}, {"completion": "Success", "duration": 236.7368755, "library": "DuckDB", "num_cpus": 128, "query": 7}, {"completion": "Success", "duration": 56.57871389, "library": "DuckDB", "num_cpus": 128, "query": 8}, {"completion": "Success", "duration": 173.2250803, "library": "DuckDB", "num_cpus": 128, "query": 9}, {"completion": "Success", "duration": 119.1557136, "library": "DuckDB", "num_cpus": 128, "query": 10}, {"completion": "Success", "duration": 17.33489633, "library": "DuckDB", "num_cpus": 128, "query": 11}, {"completion": "Success", "duration": 26.00417137, "library": "DuckDB", "num_cpus": 128, "query": 12}, {"completion": "Success", "duration": 78.27388215, "library": "DuckDB", "num_cpus": 128, "query": 13}, {"completion": "Success", "duration": 44.00732279, "library": "DuckDB", "num_cpus": 128, "query": 14}, {"completion": "Success", "duration": 69.34479427, "library": "DuckDB", "num_cpus": 128, "query": 15}, {"completion": "Success", "duration": 29.36622119, "library": "DuckDB", "num_cpus": 128, "query": 16}, {"completion": "Success", "duration": 37.8093152, "library": "DuckDB", "num_cpus": 128, "query": 17}, {"completion": "Failure / Timeout", "duration": null, "library": "DuckDB", "num_cpus": 128, "query": 18}, {"completion": "Success", "duration": 33.65130925, "library": "DuckDB", "num_cpus": 128, "query": 19}, {"completion": "Success", "duration": 35.96667242, "library": "DuckDB", "num_cpus": 128, "query": 20}, {"completion": "Success", "duration": 106.5192387, "library": "DuckDB", "num_cpus": 128, "query": 21}, {"completion": "Success", "duration": 15.40155816, "library": "DuckDB", "num_cpus": 128, "query": 22}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 1}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 2}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 3}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 4}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 5}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 6}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 7}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 8}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 9}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 10}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 11}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 12}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 13}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 14}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 15}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 16}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 17}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 18}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 19}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 20}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 21}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 22}, {"completion": "Success", "duration": 47.26570702, "library": "PySpark", "num_cpus": 128, "query": 1}, {"completion": "Success", "duration": 41.25438881, "library": "PySpark", "num_cpus": 128, "query": 2}, {"completion": "Success", "duration": 66.01771116, "library": "PySpark", "num_cpus": 128, "query": 3}, {"completion": "Success", "duration": 57.52005577, "library": "PySpark", "num_cpus": 128, "query": 4}, {"completion": "Success", "duration": 155.1479619, "library": "PySpark", "num_cpus": 128, "query": 5}, {"completion": "Success", "duration": 19.86647058, "library": "PySpark", "num_cpus": 128, "query": 6}, {"completion": "Success", "duration": 66.81416082, "library": "PySpark", "num_cpus": 128, "query": 7}, {"completion": "Success", "duration": 150.4258046, "library": "PySpark", "num_cpus": 128, "query": 8}, {"completion": "Success", "duration": 192.1831613, "library": "PySpark", "num_cpus": 128, "query": 9}, {"completion": "Success", "duration": 54.85559559, "library": "PySpark", "num_cpus": 128, "query": 10}, {"completion": "Success", "duration": 30.85646725, "library": "PySpark", "num_cpus": 128, "query": 11}, {"completion": "Success", "duration": 39.33754587, "library": "PySpark", "num_cpus": 128, "query": 12}, {"completion": "Success", "duration": 48.68391037, "library": "PySpark", "num_cpus": 128, "query": 13}, {"completion": "Success", "duration": 23.93437481, "library": "PySpark", "num_cpus": 128, "query": 14}, {"completion": "Success", "duration": 41.47582936, "library": "PySpark", "num_cpus": 128, "query": 15}, {"completion": "Success", "duration": 21.09156013, "library": "PySpark", "num_cpus": 128, "query": 16}, {"completion": "Success", "duration": 146.2289822, "library": "PySpark", "num_cpus": 128, "query": 17}, {"completion": "Success", "duration": 147.4659843, "library": "PySpark", "num_cpus": 128, "query": 18}, {"completion": "Success", "duration": 35.36190605, "library": "PySpark", "num_cpus": 128, "query": 19}, {"completion": "Success", "duration": 39.92999053, "library": "PySpark", "num_cpus": 128, "query": 20}, {"completion": "Success", "duration": 224.4968228, "library": "PySpark", "num_cpus": 128, "query": 21}, {"completion": "Success", "duration": 21.77229381, "library": "PySpark", "num_cpus": 128, "query": 22}]}, "params": [{"name": "param_4", "select": {"type": "point", "fields": ["library"], "toggle": "true"}, "bind": "legend", "value": [{"library": "Dask"}, {"library": "PySpark"}], "views": ["view_4"]}], "resolve": {"scale": {"color": "independent"}}, "title": {"text": "Query Runtime", "anchor": "middle", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 1 TB | CPUs: 128", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-3', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input id="sd-tab-item-6" name="sd-tab-set-2" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-6"&gt;
10 TB - 128 CPUs&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-4"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "scale": {"bandPaddingInner": 0.1, "bandWithNestedOffsetPaddingInner": 0.4}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "vconcat": [{"mark": {"type": "bar"}, "encoding": {"color": {"field": "library", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask", "DuckDB", "Polars", "PySpark"], "range": ["#440154", "#3b528b", "#5ec962", "#21918c"]}, "type": "nominal"}, "opacity": {"condition": {"param": "param_5", "value": 1}, "value": 0.3}, "tooltip": [{"field": "library", "title": "Library"}, {"field": "duration", "format": ".2f", "title": "Runtime"}], "x": {"axis": {"labelAngle": 0}, "field": "query", "scale": {"domain": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]}, "title": "Query", "type": "ordinal"}, "xOffset": {"field": "library", "type": "nominal"}, "y": {"field": "duration", "title": "Runtime (s)", "type": "quantitative"}}, "name": "view_5", "width": 660}, {"mark": {"type": "rect"}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success", "Failure / Timeout", "Not Available"], "range": ["#bbc", "#f676a3", "#ddd"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "library", "title": "Library"}}, "width": 660}], "data": {"name": "data-2517b533e2968fabe41b011a7bb231e1"}, "datasets": {"data-2517b533e2968fabe41b011a7bb231e1": [{"completion": "Success", "duration": 282.5907674, "library": "Dask", "num_cpus": 128, "query": 1}, {"completion": "Success", "duration": 1111.77673, "library": "Dask", "num_cpus": 128, "query": 2}, {"completion": "Success", "duration": 860.7199323, "library": "Dask", "num_cpus": 128, "query": 3}, {"completion": "Success", "duration": 395.237807, "library": "Dask", "num_cpus": 128, "query": 4}, {"completion": "Failure / Timeout", "duration": null, "library": "Dask", "num_cpus": 128, "query": 5}, {"completion": "Success", "duration": 103.6557188, "library": "Dask", "num_cpus": 128, "query": 6}, {"completion": "Success", "duration": 2059.678836, "library": "Dask", "num_cpus": 128, "query": 7}, {"completion": "Success", "duration": 2301.317333, "library": "Dask", "num_cpus": 128, "query": 8}, {"completion": "Failure / Timeout", "duration": null, "library": "Dask", "num_cpus": 128, "query": 9}, {"completion": "Success", "duration": 1007.80604, "library": "Dask", "num_cpus": 128, "query": 10}, {"completion": "Success", "duration": 125.6502235, "library": "Dask", "num_cpus": 128, "query": 11}, {"completion": "Success", "duration": 613.8739057, "library": "Dask", "num_cpus": 128, "query": 12}, {"completion": "Success", "duration": 283.7092054, "library": "Dask", "num_cpus": 128, "query": 13}, {"completion": "Success", "duration": 306.5319939, "library": "Dask", "num_cpus": 128, "query": 14}, {"completion": "Success", "duration": 359.5247343, "library": "Dask", "num_cpus": 128, "query": 15}, {"completion": "Success", "duration": 167.3639164, "library": "Dask", "num_cpus": 128, "query": 16}, {"completion": "Success", "duration": 1111.068933, "library": "Dask", "num_cpus": 128, "query": 17}, {"completion": "Failure / Timeout", "duration": null, "library": "Dask", "num_cpus": 128, "query": 18}, {"completion": "Success", "duration": 431.6188579, "library": "Dask", "num_cpus": 128, "query": 19}, {"completion": "Success", "duration": 630.6849394, "library": "Dask", "num_cpus": 128, "query": 20}, {"completion": "Success", "duration": 1998.355702, "library": "Dask", "num_cpus": 128, "query": 21}, {"completion": "Success", "duration": 169.6138759, "library": "Dask", "num_cpus": 128, "query": 22}, {"completion": "Success", "duration": 363.3646822, "library": "DuckDB", "num_cpus": 128, "query": 1}, {"completion": "Success", "duration": 177.8670843, "library": "DuckDB", "num_cpus": 128, "query": 2}, {"completion": "Failure / Timeout", "duration": null, "library": "DuckDB", "num_cpus": 128, "query": 3}, {"completion": "Success", "duration": 347.4554381, "library": "DuckDB", "num_cpus": 128, "query": 4}, {"completion": "Success", "duration": 452.7523837, "library": "DuckDB", "num_cpus": 128, "query": 5}, {"completion": "Success", "duration": 221.262579, "library": "DuckDB", "num_cpus": 128, "query": 6}, {"completion": "Failure / Timeout", "duration": null, "library": "DuckDB", "num_cpus": 128, "query": 7}, {"completion": "Success", "duration": 563.9598584, "library": "DuckDB", "num_cpus": 128, "query": 8}, {"completion": "Failure / Timeout", "duration": null, "library": "DuckDB", "num_cpus": 128, "query": 9}, {"completion": "Failure / Timeout", "duration": null, "library": "DuckDB", "num_cpus": 128, "query": 10}, {"completion": "Success", "duration": 157.3635895, "library": "DuckDB", "num_cpus": 128, "query": 11}, {"completion": "Success", "duration": 309.0066972, "library": "DuckDB", "num_cpus": 128, "query": 12}, {"completion": "Failure / Timeout", "duration": null, "library": "DuckDB", "num_cpus": 128, "query": 13}, {"completion": "Success", "duration": 494.4503944, "library": "DuckDB", "num_cpus": 128, "query": 14}, {"completion": "Failure / Timeout", "duration": null, "library": "DuckDB", "num_cpus": 128, "query": 15}, {"completion": "Success", "duration": 284.0146158, "library": "DuckDB", "num_cpus": 128, "query": 16}, {"completion": "Success", "duration": 429.5142057, "library": "DuckDB", "num_cpus": 128, "query": 17}, {"completion": "Failure / Timeout", "duration": null, "library": "DuckDB", "num_cpus": 128, "query": 18}, {"completion": "Success", "duration": 481.0920424, "library": "DuckDB", "num_cpus": 128, "query": 19}, {"completion": "Success", "duration": 424.7717073, "library": "DuckDB", "num_cpus": 128, "query": 20}, {"completion": "Failure / Timeout", "duration": null, "library": "DuckDB", "num_cpus": 128, "query": 21}, {"completion": "Success", "duration": 157.8689592, "library": "DuckDB", "num_cpus": 128, "query": 22}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 1}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 2}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 3}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 4}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 5}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 6}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 7}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 8}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 9}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 10}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 11}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 12}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 13}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 14}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 15}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 16}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 17}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 18}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 19}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 20}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 21}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 128, "query": 22}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 1}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 2}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 3}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 4}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 5}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 6}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 7}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 8}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 9}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 10}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 11}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 12}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 13}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 14}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 15}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 16}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 17}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 18}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 19}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 20}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 21}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 128, "query": 22}]}, "params": [{"name": "param_5", "select": {"type": "point", "fields": ["library"], "toggle": "true"}, "bind": "legend", "value": [{"library": "Dask"}, {"library": "DuckDB"}], "views": ["view_5"]}], "resolve": {"scale": {"color": "independent"}}, "title": {"text": "Query Runtime", "anchor": "middle", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 10 TB | CPUs: 128", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-4', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input id="sd-tab-item-7" name="sd-tab-set-2" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-7"&gt;
10 TB - 1280 CPUs&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-5"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "scale": {"bandPaddingInner": 0.1, "bandWithNestedOffsetPaddingInner": 0.4}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "vconcat": [{"mark": {"type": "bar"}, "encoding": {"color": {"field": "library", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask", "DuckDB", "Polars", "PySpark"], "range": ["#440154", "#3b528b", "#5ec962", "#21918c"]}, "type": "nominal"}, "opacity": {"condition": {"param": "param_6", "value": 1}, "value": 0.3}, "tooltip": [{"field": "library", "title": "Library"}, {"field": "duration", "format": ".2f", "title": "Runtime"}], "x": {"axis": {"labelAngle": 0}, "field": "query", "scale": {"domain": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]}, "title": "Query", "type": "ordinal"}, "xOffset": {"field": "library", "type": "nominal"}, "y": {"field": "duration", "title": "Runtime (s)", "type": "quantitative"}}, "name": "view_6", "width": 660}, {"mark": {"type": "rect"}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success", "Failure / Timeout", "Not Available"], "range": ["#bbc", "#f676a3", "#ddd"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "library", "title": "Library"}}, "width": 660}], "data": {"name": "data-86e1dbb4824d3bad195404c33ff5e6cd"}, "datasets": {"data-86e1dbb4824d3bad195404c33ff5e6cd": [{"completion": "Success", "duration": 66.60129809, "library": "Dask", "num_cpus": 1280, "query": 1}, {"completion": "Success", "duration": 82.95414758, "library": "Dask", "num_cpus": 1280, "query": 2}, {"completion": "Success", "duration": 134.3289838, "library": "Dask", "num_cpus": 1280, "query": 3}, {"completion": "Success", "duration": 85.26894689, "library": "Dask", "num_cpus": 1280, "query": 4}, {"completion": "Success", "duration": 306.5999336, "library": "Dask", "num_cpus": 1280, "query": 5}, {"completion": "Success", "duration": 31.18334579, "library": "Dask", "num_cpus": 1280, "query": 6}, {"completion": "Success", "duration": 433.282084, "library": "Dask", "num_cpus": 1280, "query": 7}, {"completion": "Success", "duration": 573.6739829, "library": "Dask", "num_cpus": 1280, "query": 8}, {"completion": "Success", "duration": 450.3881218, "library": "Dask", "num_cpus": 1280, "query": 9}, {"completion": "Success", "duration": 187.1356595, "library": "Dask", "num_cpus": 1280, "query": 10}, {"completion": "Success", "duration": 53.12274289, "library": "Dask", "num_cpus": 1280, "query": 11}, {"completion": "Success", "duration": 104.0207951, "library": "Dask", "num_cpus": 1280, "query": 12}, {"completion": "Success", "duration": 92.32670546, "library": "Dask", "num_cpus": 1280, "query": 13}, {"completion": "Success", "duration": 99.17588496, "library": "Dask", "num_cpus": 1280, "query": 14}, {"completion": "Success", "duration": 151.0759761, "library": "Dask", "num_cpus": 1280, "query": 15}, {"completion": "Success", "duration": 65.88800359, "library": "Dask", "num_cpus": 1280, "query": 16}, {"completion": "Success", "duration": 179.3225074, "library": "Dask", "num_cpus": 1280, "query": 17}, {"completion": "Failure / Timeout", "duration": null, "library": "Dask", "num_cpus": 1280, "query": 18}, {"completion": "Success", "duration": 117.7497809, "library": "Dask", "num_cpus": 1280, "query": 19}, {"completion": "Success", "duration": 127.3478861, "library": "Dask", "num_cpus": 1280, "query": 20}, {"completion": "Success", "duration": 364.2363629, "library": "Dask", "num_cpus": 1280, "query": 21}, {"completion": "Success", "duration": 51.83337355, "library": "Dask", "num_cpus": 1280, "query": 22}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 1}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 2}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 3}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 4}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 5}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 6}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 7}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 8}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 9}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 10}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 11}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 12}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 13}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 14}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 15}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 16}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 17}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 18}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 19}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 20}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 21}, {"completion": "Not Available", "duration": null, "library": "DuckDB", "num_cpus": 1280, "query": 22}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 1}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 2}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 3}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 4}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 5}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 6}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 7}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 8}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 9}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 10}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 11}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 12}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 13}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 14}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 15}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 16}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 17}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 18}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 19}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 20}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 21}, {"completion": "Not Available", "duration": null, "library": "Polars", "num_cpus": 1280, "query": 22}, {"completion": "Success", "duration": 77.04977679, "library": "PySpark", "num_cpus": 1280, "query": 1}, {"completion": "Success", "duration": 89.61275721, "library": "PySpark", "num_cpus": 1280, "query": 2}, {"completion": "Success", "duration": 229.9624035, "library": "PySpark", "num_cpus": 1280, "query": 3}, {"completion": "Success", "duration": 150.3736932, "library": "PySpark", "num_cpus": 1280, "query": 4}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 1280, "query": 5}, {"completion": "Success", "duration": 35.4250896, "library": "PySpark", "num_cpus": 1280, "query": 6}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 1280, "query": 7}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 1280, "query": 8}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 1280, "query": 9}, {"completion": "Success", "duration": 186.3698347, "library": "PySpark", "num_cpus": 1280, "query": 10}, {"completion": "Success", "duration": 112.8401818, "library": "PySpark", "num_cpus": 1280, "query": 11}, {"completion": "Success", "duration": 107.7243538, "library": "PySpark", "num_cpus": 1280, "query": 12}, {"completion": "Success", "duration": 116.439326, "library": "PySpark", "num_cpus": 1280, "query": 13}, {"completion": "Success", "duration": 45.74167848, "library": "PySpark", "num_cpus": 1280, "query": 14}, {"completion": "Success", "duration": 64.98514605, "library": "PySpark", "num_cpus": 1280, "query": 15}, {"completion": "Success", "duration": 44.01199055, "library": "PySpark", "num_cpus": 1280, "query": 16}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 1280, "query": 17}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 1280, "query": 18}, {"completion": "Success", "duration": 88.89404082, "library": "PySpark", "num_cpus": 1280, "query": 19}, {"completion": "Success", "duration": 93.45699334, "library": "PySpark", "num_cpus": 1280, "query": 20}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 1280, "query": 21}, {"completion": "Failure / Timeout", "duration": null, "library": "PySpark", "num_cpus": 1280, "query": 22}]}, "params": [{"name": "param_6", "select": {"type": "point", "fields": ["library"], "toggle": "true"}, "bind": "legend", "value": null, "views": ["view_6"]}], "resolve": {"scale": {"color": "independent"}}, "title": {"text": "Query Runtime", "anchor": "middle", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 10 TB | CPUs: 1280", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-5', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;There is too much data here to understand all at once, so this post breaks down results by project and by comparing different projects.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#recommendations"&gt;&lt;strong&gt;Project results:&lt;/strong&gt; How each project performs individually&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#spark-results"&gt;Spark&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#dask-results"&gt;Dask&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#duckdb-results"&gt;DuckDB&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#polars-results"&gt;Polars&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#comparisons"&gt;&lt;strong&gt;Comparisons between Projects:&lt;/strong&gt; Head-to-head comparisons&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#dask-vs-spark"&gt;Dask vs. Spark&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#polars-vs-duckdb"&gt;Polars vs. DuckDB&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#polars-vs-pandas"&gt;Polars vs. pandas and Dask&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#dask-vs-duckdb"&gt;Dask vs DuckDB&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#query-details"&gt;&lt;strong&gt;Experimental Details:&lt;/strong&gt; How we ran these benchmarks&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="when-to-use-duckdb-vs-polars-vs-dask-vs-spark"&gt;
&lt;span id="recommendations"&gt;&lt;/span&gt;&lt;h2&gt;When to use DuckDB vs. Polars vs. Dask vs. Spark&lt;/h2&gt;
&lt;p&gt;Deciding which tool is right for you depends on many factors, some of which are objectively quantifiable (speed, scale), some of which are not (language preference, workload type, deployment pain).&lt;/p&gt;
&lt;p&gt;In this section we’ll go project by project, explaining how each did.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#spark-results"&gt;Spark&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#dask-results"&gt;Dask&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#duckdb-results"&gt;DuckDB&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#polars-results"&gt;Polars&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First though, we’ll include an arbitrary table and some general subjective takes.&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table" id="id5"&gt;
&lt;caption&gt;&lt;span class="caption-text"&gt;&lt;em&gt;DuckDB and Dask are the most robust choices today. Polars is developing rapidly, though, and we recommend &lt;a class="reference external" href="https://pola.rs/posts/benchmarks/"&gt;their benchmarks&lt;/a&gt; for the latest performance details.&lt;/em&gt;&lt;/span&gt;&lt;/caption&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head"&gt;&lt;p&gt;&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-center"&gt;&lt;p&gt;Spark&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-center"&gt;&lt;p&gt;Dask&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-center"&gt;&lt;p&gt;DuckDB&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-center"&gt;&lt;p&gt;Polars&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Fast Locally&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;🤔&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Fast on Cloud (1 TB)&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Fast on Cloud (10 TB)&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;Scales Out&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;SQL&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;🤔&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;🤔&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;More than SQL&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;Sensible Defaults&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Local + small?  Choose DuckDB or Polars&lt;/strong&gt;. For small data (10 GB or less) Polars and DuckDB (or pandas) are great.  Conversely, Dask and Spark leave a lot of performance on the table in this configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Local + big? Choose Dask or DuckDB&lt;/strong&gt;. For larger data, Polars isn’t yet fully stable. Polars is improving quickly, although, and is the most dynamic project in this group. DuckDB is more reliable (so is Dask, but it’s slower). Spark fails here pretty easily.  If you want to perform SQL queries on 100 GB of local data, DuckDB is the clear winner here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloud at 1 TB or less? Choose Dask or DuckDB&lt;/strong&gt;. For datasets that fit on a single cloud VM, Dask and DuckDB usually outperform Spark and Polars, respectively. Performance between the fast-local systems (Polars/DuckDB) and the distributed systems (Dask/Spark) converge a lot when we move to the cloud, largely due to S3 access bottlenecks (it doesn’t matter if you’re lightning fast if you’re waiting on S3 for data access). DuckDB does well across scales for the same hardware, but can’t scale beyond a single VM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloud at large scale? Choose Dask or Spark&lt;/strong&gt;. DuckDB can be as efficient as Dask or Spark on a single big VM, but if you want to process large volumes quickly, you eventually need a scalable system. Dask and Spark can outperform DuckDB and Polars on larger problems by adding more hardware. For large scale, Dask often outperforms Spark and scales well beyond DuckDB and Polars.&lt;/p&gt;
&lt;section id="how-does-spark-do-on-tpc-h-benchmarks"&gt;
&lt;span id="spark-results"&gt;&lt;/span&gt;&lt;h3&gt;How does Spark do on TPC-H Benchmarks?&lt;/h3&gt;
&lt;p&gt;Spark is the de-facto standard technology in this space today, it is also the least performant in general.  Spark’s days seem to be numbered.&lt;/p&gt;
&lt;p&gt;Spark is able to complete queries at all scales, but is out-performed at large scale by Dask (by about 2x), and very out-performed at small scale by everyone (by about 10x+).  Spark also performs poorly when given far less memory than the data it has to process, as was the case in the 10 TiB / 128 core case (which we included mostly so we could directly compare Spark against DuckDB, which is restricted to run on one machine).&lt;/p&gt;

&lt;div id="tpch-md-altair-plot-6"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"labelFont": "monospace", "title": null}, "scale": {"bandPaddingInner": 0.1}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-0197479bc5e55680bff93bcb246f11f5"}, "mark": {"type": "rect"}, "datasets": {"data-0197479bc5e55680bff93bcb246f11f5": [{"completion": "Success", "query": 1, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 2, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 3, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 4, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 5, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 6, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 7, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 8, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 9, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 10, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 11, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 12, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 13, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 14, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 15, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 16, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 17, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 18, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 19, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 20, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 21, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 22, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 1, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 2, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 3, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 4, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 5, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 6, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 7, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 8, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 9, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 10, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 11, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 12, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 13, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 14, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 15, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 16, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 17, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 18, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 19, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 20, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 21, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 22, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 5, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 7, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 9, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 13, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 18, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 21, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 5, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 7, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 9, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 13, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 18, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 21, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 1, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 2, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 3, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 4, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 5, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 6, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 7, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 8, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 9, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 10, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 11, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 12, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 13, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 14, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 15, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 16, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 17, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 18, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 19, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 20, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 21, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 22, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Failure / Timeout", "query": 5, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Failure / Timeout", "query": 7, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Failure / Timeout", "query": 8, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Failure / Timeout", "query": 9, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 13, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Failure / Timeout", "query": 17, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Failure / Timeout", "query": 18, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Failure / Timeout", "query": 21, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Failure / Timeout", "query": 22, "run": "Cloud |  10 TB | 1280 CPUs"}]}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success", "Failure / Timeout"], "range": ["#bbc", "#f676a3"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "run", "sort": [65, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 62, 63, 64, 44, 86, 83, 82, 81, 80, 79, 78, 77, 76, 75, 73, 72, 71, 70, 69, 68, 67, 66, 74, 87, 85, 84, 89, 109, 108, 107, 106, 105, 104, 103, 102, 101, 88, 99, 100, 91, 92, 93, 90, 95, 96, 97, 98, 94, 122, 129, 128, 127, 126, 125, 124, 123, 121, 116, 119, 118, 117, 115, 114, 113, 112, 111, 120, 110, 131, 130, 19, 18, 17, 16, 15, 14, 13, 12, 11, 21, 9, 8, 7, 6, 5, 4, 3, 2, 1, 20, 10, 0, 23, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 22, 43], "title": null}}, "title": {"text": "PySpark Query Completion", "dy": -18, "fontSize": 22.5}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-6', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;p&gt;Anecdotally, using Spark was also more painful than using the other systems.  We spent more time configuring Spark than we spent on all other systems combined.  Even then, we’re uncertain that we have it running optimally.  Spark’s performance was highly sensitive to non-default settings (like explicitly setting an output number of shuffle partitions).  Additionally we found that Spark was surprisingly inefficient.  It had the highest CPU occupancy rates of any of the projects, while also some of the slowest performance.&lt;/p&gt;
&lt;p&gt;Still, Spark’s history makes it a sane choice for any organization looking for a solid and well-known solution.  We would recommend it mostly for inertia, and for its broad support among enterprise data software systems.  We’re also curious about the various projects that rewrite Spark internals, like Photon from Databricks, and the open source Comet system, which were not evaluated here.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-does-dask-do-on-tpc-h-benchmarks"&gt;
&lt;span id="dask-results"&gt;&lt;/span&gt;&lt;h3&gt;How does Dask do on TPC-H Benchmarks?&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: We like Dask and know how to use it better than the other frameworks.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Locally, &lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; is outperformed at small scale by DuckDB and Polars, but performs reliably across all scales.  Dask performs universally better than Spark, and is more robust than Polars and DuckDB even if it’s slower at small scales with fast disk.  Dask consistently outperforms other technologies at scale in the cloud, particularly as we increase the amount of hardware available.&lt;/p&gt;
&lt;p&gt;Dask was the most robust system among the configurations tested (although again, there’s bias here because we’re better at Dask than at other systems).&lt;/p&gt;

&lt;div id="tpch-md-altair-plot-7"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"labelFont": "monospace", "title": null}, "scale": {"bandPaddingInner": 0.1}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-bcab3992a610845dbc644871acb70ed0"}, "mark": {"type": "rect"}, "datasets": {"data-bcab3992a610845dbc644871acb70ed0": [{"completion": "Success", "query": 1, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 2, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 3, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 4, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 5, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 6, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 7, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 8, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 9, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 10, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 11, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 12, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 13, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 14, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 15, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 16, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 17, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 18, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 19, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 20, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 21, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 22, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 1, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 2, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 3, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 4, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 5, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 6, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 7, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 8, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 9, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 10, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 11, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 12, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 13, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 14, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 15, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 16, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 17, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 18, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 19, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 20, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 21, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 22, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 5, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 7, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 9, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 13, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 18, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 21, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 5, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 7, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 9, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 13, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 18, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 21, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 5, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 7, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 9, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 13, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 18, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 21, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 5, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 7, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 9, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 13, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Failure / Timeout", "query": 18, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 21, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud |  10 TB | 1280 CPUs"}]}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success", "Failure / Timeout"], "range": ["#bbc", "#f676a3"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "run", "sort": [65, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 62, 63, 64, 44, 86, 83, 82, 81, 80, 79, 78, 77, 76, 75, 73, 72, 71, 70, 69, 68, 67, 66, 74, 87, 85, 84, 89, 109, 108, 107, 106, 105, 104, 103, 102, 101, 88, 99, 100, 91, 92, 93, 90, 95, 96, 97, 98, 94, 122, 129, 128, 127, 126, 125, 124, 123, 121, 116, 119, 118, 117, 115, 114, 113, 112, 111, 120, 110, 131, 130, 19, 18, 17, 16, 15, 14, 13, 12, 11, 21, 9, 8, 7, 6, 5, 4, 3, 2, 1, 20, 10, 0, 23, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 22, 43], "title": null}}, "title": {"text": "Dask Query Completion", "dy": -18, "fontSize": 22.5}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-7', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;p&gt;Compared to Spark, Dask was easy to configure.  Dask automatically inspected local hardware and configured itself appropriately. Additionally Dask’s out-of-core shuffling algorithms were stable even when data sizes were far larger than memory (compare against the 10 TiB 128 core case).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-does-duckdb-do-on-tpc-h-benchmarks"&gt;
&lt;span id="duckdb-results"&gt;&lt;/span&gt;&lt;h3&gt;How does DuckDB do on TPC-H Benchmarks?&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://duckdb.org/"&gt;DuckDB&lt;/a&gt; performs commendably at all scales, both locally and in the cloud.  It can be occasionally outperformed locally (by Polars, see comparisons below) and in the cloud (by Dask) but is a robust choice across all platforms.&lt;/p&gt;

&lt;div id="tpch-md-altair-plot-8"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"labelFont": "monospace", "title": null}, "scale": {"bandPaddingInner": 0.1}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-f10b9c1f0a5f4f7b59c8081f2dac8749"}, "mark": {"type": "rect"}, "datasets": {"data-f10b9c1f0a5f4f7b59c8081f2dac8749": [{"completion": "Success", "query": 1, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 2, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 3, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 4, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 5, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 6, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 7, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 8, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 9, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 10, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 11, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 12, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 13, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 14, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 15, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 16, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 17, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 18, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 19, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 20, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 21, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 22, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 1, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 2, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 3, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 4, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 5, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 6, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 7, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 8, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 9, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 10, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 11, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 12, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 13, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 14, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 15, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 16, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 17, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 18, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 19, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 20, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 21, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 22, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 5, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 7, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 9, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 13, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 18, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 21, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 5, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 7, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 9, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 13, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 18, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 21, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 3, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 5, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 7, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 9, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 10, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 13, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 15, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 18, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Failure / Timeout", "query": 21, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 1, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 2, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 3, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 4, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 5, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 6, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 7, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 8, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 9, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 10, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 11, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 12, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 13, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 14, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 15, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 16, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 17, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 18, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 19, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 20, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 21, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 22, "run": "Cloud |  10 TB | 1280 CPUs"}]}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success", "Failure / Timeout", "Not Available"], "range": ["#bbc", "#f676a3", "#ddd"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "run", "sort": [65, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 62, 63, 64, 44, 86, 83, 82, 81, 80, 79, 78, 77, 76, 75, 73, 72, 71, 70, 69, 68, 67, 66, 74, 87, 85, 84, 89, 109, 108, 107, 106, 105, 104, 103, 102, 101, 88, 99, 100, 91, 92, 93, 90, 95, 96, 97, 98, 94, 122, 129, 128, 127, 126, 125, 124, 123, 121, 116, 119, 118, 117, 115, 114, 113, 112, 111, 120, 110, 131, 130, 19, 18, 17, 16, 15, 14, 13, 12, 11, 21, 9, 8, 7, 6, 5, 4, 3, 2, 1, 20, 10, 0, 23, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 22, 43], "title": null}}, "title": {"text": "DuckDB Query Completion", "dy": -18, "fontSize": 22.5}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-8', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;p&gt;However, two things stop us from recommending DuckDB universally (aside from self-interest):&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale Limitations&lt;/strong&gt;: If you need to do fast queries on 10+TB-scale data, DuckDB’s inability to use multiple machines is a limitation. Dask and Spark can easily get 5x speed increases over DuckDB at a 10x hardware cost difference.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SQL-only&lt;/strong&gt;: Often people need more than just SQL (arguably this explain’s Python’s popularity today) and DuckDB is just SQL.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Using DuckDB was easy (it was pretty much plug-and-play).  It is the only framework where we did not feel the urge to communicate with experts of the software, whereas we collaborated with Polars and Spark developers in producing these numbers (see the &lt;a class="reference internal" href="#query-details"&gt;experiment details&lt;/a&gt; section below).
It was however mildly inconvenient to add configuration to access data on S3, but documentation and LLMs sufficed to point us in the right direction.  There are some small and straightforward things DuckDB can do to reduce friction for lazy users (and honestly, we’re all pretty lazy).&lt;/p&gt;
&lt;p&gt;Kudos to the DuckDB team.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-does-polars-do-on-tpc-h-benchmarks"&gt;
&lt;span id="polars-results"&gt;&lt;/span&gt;&lt;h3&gt;How does Polars do on TPC-H Benchmarks?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Updated June 17, 2025:&lt;/strong&gt; &lt;em&gt;The Polars streaming engine has undergone significant rewrites since these benchmarks were run. We recommend the &lt;a class="reference external" href="https://pola.rs/posts/benchmarks/"&gt;Polars benchmarks&lt;/a&gt; for the latest performance details.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://pola.rs/"&gt;Polars&lt;/a&gt; does fantastically well at modest scale (10 GB) locally, where it can use fast local disk and fit tables in memory.  The only contender at this scale is DuckDB.  However, Polars struggles in a few situations:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Its cloud data readers are not yet very performant&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Its unable to handle large multi-table joins well, which are unfortunately common in this benchmark suite&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Additionally, when Polars fails, it tends to fail hard, often resulting in machines grinding to a halt.  We unreservedly recommend Polars for the 10 GB scale on local machines in memory, and are excited to see how Polars develops in the future (the project is moving quickly).&lt;/p&gt;

&lt;div id="tpch-md-altair-plot-9"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"labelFont": "monospace", "title": null}, "scale": {"bandPaddingInner": 0.1}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-6a3b63d1487a9c838d0588d1f65a225c"}, "mark": {"type": "rect"}, "datasets": {"data-6a3b63d1487a9c838d0588d1f65a225c": [{"completion": "Success", "query": 1, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 2, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 3, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 4, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 5, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 6, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 7, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 8, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 9, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 10, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 11, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 12, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 13, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 14, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 15, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 16, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 17, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 18, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 19, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 20, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 21, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 22, "run": "Local |  10 GB |    8 CPUs"}, {"completion": "Success", "query": 1, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 2, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 3, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 4, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 5, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 6, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 7, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 8, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 9, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 10, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 11, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 12, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 13, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 14, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 15, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 16, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 17, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 18, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 19, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 20, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 21, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Failure / Timeout", "query": 22, "run": "Local | 100 GB |    8 CPUs"}, {"completion": "Success", "query": 1, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 2, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 3, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 4, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Failure / Timeout", "query": 5, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 6, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Failure / Timeout", "query": 7, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 8, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Failure / Timeout", "query": 9, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 10, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 11, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 12, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Failure / Timeout", "query": 13, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 14, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 15, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 16, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 17, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 18, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 19, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 20, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 21, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Success", "query": 22, "run": "Cloud | 100 GB |   32 CPUs"}, {"completion": "Not Available", "query": 1, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 2, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 3, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 4, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 5, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 6, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 7, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 8, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 9, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 10, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 11, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 12, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 13, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 14, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 15, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 16, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 17, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 18, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 19, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 20, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 21, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 22, "run": "Cloud |   1 TB |  128 CPUs"}, {"completion": "Not Available", "query": 1, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 2, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 3, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 4, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 5, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 6, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 7, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 8, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 9, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 10, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 11, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 12, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 13, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 14, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 15, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 16, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 17, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 18, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 19, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 20, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 21, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 22, "run": "Cloud |  10 TB |  128 CPUs"}, {"completion": "Not Available", "query": 1, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 2, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 3, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 4, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 5, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 6, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 7, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 8, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 9, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 10, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 11, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 12, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 13, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 14, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 15, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 16, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 17, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 18, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 19, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 20, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 21, "run": "Cloud |  10 TB | 1280 CPUs"}, {"completion": "Not Available", "query": 22, "run": "Cloud |  10 TB | 1280 CPUs"}]}, "encoding": {"color": {"field": "completion", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Success", "Failure / Timeout", "Not Available"], "range": ["#bbc", "#f676a3", "#ddd"]}, "type": "nominal"}, "x": {"field": "query", "title": "Query"}, "y": {"field": "run", "sort": [65, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 62, 63, 64, 44, 86, 83, 82, 81, 80, 79, 78, 77, 76, 75, 73, 72, 71, 70, 69, 68, 67, 66, 74, 87, 85, 84, 89, 109, 108, 107, 106, 105, 104, 103, 102, 101, 88, 99, 100, 91, 92, 93, 90, 95, 96, 97, 98, 94, 122, 129, 128, 127, 126, 125, 124, 123, 121, 116, 119, 118, 117, 115, 114, 113, 112, 111, 120, 110, 131, 130, 19, 18, 17, 16, 15, 14, 13, 12, 11, 21, 9, 8, 7, 6, 5, 4, 3, 2, 1, 20, 10, 0, 23, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 22, 43], "title": null}}, "title": {"text": "Polars Query Completion", "dy": -18, "fontSize": 22.5}, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-9', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;p&gt;Using Polars was an interesting case. It both broke frequently, but was also rapidly fixed by the Polars maintainers. This gave the impression that it’s not yet ready (again, this is with the experimental streaming backend, not in-memory), but moving very fast.  This was especially true when deploying on the cloud, where Polars has not historically seen as much use. Note that we have not run benchmarks with Polars on the cloud for scales above 100 GB because initial attempts showed poor scaling behavior.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="comparisons"&gt;
&lt;span id="id1"&gt;&lt;/span&gt;&lt;h2&gt;Comparisons&lt;/h2&gt;
&lt;aside class="system-message"&gt;
&lt;p class="system-message-title"&gt;System Message: INFO/1 (&lt;span class="docutils literal"&gt;/home/runnerx/actions-runner/_work/platform/platform/python-api/docs/source/blog/tpch.md&lt;/span&gt;, line 231); &lt;em&gt;&lt;a href="#id1"&gt;backlink&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Duplicate implicit target name: “comparisons”.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;In this section we consider subsets of the data to show head-to-head matchups between similar technologies.  We compare Dask vs Spark, Polars vs DuckDB, and so on.  Restricting the comparison space allows us to get a bit more insight into our data.&lt;/p&gt;
&lt;section id="dask-vs-spark"&gt;
&lt;span id="id2"&gt;&lt;/span&gt;&lt;h3&gt;Dask vs. Spark&lt;/h3&gt;
&lt;p&gt;Today TPC-H data shows that Dask is usually faster than Spark, and more robustly performant across scales.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input id="sd-tab-item-8" name="sd-tab-set-3" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-8"&gt;
100 GB (Local)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-10"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-84da63744000e5d27a077acfef9753f8"}, "mark": {"type": "bar"}, "datasets": {"data-84da63744000e5d27a077acfef9753f8": [{"label": "PySpark is faster", "num_cpus": 8, "query": 1, "relative_difference": -2.004542411035958}, {"label": "Dask is faster", "num_cpus": 8, "query": 2, "relative_difference": 0.22139927472480403}, {"label": "Dask is faster", "num_cpus": 8, "query": 3, "relative_difference": 0.09025556148538445}, {"label": "Dask is faster", "num_cpus": 8, "query": 4, "relative_difference": 0.19037905999780286}, {"label": "Dask is faster", "num_cpus": 8, "query": 5, "relative_difference": 0.521035451385389}, {"label": "Dask is faster", "num_cpus": 8, "query": 6, "relative_difference": 1.1233479377232822}, {"label": "PySpark is faster", "num_cpus": 8, "query": 7, "relative_difference": -0.09999472738535893}, {"label": "PySpark is faster", "num_cpus": 8, "query": 8, "relative_difference": -0.049954422381289935}, {"label": "PySpark is faster", "num_cpus": 8, "query": 9, "relative_difference": -0.1445147326312985}, {"label": "Dask is faster", "num_cpus": 8, "query": 10, "relative_difference": 0.3837705421256795}, {"label": "Dask is faster", "num_cpus": 8, "query": 11, "relative_difference": 1.0238879972242247}, {"label": "PySpark is faster", "num_cpus": 8, "query": 12, "relative_difference": -0.28024488964684774}, {"label": "Dask is faster", "num_cpus": 8, "query": 13, "relative_difference": 1.9999021445375207}, {"label": "Dask is faster", "num_cpus": 8, "query": 14, "relative_difference": 1.3990137202654551}, {"label": "Dask is faster", "num_cpus": 8, "query": 15, "relative_difference": 5.071870478714843}, {"label": "Dask is faster", "num_cpus": 8, "query": 16, "relative_difference": 0.678000095343143}, {"label": "Dask is faster", "num_cpus": 8, "query": 17, "relative_difference": 0.7990426408967443}, {"label": "PySpark is faster", "num_cpus": 8, "query": 18, "relative_difference": -0.008163862566309393}, {"label": "Dask is faster", "num_cpus": 8, "query": 19, "relative_difference": 0.001615441186143491}, {"label": "PySpark is faster", "num_cpus": 8, "query": 20, "relative_difference": -1.213775718751198}, {"label": "PySpark is faster", "num_cpus": 8, "query": 21, "relative_difference": -0.5846167169032149}, {"label": "Dask is faster", "num_cpus": 8, "query": 22, "relative_difference": 0.821275003072044}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask is faster", "PySpark is faster"], "range": ["#440154", "#21918c"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "scale": {"clamp": true, "domain": [-2, 2]}, "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Dask and PySpark", "dy": -18, "fontSize": 22.5, "subtitle": "Local | Scale: 100 GB | CPUs: 8", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-10', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input checked="checked" id="sd-tab-item-9" name="sd-tab-set-3" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-9"&gt;
1 TB (Cloud)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-11"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-def2d0ad19197fb51185a5d2631bccaa"}, "mark": {"type": "bar"}, "datasets": {"data-def2d0ad19197fb51185a5d2631bccaa": [{"label": "Dask is faster", "num_cpus": 128, "query": 1, "relative_difference": 0.5882565053825166}, {"label": "Dask is faster", "num_cpus": 128, "query": 2, "relative_difference": 0.04950868663060256}, {"label": "Dask is faster", "num_cpus": 128, "query": 3, "relative_difference": 0.11944840793941186}, {"label": "Dask is faster", "num_cpus": 128, "query": 4, "relative_difference": 0.37849759475373884}, {"label": "Dask is faster", "num_cpus": 128, "query": 5, "relative_difference": 0.6470971060898673}, {"label": "Dask is faster", "num_cpus": 128, "query": 6, "relative_difference": 1.3992861232734615}, {"label": "PySpark is faster", "num_cpus": 128, "query": 7, "relative_difference": -0.4540222542302672}, {"label": "Dask is faster", "num_cpus": 128, "query": 8, "relative_difference": 0.24360144005705367}, {"label": "Dask is faster", "num_cpus": 128, "query": 9, "relative_difference": 0.17835500916707153}, {"label": "PySpark is faster", "num_cpus": 128, "query": 10, "relative_difference": -0.19571150334856846}, {"label": "Dask is faster", "num_cpus": 128, "query": 11, "relative_difference": 0.8580704549546986}, {"label": "PySpark is faster", "num_cpus": 128, "query": 12, "relative_difference": -0.1700354351566467}, {"label": "Dask is faster", "num_cpus": 128, "query": 13, "relative_difference": 0.7658380213960514}, {"label": "Dask is faster", "num_cpus": 128, "query": 14, "relative_difference": 0.05230706376883272}, {"label": "Dask is faster", "num_cpus": 128, "query": 15, "relative_difference": 1.428462376752195}, {"label": "PySpark is faster", "num_cpus": 128, "query": 16, "relative_difference": -0.14757130296743012}, {"label": "Dask is faster", "num_cpus": 128, "query": 17, "relative_difference": 0.8799496821582252}, {"label": "Dask is faster", "num_cpus": 128, "query": 18, "relative_difference": 0.13254019747824117}, {"label": "Dask is faster", "num_cpus": 128, "query": 19, "relative_difference": 0.04301487292162765}, {"label": "PySpark is faster", "num_cpus": 128, "query": 20, "relative_difference": -0.375741410675461}, {"label": "Dask is faster", "num_cpus": 128, "query": 21, "relative_difference": 0.3843763586100348}, {"label": "PySpark is faster", "num_cpus": 128, "query": 22, "relative_difference": -0.007116707194573712}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask is faster", "PySpark is faster"], "range": ["#440154", "#21918c"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "scale": {"clamp": true, "domain": [-2, 2]}, "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Dask and PySpark", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 1 TB | CPUs: 128", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-11', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input id="sd-tab-item-10" name="sd-tab-set-3" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-10"&gt;
10 TB (Cloud)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-12"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-9fab97ec8348780d2a678bf27e459bbd"}, "mark": {"type": "bar"}, "datasets": {"data-9fab97ec8348780d2a678bf27e459bbd": [{"label": "Dask is faster", "num_cpus": 1280, "query": 1, "relative_difference": 0.1568810068218296}, {"label": "Dask is faster", "num_cpus": 1280, "query": 2, "relative_difference": 0.08026855587393646}, {"label": "Dask is faster", "num_cpus": 1280, "query": 3, "relative_difference": 0.7119343643839877}, {"label": "Dask is faster", "num_cpus": 1280, "query": 4, "relative_difference": 0.7635223452916272}, {"label": "PySpark failed", "num_cpus": 1280, "query": 5, "relative_difference": 1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 1280, "query": 6, "relative_difference": 0.13602593636248803}, {"label": "PySpark failed", "num_cpus": 1280, "query": 7, "relative_difference": 1.7976931348623157e+308}, {"label": "PySpark failed", "num_cpus": 1280, "query": 8, "relative_difference": 1.7976931348623157e+308}, {"label": "PySpark failed", "num_cpus": 1280, "query": 9, "relative_difference": 1.7976931348623157e+308}, {"label": "PySpark is faster", "num_cpus": 1280, "query": 10, "relative_difference": -0.004109167136584846}, {"label": "Dask is faster", "num_cpus": 1280, "query": 11, "relative_difference": 1.1241407288335146}, {"label": "Dask is faster", "num_cpus": 1280, "query": 12, "relative_difference": 0.03560402221920723}, {"label": "Dask is faster", "num_cpus": 1280, "query": 13, "relative_difference": 0.261166261915916}, {"label": "PySpark is faster", "num_cpus": 1280, "query": 14, "relative_difference": -1.168173277755067}, {"label": "PySpark is faster", "num_cpus": 1280, "query": 15, "relative_difference": -1.324777049570084}, {"label": "PySpark is faster", "num_cpus": 1280, "query": 16, "relative_difference": -0.4970466631166719}, {"label": "PySpark failed", "num_cpus": 1280, "query": 17, "relative_difference": 1.7976931348623157e+308}, {"label": "PySpark failed", "num_cpus": 1280, "query": 18, "relative_difference": 1.7976931348623157e+308}, {"label": "PySpark is faster", "num_cpus": 1280, "query": 19, "relative_difference": -0.3246082618567142}, {"label": "PySpark is faster", "num_cpus": 1280, "query": 20, "relative_difference": -0.3626362410002179}, {"label": "PySpark failed", "num_cpus": 1280, "query": 21, "relative_difference": 1.7976931348623157e+308}, {"label": "PySpark failed", "num_cpus": 1280, "query": 22, "relative_difference": 1.7976931348623157e+308}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask is faster", "PySpark is faster", "PySpark failed"], "range": ["#440154", "#21918c", "#a481aa"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "scale": {"clamp": true, "domain": [-2, 2]}, "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Dask and PySpark", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 10 TB | CPUs: 1280", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-12', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Additionally, users tend to like Dask for non-performance reasons like the following:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dask is easier to use and debug (for Python devs)&lt;/strong&gt;. Dask is well-integrated in the larger Python ecosystem and doesn’t require digging through JVM logs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dask is easier to deploy&lt;/strong&gt;. Deploying Spark is painful and most users rely on Databricks or EMR. You can deploy Dask easily with Kubernetes, HPC job managers, or on the cloud with Coiled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dask is highly flexible&lt;/strong&gt;. You can use Dask DataFrame with tabular datasets and it also extends to other applications like training ML models (XGBoost, PyTorch), workflow management (Prefect, Dagster), and highly custom parallelism.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For more information see &lt;a class="reference internal" href="blog/spark-vs-dask.html"&gt;&lt;span class="doc"&gt;Dask vs. Spark&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="polars-vs-duckdb"&gt;
&lt;span id="id3"&gt;&lt;/span&gt;&lt;h3&gt;Polars vs. DuckDB&lt;/h3&gt;
&lt;p&gt;Our benchmarks show that typically DuckDB outperforms Polars on single-machine local computations, such as a Macbook Pro.  This differs from other benchmarks we’ve read from other vendors, so it’s important to mention that local-hardware benchmarks are highly hardware specific.  In particular, we were using the Polars streaming system, due to being memory constrained in these benchmarks, which differs from the system used in typical Polars benchmarks.&lt;/p&gt;
&lt;p&gt;As we increase scale and move to the cloud, DuckDB’s lead becomes more prominent.
In general, DuckDB feels like a more mature project today.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-11" name="sd-tab-set-4" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-11"&gt;
10 GB (local)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-13"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-ceb11c15cdcbcba9d2a51e7072fb2ab1"}, "mark": {"type": "bar"}, "datasets": {"data-ceb11c15cdcbcba9d2a51e7072fb2ab1": [{"label": "DuckDB is faster", "num_cpus": 8, "query": 1, "relative_difference": 2.402242361497319}, {"label": "Polars is faster", "num_cpus": 8, "query": 2, "relative_difference": -0.7900719817103995}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 3, "relative_difference": 1.0518477811305886}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 4, "relative_difference": 1.3968955449491058}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 5, "relative_difference": 4.217748687731067}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 6, "relative_difference": 0.01225614831111856}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 7, "relative_difference": 5.708254985935593}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 8, "relative_difference": 2.052899831881168}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 9, "relative_difference": 5.78718061340717}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 10, "relative_difference": 0.9255772449999072}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 11, "relative_difference": 0.26528863553844123}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 12, "relative_difference": 3.5474874324643317}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 13, "relative_difference": 2.9991107725625517}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 14, "relative_difference": 0.606750774421741}, {"label": "Polars is faster", "num_cpus": 8, "query": 15, "relative_difference": -0.7221307424747284}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 16, "relative_difference": 0.7183292921134395}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 17, "relative_difference": 6.764014858421791}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 18, "relative_difference": 1.7085630966205296}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 19, "relative_difference": 1.8461124685068484}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 20, "relative_difference": 1.159062979708247}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 21, "relative_difference": 2.8463033559304347}, {"label": "DuckDB is faster", "num_cpus": 8, "query": 22, "relative_difference": 0.33222443757306236}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Polars is faster", "DuckDB is faster"], "range": ["#5ec962", "#3b528b"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Polars and DuckDB", "dy": -18, "fontSize": 22.5, "subtitle": "Local | Scale: 10 GB | CPUs: 8", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-13', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input id="sd-tab-item-12" name="sd-tab-set-4" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-12"&gt;
100 GB (cloud)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-14"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-56e56d429c65a4529f2c05c65805c968"}, "mark": {"type": "bar"}, "datasets": {"data-56e56d429c65a4529f2c05c65805c968": [{"label": "DuckDB is faster", "num_cpus": 32, "query": 1, "relative_difference": 1.635506576824806}, {"label": "Polars is faster", "num_cpus": 32, "query": 2, "relative_difference": -0.8140570393963003}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 3, "relative_difference": 1.3949456754017464}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 4, "relative_difference": 2.025838177966915}, {"label": "Polars failed", "num_cpus": 32, "query": 5, "relative_difference": 1.7976931348623157e+308}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 6, "relative_difference": 2.4803594675067178}, {"label": "Polars failed", "num_cpus": 32, "query": 7, "relative_difference": 1.7976931348623157e+308}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 8, "relative_difference": 3.65058692918406}, {"label": "Polars failed", "num_cpus": 32, "query": 9, "relative_difference": 1.7976931348623157e+308}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 10, "relative_difference": 3.0338565812284313}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 11, "relative_difference": 0.3302939374662581}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 12, "relative_difference": 41.64531969502746}, {"label": "Polars failed", "num_cpus": 32, "query": 13, "relative_difference": 1.7976931348623157e+308}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 14, "relative_difference": 3.1197585756640382}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 15, "relative_difference": 0.6013115753637487}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 16, "relative_difference": 1.3344936544521393}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 17, "relative_difference": 7.198144691750027}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 18, "relative_difference": 28.879495389327463}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 19, "relative_difference": 6.450444070036401}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 20, "relative_difference": 1.6157848381376496}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 21, "relative_difference": 1.9916274291630638}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 22, "relative_difference": 0.9535350851439394}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Polars is faster", "Polars failed", "DuckDB is faster"], "range": ["#5ec962", "#9da9c5", "#3b528b"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "scale": {"clamp": true, "domain": [-1, 8]}, "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Polars and DuckDB", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 100 GB | CPUs: 32", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-14', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;However, DuckDB also requires SQL knowledge (though there is an &lt;a class="reference external" href="https://duckdb.org/docs/guides/python/ibis.html"&gt;integration with Ibis Dataframes&lt;/a&gt;). Python users who prefer DataFrames may prefer Polars.  Subjectively, Polars also “feels more Pythonic” which is admittedly squishy and hard to define, but also pretty important.&lt;/p&gt;
&lt;p&gt;While we would recommend DuckDB from a performance standpoint, when given a novel data problem to solve we find ourselves reaching for Polars (or just pandas) more often.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="polars-vs-pandas-and-dask"&gt;
&lt;span id="polars-vs-pandas"&gt;&lt;/span&gt;&lt;h3&gt;Polars vs. Pandas and Dask&lt;/h3&gt;
&lt;p&gt;Polars beats the crap out of Dask locally on smallish data.  Dask beat the crap out of Polars in the cloud on large data.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-13" name="sd-tab-set-5" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-13"&gt;
10 GB (local)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-15"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-816dc1a5bf78307f1e1a971c11043f2f"}, "mark": {"type": "bar"}, "datasets": {"data-816dc1a5bf78307f1e1a971c11043f2f": [{"label": "Polars is faster", "num_cpus": 8, "query": 1, "relative_difference": 0.6855118403187067}, {"label": "Polars is faster", "num_cpus": 8, "query": 2, "relative_difference": 14.427097461616993}, {"label": "Polars is faster", "num_cpus": 8, "query": 3, "relative_difference": 1.9597712907638374}, {"label": "Polars is faster", "num_cpus": 8, "query": 4, "relative_difference": 1.237737132939303}, {"label": "Polars is faster", "num_cpus": 8, "query": 5, "relative_difference": 1.1303083706134631}, {"label": "Dask is faster", "num_cpus": 8, "query": 6, "relative_difference": -0.771656176052355}, {"label": "Dask is faster", "num_cpus": 8, "query": 7, "relative_difference": -1.4530635493559583}, {"label": "Polars is faster", "num_cpus": 8, "query": 8, "relative_difference": 1.5567255750953652}, {"label": "Polars is faster", "num_cpus": 8, "query": 9, "relative_difference": 0.5403069439202067}, {"label": "Polars is faster", "num_cpus": 8, "query": 10, "relative_difference": 1.3007272703976074}, {"label": "Polars is faster", "num_cpus": 8, "query": 11, "relative_difference": 4.603450059520748}, {"label": "Dask is faster", "num_cpus": 8, "query": 12, "relative_difference": -0.12454022728367466}, {"label": "Dask is faster", "num_cpus": 8, "query": 13, "relative_difference": -0.6948479711704048}, {"label": "Polars is faster", "num_cpus": 8, "query": 14, "relative_difference": 0.12245950893958714}, {"label": "Polars is faster", "num_cpus": 8, "query": 15, "relative_difference": 0.2834623618312797}, {"label": "Polars is faster", "num_cpus": 8, "query": 16, "relative_difference": 3.7534834641731027}, {"label": "Polars is faster", "num_cpus": 8, "query": 17, "relative_difference": 0.7546442698641506}, {"label": "Polars is faster", "num_cpus": 8, "query": 18, "relative_difference": 0.858552074982076}, {"label": "Polars is faster", "num_cpus": 8, "query": 19, "relative_difference": 0.07431090017138184}, {"label": "Polars is faster", "num_cpus": 8, "query": 20, "relative_difference": 3.040591415638115}, {"label": "Polars is faster", "num_cpus": 8, "query": 21, "relative_difference": 1.809794343632562}, {"label": "Polars is faster", "num_cpus": 8, "query": 22, "relative_difference": 3.254637172399859}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Polars is faster", "Dask is faster"], "range": ["#5ec962", "#440154"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "scale": {"clamp": true, "domain": [-2, 5]}, "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Polars and Dask", "dy": -18, "fontSize": 22.5, "subtitle": "Local | Scale: 10 GB | CPUs: 8", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-15', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input id="sd-tab-item-14" name="sd-tab-set-5" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-14"&gt;
100 GB (cloud)&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-16"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-733ed42e885ca4a0bed33c35a81948f4"}, "mark": {"type": "bar"}, "datasets": {"data-733ed42e885ca4a0bed33c35a81948f4": [{"label": "Dask is faster", "num_cpus": 32, "query": 1, "relative_difference": 2.445441867956541}, {"label": "Polars is faster", "num_cpus": 32, "query": 2, "relative_difference": -2.6153502989555966}, {"label": "Dask is faster", "num_cpus": 32, "query": 3, "relative_difference": 0.689804646002562}, {"label": "Dask is faster", "num_cpus": 32, "query": 4, "relative_difference": 0.7289288026011733}, {"label": "Polars failed", "num_cpus": 32, "query": 5, "relative_difference": 1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 32, "query": 6, "relative_difference": 4.168489470286718}, {"label": "Polars failed", "num_cpus": 32, "query": 7, "relative_difference": 1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 32, "query": 8, "relative_difference": 1.0599497951170194}, {"label": "Polars failed", "num_cpus": 32, "query": 9, "relative_difference": 1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 32, "query": 10, "relative_difference": 1.42002938566125}, {"label": "Polars is faster", "num_cpus": 32, "query": 11, "relative_difference": -0.6489835294772319}, {"label": "Dask is faster", "num_cpus": 32, "query": 12, "relative_difference": 18.510465773467185}, {"label": "Polars failed", "num_cpus": 32, "query": 13, "relative_difference": 1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 32, "query": 14, "relative_difference": 3.45095297258927}, {"label": "Dask is faster", "num_cpus": 32, "query": 15, "relative_difference": 2.8020200295884523}, {"label": "Polars is faster", "num_cpus": 32, "query": 16, "relative_difference": -0.23234702797056986}, {"label": "Dask is faster", "num_cpus": 32, "query": 17, "relative_difference": 2.39125783241332}, {"label": "Dask is faster", "num_cpus": 32, "query": 18, "relative_difference": 10.506974086026789}, {"label": "Dask is faster", "num_cpus": 32, "query": 19, "relative_difference": 4.78149225681369}, {"label": "Polars is faster", "num_cpus": 32, "query": 20, "relative_difference": -0.3933874563118069}, {"label": "Dask is faster", "num_cpus": 32, "query": 21, "relative_difference": 0.09535915607551461}, {"label": "Polars is faster", "num_cpus": 32, "query": 22, "relative_difference": -0.25272628351215365}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Polars is faster", "Polars failed", "Dask is faster"], "range": ["#5ec962", "#a481aa", "#440154"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "scale": {"clamp": true, "domain": [-3, 5]}, "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Polars and Dask", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 100 GB | CPUs: 32", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-16', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;However, today when we say “Dask” in this context we mean “Dask DataFrame” which is a Dask + pandas project.
In an ideal world, Polars and Dask would work together, much like how pandas and Dask work together today.  Dask handles distributed coordination, but leaves in-memory computing to other projects (in this case Pandas).  It would be very feasible to swap Polars in instead.  They do very different things well, and other things less well (polars works great on a single machine, Dask helps coordinate many machines).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-vs-duckdb"&gt;
&lt;span id="id4"&gt;&lt;/span&gt;&lt;h3&gt;Dask vs. DuckDB&lt;/h3&gt;
&lt;p&gt;We mostly view a competition between Dask and Spark (scalable systems) and DuckDB and Polars (single-machine-but-fast systems) but Dask and DuckDB seem to win competitions often enough that we wanted to see how they compared against each other.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input id="sd-tab-item-15" name="sd-tab-set-6" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-15"&gt;
100 GB&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-17"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-610bd9e2ac9e7817b1c0497c2f6ad209"}, "mark": {"type": "bar"}, "datasets": {"data-610bd9e2ac9e7817b1c0497c2f6ad209": [{"label": "Dask is faster", "num_cpus": 32, "query": 1, "relative_difference": -0.3073167406425239}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 2, "relative_difference": 0.9929639589275254}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 3, "relative_difference": 0.4172914490839405}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 4, "relative_difference": 0.7501230666147395}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 5, "relative_difference": 1.5333870409188197}, {"label": "Dask is faster", "num_cpus": 32, "query": 6, "relative_difference": -0.48504472556375156}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 7, "relative_difference": 0.7815872869477917}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 8, "relative_difference": 1.257621491653817}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 9, "relative_difference": 1.8866121365213622}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 10, "relative_difference": 0.6668626443667}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 11, "relative_difference": 1.1936327922452743}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 12, "relative_difference": 1.1857663568966144}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 13, "relative_difference": 0.2177601211455009}, {"label": "Dask is faster", "num_cpus": 32, "query": 14, "relative_difference": -0.08039170034905477}, {"label": "Dask is faster", "num_cpus": 32, "query": 15, "relative_difference": -1.3743162093390837}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 16, "relative_difference": 1.8769063168802484}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 17, "relative_difference": 1.417434797611948}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 18, "relative_difference": 1.5966422767572659}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 19, "relative_difference": 0.28867146042715747}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 20, "relative_difference": 2.644801781871611}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 21, "relative_difference": 1.7311840254127748}, {"label": "DuckDB is faster", "num_cpus": 32, "query": 22, "relative_difference": 1.447244746922966}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask is faster", "DuckDB is faster"], "range": ["#440154", "#3b528b"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "scale": {"clamp": true, "domain": [-2, 2]}, "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Dask and DuckDB", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 100 GB | CPUs: 32", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-17', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input checked="checked" id="sd-tab-item-16" name="sd-tab-set-6" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-16"&gt;
1 TB&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-18"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-a6593d495f563cf70e08a11cffa80601"}, "mark": {"type": "bar"}, "datasets": {"data-a6593d495f563cf70e08a11cffa80601": [{"label": "Dask is faster", "num_cpus": 128, "query": 1, "relative_difference": 0.26468100052417026}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 2, "relative_difference": -0.846232385425239}, {"label": "Dask is faster", "num_cpus": 128, "query": 3, "relative_difference": 1.8337416163023121}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 4, "relative_difference": -0.27167959077887033}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 5, "relative_difference": -1.0954125223802804}, {"label": "Dask is faster", "num_cpus": 128, "query": 6, "relative_difference": 1.3795249713367312}, {"label": "Dask is faster", "num_cpus": 128, "query": 7, "relative_difference": 1.4368362119457232}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 8, "relative_difference": -1.1379032905408448}, {"label": "Dask is faster", "num_cpus": 128, "query": 9, "relative_difference": 0.062115118224320795}, {"label": "Dask is faster", "num_cpus": 128, "query": 10, "relative_difference": 0.8166343832420967}, {"label": "Dask is faster", "num_cpus": 128, "query": 11, "relative_difference": 0.0438479055140582}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 12, "relative_difference": -0.7699592094327902}, {"label": "Dask is faster", "num_cpus": 128, "query": 13, "relative_difference": 1.8391104192796524}, {"label": "Dask is faster", "num_cpus": 128, "query": 14, "relative_difference": 0.9348412898641382}, {"label": "Dask is faster", "num_cpus": 128, "query": 15, "relative_difference": 3.060225594204156}, {"label": "Dask is faster", "num_cpus": 128, "query": 16, "relative_difference": 0.21327620658085755}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 17, "relative_difference": -1.0572563919380376}, {"label": "DuckDB failed", "num_cpus": 128, "query": 18, "relative_difference": 1.7976931348623157e+308}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 19, "relative_difference": -0.007495694688312985}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 20, "relative_difference": -0.5273401124940653}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 21, "relative_difference": -0.5223971808202568}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 22, "relative_difference": -0.42370275930575063}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask is faster", "DuckDB is faster", "DuckDB failed"], "range": ["#440154", "#3b528b", "#a481aa"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "scale": {"clamp": true, "domain": [-2, 2]}, "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Dask and DuckDB", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 1 TB | CPUs: 128", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-18', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;input id="sd-tab-item-17" name="sd-tab-set-6" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-17"&gt;
10 TB&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;

&lt;div id="tpch-md-altair-plot-19"&gt;
&lt;script&gt;
  // embed when document is loaded, to ensure vega library is available
  // this works on all modern browsers, except IE8 and older
  document.addEventListener("DOMContentLoaded", function(event) {
      var spec = {"config": {"view": {"continuousWidth": 300, "continuousHeight": 300, "strokeOpacity": 0}, "axis": {"labelFontSize": 12, "titleFontSize": 15, "titleFontWeight": 400}, "axisLeft": {"format": ".0%", "labelExpr": "datum.value &gt;= 0 ? datum.label : substring(datum.label, 1)"}, "scale": {"bandPaddingInner": 0.5}, "title": {"fontSize": 22.5, "fontWeight": 400}}, "data": {"name": "data-95d0640b28fea4714e4b8d1e7b4f035a"}, "mark": {"type": "bar"}, "datasets": {"data-95d0640b28fea4714e4b8d1e7b4f035a": [{"label": "Dask is faster", "num_cpus": 128, "query": 1, "relative_difference": 0.28583352366097153}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 2, "relative_difference": -5.2506041203487595}, {"label": "DuckDB failed", "num_cpus": 128, "query": 3, "relative_difference": 1.7976931348623157e+308}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 4, "relative_difference": -0.137520854936937}, {"label": "Dask failed", "num_cpus": 128, "query": 5, "relative_difference": -1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 128, "query": 6, "relative_difference": 1.1345911403780646}, {"label": "DuckDB failed", "num_cpus": 128, "query": 7, "relative_difference": 1.7976931348623157e+308}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 8, "relative_difference": -3.080640312821243}, {"label": "DuckDB failed", "num_cpus": 128, "query": 9, "relative_difference": 1.7976931348623157e+308}, {"label": "DuckDB failed", "num_cpus": 128, "query": 10, "relative_difference": 1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 128, "query": 11, "relative_difference": 0.25239402777504805}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 12, "relative_difference": -0.986603886784626}, {"label": "DuckDB failed", "num_cpus": 128, "query": 13, "relative_difference": 1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 128, "query": 14, "relative_difference": 0.6130466125545926}, {"label": "DuckDB failed", "num_cpus": 128, "query": 15, "relative_difference": 1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 128, "query": 16, "relative_difference": 0.6969883467664838}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 17, "relative_difference": -1.5868036918342139}, {"label": "DuckDB failed", "num_cpus": 128, "query": 18, "relative_difference": 1.7976931348623157e+308}, {"label": "Dask is faster", "num_cpus": 128, "query": 19, "relative_difference": 0.11462238869892528}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 20, "relative_difference": -0.4847621170648527}, {"label": "DuckDB failed", "num_cpus": 128, "query": 21, "relative_difference": 1.7976931348623157e+308}, {"label": "DuckDB is faster", "num_cpus": 128, "query": 22, "relative_difference": -0.07439661830620344}]}, "encoding": {"color": {"field": "label", "legend": {"direction": "horizontal", "labelFontSize": 15, "orient": "top", "title": null, "titleAnchor": "start", "titleFontSize": 15}, "scale": {"domain": ["Dask is faster", "Dask failed", "DuckDB is faster", "DuckDB failed"], "range": ["#440154", "#9da9c5", "#3b528b", "#a481aa"]}, "type": "nominal"}, "tooltip": [{"field": "label", "title": "Who wins?"}, {"field": "relative_difference_tooltip", "format": ".0%", "title": "Rel. Diff."}], "x": {"axis": {"labelAngle": 0}, "field": "query", "title": "Query", "type": "nominal"}, "y": {"field": "relative_difference", "scale": {"clamp": true, "domain": [-2, 2]}, "title": "Relative Difference in Runtime (%)", "type": "quantitative"}}, "title": {"text": "Relative Performance of Dask and DuckDB", "dy": -18, "fontSize": 22.5, "subtitle": "Cloud | Scale: 10 TB | CPUs: 128", "subtitleColor": "#898e90", "subtitleFontSize": 18}, "transform": [{"calculate": "abs(datum.relative_difference)", "as": "relative_difference_tooltip"}], "width": 670, "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json"};
      var opt = {
        "mode": "vega-lite",
        "renderer": "canvas",
        "actions": {"editor": true, "source": true, "export": true}
      };
      vegaEmbed('#tpch-md-altair-plot-19', spec, opt).catch(console.err);
  });
&lt;/script&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;DuckDB generally does better for smaller datasets.  Dask does a bit better on larger datasets.  They’re both pretty solid though.&lt;/p&gt;
&lt;p&gt;These results aren’t quite fair in two ways:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;We’re not looking at local systems, where DuckDB will be faster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We’re constraining Dask to run at 128 cores or less, because DuckDB can’t go higher than that for a fair comparison&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In practice we expect the application and language preference to have a higher impact on choice between these two.  If you have an application that uses SQL then you’ll probably use DuckDB, even when it’s kinda slow at large scale.  If you have an application which requires more generic Python logic you’ll probably choose Dask, even when it’s kinda slow at small scale.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="tpc-h-experimental-details"&gt;
&lt;span id="query-details"&gt;&lt;/span&gt;&lt;h2&gt;TPC-H Experimental Details&lt;/h2&gt;
&lt;p&gt;We ran the TPC-H benchmarks with Dask, Spark, DuckDB and Polars on a variety of scales locally and on the cloud. Implementation details and instructions on how to run the benchmarks yourself are in the public &lt;a class="reference external" href="https://github.com/coiled/benchmarks/tree/13ebb9c72b1941c90b602e3aaea82ac18fafcddc/tests/tpch"&gt;coiled/benchmarks GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-18" name="sd-tab-set-7" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-18"&gt;
Dask&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="c1"&gt;# Get Dask Cluster&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a class="sd-sphinx-override sd-badge sd-outline-primary sd-text-primary reference internal" href="user_guide/dask.html"&gt;&lt;span class="doc"&gt;Learn more&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-19" name="sd-tab-set-7" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-19"&gt;
PySpark&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="c1"&gt;# Get Spark Cluster&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_spark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a class="sd-sphinx-override sd-badge sd-outline-primary sd-text-primary reference internal" href="user_guide/spark.html"&gt;&lt;span class="doc"&gt;Learn more&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-20" name="sd-tab-set-7" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-20"&gt;
DuckDB&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="c1"&gt;# Run DuckDB query&lt;/span&gt;
&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.32xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a class="sd-sphinx-override sd-badge sd-outline-primary sd-text-primary reference internal" href="examples/duckdb-queries-cloud-coiled.html"&gt;&lt;span class="doc"&gt;Learn more&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-21" name="sd-tab-set-7" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-21"&gt;
Polars&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="c1"&gt;# Run Polars Query&lt;/span&gt;
&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.32xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a class="sd-sphinx-override sd-badge sd-outline-primary sd-text-primary reference internal" href="blog/polars-queries-cloud-coiled.html"&gt;&lt;span class="doc"&gt;Learn more&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;While infrastructure setup was easy, no project “just worked” efficiently and everything required some degree of looking up documentation, tweaking configuration parameters to get decent performance at scale, passing credentials around, etc., but some more than others (gah! Spark.)  Some projects degraded decently even when not optimally configured (DuckDB and Dask, nice job) while others failed ungracefully, grinding machines to a halt (Spark and Polars).&lt;/p&gt;
&lt;section id="bias"&gt;
&lt;h3&gt;Bias&lt;/h3&gt;
&lt;p&gt;We’ve done our best to avoid intentionally tuning for our favorite project. We’ve chosen benchmarks chosen by other groups, run everything on standard hardware, and have asked the following experts in other systems to help ensure good performance for the different systems:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Polars: &lt;a class="reference external" href="https://www.ritchievink.com/"&gt;Ritchie Vink&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spark: Jeffrey Chou and Vinoo Ganesh of &lt;a class="reference external" href="https://synccomputing.com/"&gt;Sync Computing&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DuckDB: no one, but this didn’t seem necessary (they do pretty well!)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although of course everyone’s time is limited, and asking experts for input makes no guarantee that we’re still not missing important opportunities for performance in those projects.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dataset-layout"&gt;
&lt;h3&gt;Dataset Layout&lt;/h3&gt;
&lt;p&gt;The datasets used in our benchmarks are derived from the datasets generated for the TPC-H benchmarks at the corresponding scale factors stored in Parquet format. The scale reflects the &lt;em&gt;total&lt;/em&gt; size of the entire dataset. To generate the data, we used the &lt;a class="reference external" href="https://duckdb.org/docs/extensions/tpch.html"&gt;DuckDB TPC-H extension&lt;/a&gt; to generate the data and PyArrow to write partitions of ~100 MB each to disk. The resulting partitions are partially inhomogenous, which reflects the datasets we encounter in practice. All files are compressed with Snappy. See &lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/13ebb9c72b1941c90b602e3aaea82ac18fafcddc/tests/tpch/generate_data.py"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;generate_data.py&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; for details.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="measurements"&gt;
&lt;h3&gt;Measurements&lt;/h3&gt;
&lt;p&gt;We report three main metrics:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Runtime&lt;/strong&gt;:
This is the end-to-end runtime from generating the query to receiving the result (in seconds). This is our primary measurement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Relative Difference in Runtime&lt;/strong&gt;: This metric shows how large the performance difference between two systems is (in percent). We calculated the relative difference in runtime betwen two systems by dividing the absolute runtime difference by the runtime of the faster system:&lt;/p&gt;
&lt;div class="math notranslate nohighlight"&gt;
\[\text{Relative Difference in Runtime} = abs(\frac{\text{Runtime}_X - \text{Runtime}_Y}{min(\text{Runtime}_X, \text{Runtime}_Y)})\]&lt;/div&gt;
&lt;p&gt;Calculated this way, the measurement is symmetric, i.e., the value does not change if we compare X to Y or Y to X, and it is linear, i.e., each multiple of absolute difference will result in a multiple of the relative difference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Query Completion&lt;/strong&gt;:
This boolean measurement shows whether the system completed the query by returning a result. If the system did not complete the query, it either encountered a hard failure (like processes being killed because they ran out of memory) or it timed out after several hours.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="local-configuration"&gt;
&lt;h3&gt;Local Configuration&lt;/h3&gt;
&lt;p&gt;The local benchmarks were run for scales 10 (10 GB) and 100 (100 GB). We executed them on a MacBook Pro with an M1 (8-core) Apple silicon CPU and 16 GB of RAM and the built-in SSD used for storage. We did our best to create reproducible benchmarks by closing noisy background processes where possible, but your mileage may vary here depending on what background processes you still have running on your machine. The software  was installed with conda/mamba with builds from conda-forge. The datasets were stored on local SSD for fast access.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="cloud-configuration"&gt;
&lt;h3&gt;Cloud Configuration&lt;/h3&gt;
&lt;p&gt;The benchmarks were run on AWS on the cloud for scales 10 (10 GB), 100 (100 GB), 1,000 (1 TB), and 10,000 (10 TB) using EC2 instances from the M6i family (more on why we prefer &lt;a class="reference internal" href="blog/burstable-vs-nonburstable.html"&gt;&lt;span class="doc"&gt;non-burstable instances&lt;/span&gt;&lt;/a&gt;). DuckDB and Polars ran on a single, large machine with &lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;Coiled serverless functions&lt;/a&gt; while Dask and Spark ran on clusters. For scale 10,000 (10 TB), we performed two experiments and scaled the amount of vCPUs from 320 by a factor of 10 to 320. Since there is no instance type with 320 vCPUs available, this experiment was only run on the scale-out capable systems, Dask and PySpark.&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table" id="id6"&gt;
&lt;caption&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Cloud hardware used for running benchmarks at various scales (see the &lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/13ebb9c72b1941c90b602e3aaea82ac18fafcddc/tests/tpch/utils.py#L42-L105"&gt;configuration&lt;/a&gt; for more details).&lt;/em&gt;&lt;/span&gt;&lt;/caption&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-right"&gt;&lt;p&gt;Scale&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-right"&gt;&lt;p&gt;vCPUs&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-right"&gt;&lt;p&gt;Memory&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-right"&gt;&lt;p&gt;Instances (single)&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-right"&gt;&lt;p&gt;Instances (cluster)&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-right"&gt;&lt;p&gt;100 GB&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;32&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;128 GiB&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;1 x &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.8xlarge&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;16 x &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.large&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td class="text-right"&gt;&lt;p&gt;1 TB&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;128&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;512 GiB&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;1 x &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.32xlarge&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;32 x &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.xlarge&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-right"&gt;&lt;p&gt;10 TB&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;128&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;512 GiB&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;1 x &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.32xlarge&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;32 x &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.xlarge&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td class="text-right"&gt;&lt;p&gt;10 TB&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;1280&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;5 TiB&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;n/a&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-right"&gt;&lt;p&gt;320 x &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.xlarge&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;The datasets are stored on S3 in the public requester-pays bucket with subdirectories for each scale (e.g. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-runtime-ci/tpc-h/snappy/scale-10/&lt;/span&gt;&lt;/code&gt;).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="library-specific-configuration"&gt;
&lt;h3&gt;Library-Specific Configuration&lt;/h3&gt;
&lt;section id="spark"&gt;
&lt;h4&gt;Spark&lt;/h4&gt;
&lt;p&gt;For Spark, we manually configured memory limits in order to utilize existing memory while avoiding OOM errors. For cloud-based benchmarks, we configured &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;spark.executor.memory&lt;/span&gt;&lt;/code&gt; to 80% of the available memory after tweaking for several iterations. For local benchmarks, we configured &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;spark.driver.memory&lt;/span&gt;&lt;/code&gt; to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;10g&lt;/span&gt;&lt;/code&gt; to avoid &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;collect&lt;/span&gt;&lt;/code&gt; getting stuck. Locally, we also had to manually set &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;spark.driver.host&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;spark.driver.bindAddress&lt;/span&gt;&lt;/code&gt; to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;127.0.0.1&lt;/span&gt;&lt;/code&gt; since the default does not work for M1 MacBooks. We used SparkSQL to run the benchmarks.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask"&gt;
&lt;h4&gt;Dask&lt;/h4&gt;
&lt;p&gt;For Dask, we enabled &lt;a class="reference external" href="https://pandas.pydata.org/docs/user_guide/copy_on_write.html"&gt;Copy-on-Write (COW)&lt;/a&gt;, which will become the default in pandas 3, which will be released soon. We also disabled &lt;a class="reference external" href="https://distributed.dask.org/en/stable/scheduling-policies.html#queuing"&gt;task queueing&lt;/a&gt;.
Since Dask is a dataframe-based system, we had to translate the reference SQL queries to idiomatic Dask queries. As we currently lack join order optimization (see &lt;a class="reference external" href="https://github.com/dask/dask-expr/issues/1065"&gt;dask-expr#1065&lt;/a&gt;), we had to manually order joins.
For this, we diverged from the order in which tables were introduced in the SQL query to a manually optimized order that a careful user would typically choose when following the dimensions and facts defined in the TPC-H schema.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="duckdb"&gt;
&lt;h4&gt;DuckDB&lt;/h4&gt;
&lt;p&gt;For DuckDB, we did not perform any configuration apart from passing security credentials.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="polars"&gt;
&lt;h4&gt;Polars&lt;/h4&gt;
&lt;p&gt;Since Polars is a dataframe-based system, the reference SQL queries had to be translated to idiomatic Polars queries. For this, we reused the reference implementations &lt;a class="reference external" href="https://github.com/pola-rs/tpch/tree/c306449be4cbf81506ef3845aef98897206190f7"&gt;published by Polars&lt;/a&gt;.
To allow Polars to process larger-than-memory datasets, we executed all queries in &lt;a class="reference external" href="https://docs.pola.rs/user-guide/concepts/_streaming/"&gt;streaming mode&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="learn-more"&gt;
&lt;h2&gt;Learn More&lt;/h2&gt;
&lt;p&gt;This article covers quantitative results on the TPC-H Benchmark across common DataFrame libraries.
For information on how to run these projects in the cloud yourself, consider looking at the Coiled documentation for each project:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="user_guide/dask.html"&gt;&lt;span class="doc"&gt;Dask&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="user_guide/spark.html"&gt;&lt;span class="doc"&gt;Spark&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/duckdb-queries-cloud-coiled.html"&gt;&lt;span class="doc"&gt;DuckDB&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/polars-queries-cloud-coiled.html"&gt;&lt;span class="doc"&gt;Polars&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We (Coiled) care about making data processing in the cloud easy, which involves running the right tool for the right job.  That being said, we’re also biased towards Dask.  If you’d like to read more about recent Dask performance, or how it compares to Spark today, we invite you to consider the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/spark-vs-dask.html"&gt;&lt;span class="doc"&gt;Dask is better than Spark&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/dask-dataframe-is-fast.html"&gt;&lt;span class="doc"&gt;Dask DataFrame is Fast Now&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/tpch.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;May 14, 2024</summary>
    <published>2024-05-14T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-dataframe-is-fast.html</id>
    <title>Dask DataFrame is Fast Now</title>
    <updated>2024-05-14T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;May 14, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;10m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="dask-dataframe-is-fast-now"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/phofl"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/61934744?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Patrick Hoefler&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default" id="id4"&gt;
&lt;img alt="Bar chart showing a nearly 20x improvement in Dask DataFrame performance with the addition of Arrow stings, more efficient shufffling, and a query optimizer." src="https://docs.coiled.io/_images/dask-improvement.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Performance Improvements for Dask DataFrames&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;section id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;Dask DataFrame scales out pandas DataFrames to operate at the 100GB-100TB scale.&lt;/p&gt;
&lt;p&gt;Historically, &lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; was pretty slow compared to other tools in this space (like Spark). Due to a number of improvements focused on performance,
it’s now pretty fast (about 20x faster than before). The new implementation moved Dask from getting destroyed by
Spark on every benchmark to regularly outperforming Spark on TPC-H queries by a significant margin.&lt;/p&gt;
&lt;p&gt;Dask DataFrame workloads struggled with many things. Performance and memory usage were
commonly seen pain points, shuffling was unstable for bigger datasets, making scaling out
hard. Writing efficient code required understanding too much of the internals of Dask.&lt;/p&gt;
&lt;p&gt;The new implementation changed all of this. Things that didn’t work were completely rewritten from scratch and existing
implementations were improved upon. This puts Dask DataFrames on a solid foundation that
allows faster iteration cycles in the future.&lt;/p&gt;
&lt;p&gt;We’ll go through the three most prominent changes:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#arrow"&gt;Apache Arrow support&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#p2p"&gt;Faster joins&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#optimizer"&gt;Query optimization&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’ll cover how these changes impact performance and make it easier to use Dask efficiently, even for users that are new to distributed computing. We’ll also discuss plans for future improvements.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="apache-arrow-support-efficient-string-datatype"&gt;
&lt;span id="arrow"&gt;&lt;/span&gt;&lt;h2&gt;1. Apache Arrow Support: Efficient String Datatype&lt;/h2&gt;
&lt;p&gt;A Dask DataFrame consists of many pandas DataFrames. Historically, pandas used NumPy for numeric data,
but Python objects for text data, which are inefficient and blow up memory usage. Operations on
object data also hold the GIL, which doesn’t matter much for pandas, but is a catastrophy for
performance with a parallel system like Dask.&lt;/p&gt;
&lt;p&gt;The pandas 2.0 release introduced support for general-purpose Arrow datatypes, so &lt;a class="reference external" href="https://docs.coiled.io/blog/pyarrow-in-pandas-and-dask.html"&gt;Dask now uses PyArrow-backed
strings by default&lt;/a&gt;.  These are &lt;em&gt;much&lt;/em&gt; better.  PyArrow strings reduce memory usage by up to 80% and
unlock multi-threading for string operations. Workloads that previously
struggled with available memory now fit comfortably in much less space, and are
a lot faster because they no longer constantly spill excess data to disk.&lt;/p&gt;
&lt;figure class="align-default" id="id5"&gt;
&lt;img alt="Bar chart comparing memory usage (GB) for Dask DataFrame with and without PyArrow strings. Dask DataFrame uses up to 80% less memory with PyArrow strings." src="https://docs.coiled.io/_images/arrow-strings-memory-usage.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Memory Usage of the Legacy DataFrames Compared with Arrow Strings&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="faster-joins-with-a-new-shuffle-algorithm"&gt;
&lt;span id="p2p"&gt;&lt;/span&gt;&lt;h2&gt;2. Faster Joins with a New Shuffle Algorithm&lt;/h2&gt;
&lt;p&gt;Shuffling is an essential component of distributed systems to enable sorting, joins, and complex group by operations. It is an all-to-all, network-intensive operation that’s often the most expensive component in a workflow. Dask has a new shuffling system, which greatly impacts overall performance, especially on complex, data-intensive workloads.&lt;/p&gt;
&lt;p&gt;A shuffle operation is intrinsically an all-to-all communication operation where every
input partition has to provide a tiny slice of data to every output partition. Dask was already
using it’s own task-based algorithm that managed to reduce the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;O(n&lt;/span&gt; &lt;span class="pre"&gt;*&lt;/span&gt; &lt;span class="pre"&gt;n)&lt;/span&gt;&lt;/code&gt;  task
complexity to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;O(log(n)&lt;/span&gt; &lt;span class="pre"&gt;*&lt;/span&gt; &lt;span class="pre"&gt;n)&lt;/span&gt;&lt;/code&gt; where &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;n&lt;/span&gt;&lt;/code&gt; is the number of partitions. This was a drastic
reduction in the number of tasks, but the non-linear scaling ultimately did not allow Dask to process
arbitrarily large datasets.&lt;/p&gt;
&lt;p&gt;Dask introduced a new &lt;a class="reference external" href="https://docs.coiled.io/blog/shuffling-large-data-at-constant-memory.html"&gt;P2P (peer-to-peer) shuffle  method&lt;/a&gt; that reduced the task complexity to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;O(n)&lt;/span&gt;&lt;/code&gt;
which scales linearly with the size of the dataset and the size of the cluster. It also
incorporates an efficient disk integration which allows easily shuffling datasets which are much
larger than memory.  The new system is extremely stable and “just works” across any scale of data.&lt;/p&gt;
&lt;figure class="align-default" id="id6"&gt;
&lt;img alt="Line plot of memory usage (GB) over time (seconds) comparing Dask DataFrame with the peer-to-peer shuffling to task-based shuffling. With P2P shuffling, Dask DataFrame memory usage remains consistently low throughout the computation." src="https://docs.coiled.io/_images/shuffle-memory-comparison.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Memory Usage of the Legacy Shuffle Compared with P2P&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="optimizer"&gt;
&lt;span id="id1"&gt;&lt;/span&gt;&lt;h2&gt;3. Optimizer&lt;/h2&gt;
&lt;p&gt;Dask itself is lazy, which means that it registers your whole query before doing any actual work.
This is a powerful concept that enables a lot of optimizations, but historically Dask wasn’t taking advantage of this
knowledge in the past. Dask also did a bad job of hiding internal complexities and
left users on their own while navigating the difficulties of distributed computing and running
large scale queries. It made writing efficient code painful for non-experts.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.dask.org/en/stable/changelog.html#query-planning"&gt;The Dask release in March&lt;/a&gt; includes a complete re-implementation of the DataFrame API to support query optimization. This is a big deal.
The new engine centers around a query optimizer that rewrites your code to make it more efficient and
better tailored to Dask’s strengths. Let’s dive into some optimization strategies, how they make
Dask run faster and scale better.&lt;/p&gt;
&lt;p&gt;We will start with a couple general-purpose optimizations:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#column-projection"&gt;Column projection&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#filter-pushdown"&gt;Filter pushdown&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And then dive into more specific techniques that are tailored to distributed systems generally
and Dask more specifically:&lt;/p&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#partition-resizing"&gt;Automatic partition resizing&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#trivial-merge"&gt;Trivial merge and join operations&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;section id="column-projection"&gt;
&lt;span id="id2"&gt;&lt;/span&gt;&lt;h3&gt;3.1 Column Projection&lt;/h3&gt;
&lt;p&gt;Most datasets have more columns than needed. Dropping them requires foresight (“What columns will I need for this query? 🤔”) so most people don’t think about this when loading data. This is bad for performance because carrying around lots of excess data slows everything down.
Column Projection drops columns as soon as they aren’t needed anymore. It’s a straightforward optimization, but highly beneficial.&lt;/p&gt;
&lt;p&gt;The legacy implementation always reads all columns from storage and only drops columns when explicitly specified by the user.
Simply operating on less data is a big win for performance and memory usage.&lt;/p&gt;
&lt;p&gt;The optimizer looks at the query and figures out which columns are needed for each operation.
It looks at the final step of the query and then works backwards step by
step to the data source, injecting drop operations to get rid of unnecessary columns.&lt;/p&gt;
&lt;figure class="align-default" id="id7"&gt;
&lt;img alt="Diagram explaining how column projection works for the Dask DataFrame optimizer." src="https://docs.coiled.io/_images/projection.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Only require a subset of columns are needed. Replace doesn’t need access to all columns, so Dask drops unnecessary columns directly in the IO step.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="filter-pushdown"&gt;
&lt;span id="id3"&gt;&lt;/span&gt;&lt;h3&gt;3.2 Filter Pushdown&lt;/h3&gt;
&lt;p&gt;Filter pushdown is another general-purpose optimization with
the same goal as column projection: operate on less data. The legacy implementation did not reorder filter operations. The new implementation executes filter operations as early as
possible while maintaining the same results.&lt;/p&gt;
&lt;p&gt;The optimizer identifies every filter in the query and looks at the previous operation to see if we
can move the filter closer to the data source. It will repeat this until it finds an operation that
can’t be switched with a filter. This is a bit harder than
column projections, because Dask has to make sure that the operations don’t change the values of the
DataFrame. For example, switching a filter and a merge operation is fine (values don’t change), but switching a filter
and a replace operation is invalid, because the values might change and rows that would previously have been filtered out now won’t be, or vice versa.&lt;/p&gt;
&lt;figure class="align-default" id="id8"&gt;
&lt;img alt="Diagram explaining how filter pushdown (or predicate pushdown) works to reduce the amount of data being processed in the Dask DataFrame query optimizer." src="https://docs.coiled.io/_images/filter.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Initially, the filter happens after the Dropna, but Dask can execute the filter before Dropna without changing the result. This allows Dask to push the filter into the IO step.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Additionally, if the filter is strong enough then Dask can potentially drop complete files in the IO step.
This is a best-case scenario, where an earlier filter brings a huge performance improvement and even
requires reading less data from remote storage.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="automatically-resizing-partitions"&gt;
&lt;span id="partition-resizing"&gt;&lt;/span&gt;&lt;h3&gt;3.3 Automatically Resizing Partitions&lt;/h3&gt;
&lt;p&gt;In addition to implementing the common optimization techniques described above, we’ve also improved a
common pain point specific to distributed systems generally and Dask users specifically: optimal partition sizes.&lt;/p&gt;
&lt;p&gt;Dask DataFrames consist of many small pandas DataFrames called &lt;em&gt;partitions&lt;/em&gt;. Often, the number of
partitions is decided for you and Dask users are advised to manually “repartition” after reducing
or expanding their data (for example by dropping columns, filtering data, or expanding with joins) (see the &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe-best-practices.html#repartition-to-reduce-overhead"&gt;Dask docs&lt;/a&gt;).
Without this extra step,
the (usually small) overhead from Dask can become a bottleneck if the pandas DataFrames
become too small, making Dask workflows painfully slow.&lt;/p&gt;
&lt;p&gt;Manually controlling the partition size is a difficult task that we, as Dask users, shouldn’t have
to worry about. It is also slow because it requires network transfer of some partitions.
Dask DataFrame now automatically does two things to help when the partitions get
too small:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Keeps the size of each partition constant, based on the ratio of data you want to compute vs.
the original file size. If, for example, you filter out 80% of the original dataset, Dask will
automatically combine the resulting smaller partitions into fewer, larger partitions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Combines too-small partitions into larger partitions, based on an absolute minimum
(default is 75 MB). If, for example, your original dataset is split into many tiny files,
Dask will automatically combine them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="align-default" id="id9"&gt;
&lt;img alt="Diagram representing how column selection can reduce the size of a partition when reading parquet data." src="https://docs.coiled.io/_images/automatic_repartitioning_1.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Select two columns that take up 40 MB of memory out of the 200 MB from the whole parquet file.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The optimizer will look at the number of columns and the size of the data within those. It
calculates a ratio that is used to combine multiple files into one partition.&lt;/p&gt;
&lt;figure class="align-default" id="id10"&gt;
&lt;img alt="Diagram showing how automatic repartitioning works for Dask DataFrame." src="https://docs.coiled.io/_images/automatic_repartitioning_2.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;The ratio of 40/200 results in combining five files into a single partition.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This step is currently limited to IO operations (like reading in a parquet dataset), but we plan
to extend it to other operations that allow cheaply combining partitions.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="trivial-merge-and-join-operations"&gt;
&lt;span id="trivial-merge"&gt;&lt;/span&gt;&lt;h3&gt;3.4 Trivial Merge and Join Operations&lt;/h3&gt;
&lt;p&gt;Merge and join operations are typically cheap on a single machine with pandas but expensive in a
distributed setting.  Merging data in shared memory is cheap, while merging data across a network is quite slow,
due to the shuffle operations explained earlier.&lt;/p&gt;
&lt;p&gt;This is one of the most expensive operations in a distributed system. The legacy implementation triggered
a network transfer of both input DataFrames for every merge operation. This is sometimes necessary, but very
expensive.&lt;/p&gt;
&lt;figure class="align-default" id="id11"&gt;
&lt;img alt="Diagram representing how an expensive shuffle operation is avoided automatically if two DataFrames are already aligned before joining." src="https://docs.coiled.io/_images/avoiding-shuffles.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Both joins are performed on the same column. The left DataFrame is already properly partitioned after the first join, so Dask can avoid shuffling again with the new implementation.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The optimizer will determine when shuffling is necessary versus when
a trivial join is sufficient because the data is already aligned properly. This can make individual merges
an order of magnitude faster. This also applies to other operations that normally require a shuffle
like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;groupby().apply()&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Dask merges used to be inefficient, which caused long runtimes. The optimizer fixes this for
the trivial case where these operations happen after each other, but the technique isn’t very
advanced yet. There is still a lot of potential for improvement.&lt;/p&gt;
&lt;figure class="align-default" id="id12"&gt;
&lt;img alt="Diagram showing how the query optimizer for Dask DataFrame automatically shuffles data earlier to make a groupby aggregation more efficient." src="https://docs.coiled.io/_images/avoiding-shuffles-advanced.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;The current implementation shuffles both branches that originate from the same table. Injecting a shuffle node further up avoids one of the expensive operations.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The optimizer will look at the expression and inject shuffle nodes where necessary to avoid
unnecessary shuffles.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="how-do-the-improvements-stack-up-compared-to-the-legacy-implementation"&gt;
&lt;h2&gt;How do the improvements stack up compared to the legacy implementation?&lt;/h2&gt;
&lt;p&gt;Dask is now 20x faster than before.  This improvement applies to the entire
DataFrame API (not just isolated components), with no known
performance regressions.  Dask now runs workloads that were impossible to
complete in an acceptable timeframe before.  This performance boost is due to many
improvements all layered on top of each other.  It’s not about doing one thing
especially well, but about doing nothing especially poorly.&lt;/p&gt;
&lt;figure class="align-default" id="id13"&gt;
&lt;img alt="Bar chart showing a nearly 20x improvement in Dask DataFrame performance with the addition of Arrow stings, more efficient shufffling, and a query optimizer." src="https://docs.coiled.io/_images/dask-improvement.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Performance Improvements on Query 3 of the TPC-H Benchmarks from &lt;a class="github reference external" href="https://github.com/coiled/benchmarks/tree/main/tests/tpch"&gt;coiled/benchmarks&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Performance, while the most enticing improvement, is not the only thing that got better. The
optimizer hides a lot of complexity from the user and makes the transition from pandas to Dask a
lot easier because it’s now much more difficult to write poorly performing code.
The whole system is more robust.&lt;/p&gt;
&lt;p&gt;The new architecture of the API is a lot easier to work with as well. The legacy implementation leaked
a lot of internal complexities into high-level API implementations, making changes cumbersome. Improvements
are almost trivial to add now.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-s-to-come"&gt;
&lt;h2&gt;What’s to come?&lt;/h2&gt;
&lt;p&gt;Dask DataFrame changed a lot over the last 18 months. The legacy API was often difficult to work with and
struggled with scaling out. The new implementation dropped things that didn’t work and
improved existing implementations. The heavy lifting is finished now, which allows for
faster iteration cycles to improve upon the status quo. Incremental improvements are now
trivial to add.&lt;/p&gt;
&lt;p&gt;A few things that are on the immediate roadmap:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auto repartitioning:&lt;/strong&gt; this is partially implemented, but there is more potential to choose a more
efficient partition size during optimization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Faster Joins:&lt;/strong&gt; there’s still lots of fine-tuning to be done here.
For example, there is a PR in flight with a 30-40% improvement.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Join Reordering:&lt;/strong&gt; Dask doesn’t do this yet, but it’s on the immediate roadmap&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="learn-more"&gt;
&lt;h2&gt;Learn more&lt;/h2&gt;
&lt;p&gt;This article focuses on a number of improvements to Dask DataFrame and how much faster and more reliable it is as a result. If you’re choosing between Dask and other popular DataFrame tools, you might also consider:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/tpch.html"&gt;&lt;span class="doc"&gt;DataFrames at Scale Comparison: TPC-H&lt;/span&gt;&lt;/a&gt; which compares Dask, Spark, Polars, and DuckDB performance on datasets ranging from 10 GB to 10 TB both locally and on the cloud&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-dataframe-is-fast.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;May 14, 2024</summary>
    <published>2024-05-14T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/spark-vs-dask.html</id>
    <title>Dask vs. Spark</title>
    <updated>2024-04-19T00:00:00+00:00</updated>
    <author>
      <name>Florian Jetter</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Apr 19, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;6m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="dask-vs-spark"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Sarah Johnson, Florian Jetter&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;People often ask “&lt;em&gt;How does Dask compare to Spark?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Apache Spark has been the industry standard for big data, although it’s not clear that anyone especially loves it, especially in the Python space. The promise of Spark is that it won’t topple over no matter how large your data gets, and for years it’s been the best big data tool available.&lt;/p&gt;
&lt;p&gt;Today, though, there are a number of modern solutions for churning through TBs of data. &lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt;, in particular, is an attractive alternative to Spark because it’s:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Performant at large scale (and faster than Spark)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy to use (and easier to debug)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Highly flexible (does everything from ETL to machine learning)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’re Dask fans and this post isn’t shy about showing that bias. For a less biased treatment of this same topic, see the &lt;a class="reference external" href="https://docs.dask.org/en/stable/spark.html"&gt;OSS Dask documentation page&lt;/a&gt; where we’ve tried to keep a more balanced tone.&lt;/p&gt;
&lt;section id="dask-is-faster-than-spark"&gt;
&lt;h2&gt;Dask is Faster than Spark&lt;/h2&gt;
&lt;p&gt;We compared Dask and Spark on the TPC-H benchmark suite and can confidently claim that Dask is not only easier to use, but often faster and more reliable than Spark. See &lt;a class="reference internal" href="blog/tpch.html"&gt;&lt;span class="doc"&gt;DataFrames at Scale Comparison: TPC-H&lt;/span&gt;&lt;/a&gt; which compares Dask, Spark, Polars, and DuckDB performance to learn more.&lt;/p&gt;
&lt;p&gt;We’ll show results from running the TPC-H benchmarks locally on a 10 GB dataset and on the cloud on a 10 TB dataset. These two cases are important because developers typically want to iterate locally on a small sample before scaling out to the full dataset on a production job on the cloud.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Bar chart comparing the relative difference in TPC-H query runtime for Dask vs. PySpark when executed on a M1 MacBook Pro with 8 cores. Orange represents queries where Dask is faster and blue where PySpark is faster." src="https://docs.coiled.io/_images/dask_vs_spark_local.png" style="width: 800px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Dask consistently outperforms PySpark on a 10 GB dataset that fits on a typical laptop. Dask is up to 507% faster than Spark.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When running the TPC-H benchmarks locally on an M1 MacBook Pro, Dask is up to 507% faster than Spark. Since Dask is faster locally, this makes it easier for developers to iterate quickly.&lt;/p&gt;
&lt;p&gt;There were some challenges with all queries successfully completing when scaling the benchmarks to a 10 TB dataset. Originally, we ran these queries on an &lt;em&gt;underprovisioned cluster&lt;/em&gt; (32 x m6i.xlarge / 128 CPUs / 512 GB). Dask failed on three of the 22 queries, but handled everything else gracefully (albeit slowly). Spark, however, failed on all queries, either due to running out of memory, running out of disk, or running into an hour-long timeout.&lt;/p&gt;
&lt;figure class="align-default" id="id2"&gt;
&lt;img alt="Bar chart comparing the TPC-H query runtime for Dask vs. PySpark on 10 TB scale running on a cluster with about 5 TB of memory and 1280 CPUs. Dask is shown in orange, PySpark in blue, and grey where PySpark failed to complete the query or ran into a timeout." src="https://docs.coiled.io/_images/dask_vs_spark_cloud_10k.png" style="width: 800px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Dask can finish many queries that PySpark cannot on a cluster with 5 TB of memory. PySpark was unable complete seven of the 22 queries (shown in grey).&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We also ran the benchmarks on a cluster with 5 TB of memory and 1280 CPU cores. This should be sufficient, since systems should be able to deal with data that is modestly larger than RAM. While Dask completed 21 of 22 queries, Spark either failed or ran into an hour-long timeout on seven queries. This is problematic because it causes engineers to over-provision clusters with too large machines by habit, ultimately increasing cost.&lt;/p&gt;
&lt;figure class="align-default" id="id3"&gt;
&lt;img alt="Relative difference between Dask vs. PySpark on 10 TB scale running on a cluster with about 5 TB of memory and 1280 CPUs. Orange represents queries where Dask is faster, blue where PySpark is faster, and grey where PySpark failed." src="https://docs.coiled.io/_images/dask_vs_spark_cloud_10k_relative.png" style="width: 800px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Relative difference between Dask vs PySpark. Dask is up to 112% faster than Spark for queries they both completed.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For queries that Dask and PySpark completed, Dask is often faster. This is largely due to a number of engineering improvements in Dask, see &lt;a class="reference internal" href="blog/dask-dataframe-is-fast.html"&gt;&lt;span class="doc"&gt;Dask DataFrame is Fast Now&lt;/span&gt;&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-is-easier-to-use-than-spark"&gt;
&lt;h2&gt;Dask is easier to use than Spark&lt;/h2&gt;
&lt;p&gt;People are both slower and more expensive than computers, so we optimize for people. Users frequently praise how much easier Dask is to use than Spark.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;A lot of the Spark internals are run on the JVM, so it’s actually not that easy to debug. You spend a lot of time on Stack Overflow trying to figure out what did I do wrong, why am I getting this traceback. It was painful and we wanted to use something that was not as difficult to actually debug.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;- Sébastien Arnaud, CDO/CTO at Steppingblocks, on &lt;a class="reference external" href="https://youtu.be/jR0Y7NqKJs8?si=UhhZ6jZjsY4nPqvO&amp;amp;amp;t=413"&gt;why they switched from Spark to Dask&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;section id="easy-to-learn"&gt;
&lt;h3&gt;Easy to learn&lt;/h3&gt;
&lt;p&gt;Dask is well-integrated in the larger Python ecosystem; it complements and enhances other popular libraries like pandas, NumPy, and scikit-learn. Many users are already familiar with Python for handling large datasets. Dask looks and feels a lot like pandas, so it’s easier to get started quickly.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="c1"&gt;# use pandas when your data fit in memory&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;small-data.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# use Dask when you can&amp;#39;t read in your data on a single machine&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;large-data.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="easy-to-set-up"&gt;
&lt;h3&gt;Easy to set up&lt;/h3&gt;
&lt;p&gt;Historically, Spark had better support for enterprise data sources like Delta Lake and Snowflake. Dask has caught up though, and today with projects like &lt;a class="reference external" href="https://delta-io.github.io/delta-rs/"&gt;delta-rs&lt;/a&gt; (the Rust re-implementation of Delta Lake) and Snowflake’s bulk read/write capabilities, Dask gives a first-class user experience on-par with any other technology.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
Delta Lake&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;You can create a Delta Lake table from a pandas dataframe and use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask_deltatable&lt;/span&gt;&lt;/code&gt; to read in a Delta Lake table into Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;deltalake&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;deltalake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mytable&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;append&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_deltatable&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dask_deltatable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_deltalake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mytable&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2020-01-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
Snowflake&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;With &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask_snowflake&lt;/span&gt;&lt;/code&gt;, you can read from a Snowflake table into a Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_snowflake&lt;/span&gt;

&lt;span class="n"&gt;example_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;    SELECT *&lt;/span&gt;
&lt;span class="s2"&gt;    FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_snowflake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;example_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;connection_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;password&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="easy-to-debug"&gt;
&lt;h3&gt;Easy to Debug&lt;/h3&gt;
&lt;p&gt;Debugging Spark is painful, often accounting for most of the time spent with the technology. In contrast, Dask works hard to make this experience almost as easy as it is in a single-machine context. Dask users especially love the &lt;a class="reference external" href="https://docs.dask.org/en/stable/dashboard.html"&gt;Dask dashboard&lt;/a&gt;, which gives them a lot of understanding about how their computation is going.&lt;/p&gt;
&lt;script src="https://fast.wistia.com/embed/medias/l9sgt2saht.jsonp" async&gt;&lt;/script&gt;&lt;script src="https://fast.wistia.com/assets/external/E-v1.js" async&gt;&lt;/script&gt;&lt;div class="wistia_responsive_padding" style="padding:75.0% 0 0 0;position:relative;"&gt;&lt;div class="wistia_responsive_wrapper" style="height:100%;left:0;position:absolute;top:0;width:100%;"&gt;&lt;div class="wistia_embed wistia_async_l9sgt2saht seo=false videoFoam=true" style="height:100%;position:relative;width:100%"&gt;&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section id="easy-to-deploy"&gt;
&lt;h3&gt;Easy to Deploy&lt;/h3&gt;
&lt;p&gt;Deploying Spark, or even configuring it locally, is a pain. Spark users often mention it requires digging through docs and finding obscure configuration settings just to get anything running. We really don’t see it in the wild, except under some SaaS deployment solution like Databricks, EMR, Synapse, Dataproc, etc.&lt;/p&gt;
&lt;p&gt;Dask on the other hand has SaaS solutions like Coiled, but is also often deployed “raw” using systems like Kubernetes or HPC job managers (see the &lt;a class="reference external" href="https://docs.dask.org/en/stable/deploying.html"&gt;Dask Deployment docs&lt;/a&gt;). We’re a bit biased, but the main SaaS solution, &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;, is &lt;em&gt;way easier to use&lt;/em&gt; than any of the Spark SaaS solutions.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/yWz4HR-zHbU?si=nIGBF7bJ5f5Fw9gr" data-hj-allow-iframe="true" style="margin: 0 auto 20px auto; display: block;" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;&lt;/section&gt;
&lt;/section&gt;
&lt;section id="dask-does-more-than-spark"&gt;
&lt;h2&gt;Dask does more than Spark&lt;/h2&gt;
&lt;p&gt;The first questions you ask yourself are:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;“Can this tool actually solve my problem?”&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;“How painful will it be?”&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;For Spark, if your problem is SQL queries, DataFrames, or streaming analytics then the answer is “yes, this tool definitely solves my problem”. For Dask these all apply as well, and also more applications like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/ml.html"&gt;&lt;span class="doc"&gt;Training ML models&lt;/span&gt;&lt;/a&gt; with XGBoost or PyTorch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/hpo.html"&gt;&lt;span class="doc"&gt;HyperParameter optimization&lt;/span&gt;&lt;/a&gt; with Optuna&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/easy-scalable-production-etl.html"&gt;&lt;span class="doc"&gt;Workflow management&lt;/span&gt;&lt;/a&gt; with tools like Prefect or Dagster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/arxiv-matplotlib.html"&gt;&lt;span class="doc"&gt;Highly custom parallelism&lt;/span&gt;&lt;/a&gt; and ad-hoc workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/coiled-xarray.html"&gt;&lt;span class="doc"&gt;Wrangling large multi-dimensional data&lt;/span&gt;&lt;/a&gt;, such as in imaging or processing simulation output&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Internally, Spark is fundamentally a MapReduce engine. It applies the same function across many inputs uniformly, shuffles those results around, and reduces the results. This pattern is both simple and powerful, and lets Spark build up abstractions like DataFrames and SQL.&lt;/p&gt;
&lt;figure class="align-default" id="id4"&gt;
&lt;img alt="Diagram of Map-Shuffle-Reduce framework." src="https://docs.coiled.io/_images/map-reduce.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Map-Shuffle-Reduce: Apache Spark always operates on lots of data in bulk using the same operations.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;However, many applications (like those listed above) can’t be written down as a MapReduce style problem. These problems can not be efficiently solved with Spark, at least not without a lot of pain, and so people either build their own solution, or they build on tools like Dask.&lt;/p&gt;
&lt;p&gt;Instead of using MapReduce, Dask follows a task scheduling approach. This provides more flexibility, which has made Dask a powerful backend for a modern generation of tools, ranging from ML training systems like XGBoost, to workflow managers like Prefect and Dagster, all of which can back themselves with Dask clusters for scalable computing. Additionally, disciplines with a high degree of complexity, like finance and geoscience, prefer Dask because they can build arbitrarily complex computations on a fine-grained task-by-task basis.&lt;/p&gt;
&lt;figure class="align-default" id="id5"&gt;
&lt;img alt="Task graph of a credit model created with Dask." src="https://docs.coiled.io/_images/simple-model.svg" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Task graph of a credit model. Dask uses dynamic task scheduling, which can handle weirder situations (which come up a lot in Python workloads).&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In general, both Spark and Dask can probably do what you want (unless you’re doing something very strange). Speaking with our Dask bias, we’re pretty proud of the decisions and tradeoffs Dask has made and are excited to see Dask continue to make impressive performance gains.&lt;/p&gt;
&lt;p&gt;It’s also pretty easy to try out for yourself, either locally or on the cloud:&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-2" name="sd-tab-set-1" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-2"&gt;
Local&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Dask can set itself up easily in your Python session with a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;LocalCluster&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-3" name="sd-tab-set-1" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-3"&gt;
Cloud&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;It’s easy to sign up for &lt;a class="reference internal" href="user_guide/setup/index.html"&gt;&lt;span class="doc"&gt;Coiled&lt;/span&gt;&lt;/a&gt; to run Dask on the cloud.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Cluster&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Once you have a Dask cluster, you can run Python code on that cluster. Here are some examples:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/1trc.html"&gt;&lt;span class="doc"&gt;One trillion row challenge&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/scaling-xgboost-cloud-coiled.html"&gt;&lt;span class="doc"&gt;Train an XGBoost model in parallel&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/easy-scalable-production-etl.html"&gt;&lt;span class="doc"&gt;Run a scalable data pipeline&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/spark-vs-dask.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Apr 19, 2024Bar chart comparing the relative difference in TPC-H query runtime for Dask vs. PySpark when executed on a M1 MacBook Pro with 8 cores. Orange represents queries where Dask is faster and blue where PySpark is faster.</summary>
    <published>2024-04-19T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/easy-scalable-production-etl.html</id>
    <title>Easy Scalable Production ETL</title>
    <updated>2024-04-08T00:00:00+00:00</updated>
    <author>
      <name>James Bourbeau</name>
    </author>
    <content type="html">&lt;section id="easy-scalable-production-etl"&gt;

&lt;p&gt;&lt;em&gt;We show a lightweight scalable data pipeline that runs large Python jobs on a schedule on the cloud.&lt;/em&gt;&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Data&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Cost&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;575 million records / day&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;$10/day&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;It’s common to run regular large-scale Python jobs on the cloud as part of production data pipelines. Modern workflow orchestration systems like Prefect, Dagster, Airflow, Argo, etc. all work well for running jobs on a regular cadence, but we often see groups struggle with complexity around cloud infrastructure and lack of scalability.&lt;/p&gt;
&lt;p&gt;Here we show a scalable data pipeline that runs regular jobs on the cloud with &lt;a class="reference external" href="https://docs.coiled.io/user_guide/index.html"&gt;Coiled&lt;/a&gt; and Prefect. The pipeline consists of a few Python files in &lt;a class="reference external" href="https://github.com/coiled/etl-tpch"&gt;this GitHub repository&lt;/a&gt; which you can &lt;a class="reference external" href="https://github.com/coiled/etl-tpch?tab=readme-ov-file#how-to-run"&gt;run yourself&lt;/a&gt; either locally or on the cloud. This approach is:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy to deploy&lt;/strong&gt; on the cloud&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalable&lt;/strong&gt; across many cloud machines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cheap to run&lt;/strong&gt; on ephemeral VMs and a small always-on VM&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
Use Python&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Defining tasks and cloud hardware in Python makes it easy for Python data developers to write and deploy their processing code quickly.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;prefect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;64 GiB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;--- Define hardware in script&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;json_to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                        &lt;span class="c1"&gt;# &amp;lt;--- Use simple pandas functions&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OUTDIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@flow&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;convert_all_files&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list_files&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;json_to_parquet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                         &lt;span class="c1"&gt;# &amp;lt;--- Map across many files at once&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
Run jobs regularly&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Prefect runs Python jobs on a regular cadence. Coiled deploys Prefect on a cheap, always-on &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;t3.medium&lt;/span&gt;&lt;/code&gt; instance (~$1/day) on AWS.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;prefect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;

&lt;span class="n"&gt;preprocess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json_to_parquet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;preprocess&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;             &lt;span class="c1"&gt;# &amp;lt;--- Run job on a regular schedule&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reduce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_reduce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reduce&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;               &lt;span class="c1"&gt;# &amp;lt;--- Different jobs at different schedules&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-2" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-2"&gt;
Scale with Dask&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;p&gt;Coiled creates on-demand Dask clusters for processing larger-than-memory datasets.
This lets you scale out when you need to and only pay for hardware while you’re using it.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;prefect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;unshipped_orders_by_revenue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;# &amp;lt;--- Ask for hundreds of workers&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# &amp;lt;--- Run anywhere on any hardware&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="c1"&gt;# Complex query&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="data-pipelines"&gt;
&lt;h2&gt;Data pipelines&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;How do I process large-scale data continuously in production?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While details will vary across use cases, data pipelines typically look something like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Step 1: &lt;strong&gt;Acquire raw data&lt;/strong&gt; from the real world&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Example: A customer buys a loaf of bread at your grocery store&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: A satellite measurements sea temperature in Antarctica&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Step 2: &lt;strong&gt;Regularly process&lt;/strong&gt; that raw data&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Example: Apply data quality cuts followed by a feature engineering step&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: Convert to a different storage format&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extra requirement: Retry on failures, run close to data in the cloud, alert me if processing fails&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Step 3: &lt;strong&gt;Scalably analyze&lt;/strong&gt; that data periodically&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Example: Large-scale query of your data that addresses a business question&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: Train an ML model for weather forecasting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extra requirement: Data is too large to fit on a single machine&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Step 4: &lt;strong&gt;Publish results&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Example: Serve an interactive dashboard with a real-time view of customer orders&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: Host an ML model&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Running these steps on a regular cadence, task retries, and alerts are all handled well by modern workflow management systems like Prefect, Dagster, Airflow, Argo, etc. Managing cloud infrastructure and scalability is where most people tend to struggle.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="pain-points-of-common-data-pipelines"&gt;
&lt;h2&gt;Pain points of common data pipelines&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Pipelines can be overly complex or lack scalability.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Workflow management systems tend to struggle with:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complexity&lt;/strong&gt; around managing cloud infrastructure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Provisioning / deprovisioning cloud machines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Software environment management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handling cloud data access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost monitoring and spending limits&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale&lt;/strong&gt; limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Scaling existing Python code across many cloud machines in parallel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Computing on larger-than-memory datasets (e.g. 100 TB Parquet dataset in S3)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because of these issues, it’s common for systems to be overly complex or expensive.
Below we show an example that connects Prefect and Coiled to run a lightweight, scalable data pipeline on the cloud.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="easy-deployment-for-data-pipelines"&gt;
&lt;h2&gt;Easy deployment for data pipelines&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Coiled and Prefect make it easy to run regularly scheduled jobs on the cloud at scale.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Coiled is a lightweight tool that makes deploying and scaling data pipelines on the cloud straightforward. This &lt;a class="reference external" href="https://github.com/coiled/etl-tpch"&gt;example data pipeline&lt;/a&gt; combines Coiled and Prefect to run regularly scheduled jobs on the cloud. It looks like this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Step 1: &lt;strong&gt;Data generation&lt;/strong&gt; – New &lt;a class="reference external" href="https://www.tpc.org/tpch/"&gt;TPC-H&lt;/a&gt; JSON files with customer orders and supplier information appear in an S3 bucket (every 15 minutes)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Step 2: &lt;strong&gt;Regular processing&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;JSON gets transformed into Parquet / Delta (every 15 minutes)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data compaction of small Parquet files into larger ones for efficient downstream usage (every 6 hours)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Step 3: &lt;strong&gt;Scalable analysis&lt;/strong&gt; – Run large-scale multi-table analysis query to monitor unshipped, high-revenue orders (every 24 hours)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Step 4: &lt;strong&gt;Serve dashboard&lt;/strong&gt; – Results from latest business query are served on a dashboard (always on)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This pipeline processes ~575 million records daily and costs ~$10/day to run on the cloud. Let’s look at how Coiled and Prefect work together to this work.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Scalable data pipeline example that runs regularly scheduled jobs on the cloud." src="https://docs.coiled.io/_images/etl-diagram.png" style="width: 800px;" /&gt;
&lt;/figure&gt;
&lt;section id="define-tasks-and-hardware-in-python"&gt;
&lt;h3&gt;Define tasks and hardware in Python&lt;/h3&gt;
&lt;p&gt;Data-intensive &lt;a class="reference external" href="https://docs.prefect.io/concepts/tasks"&gt;Prefect tasks&lt;/a&gt; are common for per-file preprocessing steps, like those in &lt;a class="reference external" href="https://github.com/coiled/etl-tpch/blob/main/pipeline/preprocess.py"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pipeline/preprocess.py&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;. We combine them with &lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;Coiled Functions&lt;/a&gt; for remote execution on cloud VMs. Defining tasks and cloud hardware in Python makes it easy for Python data developers to write and deploy their code quickly.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# pipeline/preprocess.py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;prefect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# &amp;lt;-- Define hardware in script&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;64 GiB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;json_to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# &amp;lt;-- Use simple pandas functions&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OUTDIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@flow&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;convert_all_files&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list_files&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;json_to_parquet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# &amp;lt;-- Run across many files at once&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="run-jobs-regularly"&gt;
&lt;h3&gt;Run jobs regularly&lt;/h3&gt;
&lt;p&gt;Related tasks are connected in a &lt;a class="reference external" href="https://docs.prefect.io/latest/concepts/flows/"&gt;Prefect flow&lt;/a&gt; which defines their dependency to each other. This flow can then be scheduled to run regularly at different cadences, see for example in &lt;a class="reference external" href="https://github.com/coiled/etl-tpch/blob/main/workflow.py"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;workflow.py&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;.
We deploy this workflow on a cheap, always-on &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;t3.medium&lt;/span&gt;&lt;/code&gt; instance (~$1/day) using Coiled’s &lt;a class="reference external" href="https://docs.coiled.io/user_guide/prefect.html"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;prefect&lt;/span&gt; &lt;span class="pre"&gt;serve&lt;/span&gt;&lt;/code&gt; CLI&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# workflow.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;prefect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;

&lt;span class="n"&gt;preprocess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json_to_parquet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;preprocess&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;      &lt;span class="c1"&gt;# &amp;lt;--- Run job on a regular schedule&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reduce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_reduce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reduce&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;# &amp;lt;--- Different jobs at different schedules&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="scale-with-dask-clusters"&gt;
&lt;h3&gt;Scale with Dask clusters&lt;/h3&gt;
&lt;p&gt;The large-scale, multi-table analysis query in &lt;a class="reference external" href="https://github.com/coiled/etl-tpch/blob/main/pipeline/reduce.py"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pipeline/reduce.py&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; uses Coiled to create an on-demand &lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; cluster to handle processing our larger-than-memory data. Once the query is done all the VMs in the cluster are automatically shut down.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# pipeline/reduce.py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;prefect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;unshipped_orders_by_revenue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;# &amp;lt;--- Ask for hundreds of workers&lt;/span&gt;
        &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;# &amp;lt;--- Run anywhere on any hardware&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="c1"&gt;# Complex query&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Data pipelines with regular large-scale Python jobs are common, but can be difficult to deploy on the cloud or don’t scale well. We showed how to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use Python&lt;/strong&gt; for defining tasks and cloud hardware&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run jobs regularly&lt;/strong&gt; on the cloud using Prefect and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;prefect&lt;/span&gt; &lt;span class="pre"&gt;serve&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale with Dask&lt;/strong&gt; when processing large volumes of data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;in a &lt;a class="reference external" href="https://github.com/coiled/etl-tpch"&gt;scalable example data pipeline&lt;/a&gt; that processes ~575 million records daily and costs ~$10/day to run on the cloud.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;You can run this example data pipeline yourself, either locally or on the cloud.&lt;/p&gt;
&lt;p&gt;To run things locally, make sure you have a &lt;a class="reference external" href="https://www.prefect.io/cloud"&gt;Prefect cloud&lt;/a&gt; account and have authenticated your local machine. Then clone the pipeline GitHub repo, install dependencies, and run the pipeline:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Run data pipeline locally&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/coiled/etl-tpch
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;etl-tpch
mamba&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;environment.yml
mamba&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;etl-tpch
python&lt;span class="w"&gt; &lt;/span&gt;workflow.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To run the same pipeline on the cloud, follow &lt;a class="reference external" href="https://github.com/coiled/etl-tpch?tab=readme-ov-file#cloud"&gt;these instructions&lt;/a&gt;. Running the pipeline for several days fits comfortably within the Coiled free tier. We hope this example is easy to copy and modify for your needs.&lt;/p&gt;
&lt;p&gt;You might also be interested in these additional use cases:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/prefect-scheduled-jobs.html"&gt;&lt;span class="doc"&gt;Schedule Python Jobs with Prefect and Coiled&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/uber-lyft.html"&gt;&lt;span class="doc"&gt;Cloud Parquet ETL with Dask DataFrame&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/dagster-onlineapp.html"&gt;&lt;span class="doc"&gt;Dask, Dagster, and Coiled for Production Analysis at OnlineApp&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/easy-scalable-production-etl.html"/>
    <summary>We show a lightweight scalable data pipeline that runs large Python jobs on a schedule on the cloud.Scalable data pipeline example that runs regularly scheduled jobs on the cloud.</summary>
    <published>2024-04-08T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/1trc.html</id>
    <title>One Trillion Row Challenge</title>
    <updated>2024-02-05T00:00:00+00:00</updated>
    <author>
      <name>Sarah Johnson</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Feb 5, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="one-trillion-row-challenge"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/scharlottej13"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/8620816?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Sarah Johnson&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Last month Gunnar Morling launched the &lt;a class="reference external" href="https://www.morling.dev/blog/one-billion-row-challenge/"&gt;One Billion Row Challenge&lt;/a&gt; with the task of writing a Java program for retrieving temperature measurement values from a text file and calculating the min, mean, and max temperature per weather station. This took off greater than anyone would expect, gathering dozens of submissions from different tools.&lt;/p&gt;
&lt;p&gt;We joined in the fun and made our own unofficial &lt;a class="reference external" href="https://github.com/gunnarmorling/1brc/discussions/450"&gt;Python submission for Dask&lt;/a&gt;, which ran in 32.8 seconds on a MacBook. Fellow Dask maintainer Jacob Tomlinson took things a step further, and &lt;a class="reference external" href="https://github.com/gunnarmorling/1brc/discussions/487"&gt;ran the challenge on GPUs in 4.5 seconds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This got us thinking, what about a trillion row challenge? The 1BRC was fun, and it’d be interesting to compare performance across different sets of big data tools at a much larger scale.&lt;/p&gt;
&lt;section id="how-can-you-participate"&gt;
&lt;h2&gt;How can you participate?&lt;/h2&gt;
&lt;p&gt;Your task is to use any tool(s) you’d like to calculate the min, mean, and max temperature per weather station, sorted alphabetically. The data is stored in Parquet on S3: &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-datasets-rp/1trc&lt;/span&gt;&lt;/code&gt;. Each file is 10 million rows and there are 100,000 files. For an extra challenge, you could also &lt;a class="reference external" href="https://github.com/coiled/1trc/blob/c1a3a39f869263cc13020a535dfe22b8774ca40b/generate_data.py"&gt;generate the data yourself&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Open an issue in the &lt;a class="reference external" href="https://github.com/coiled/1trc"&gt;1TRC repo&lt;/a&gt; with your submission and enough details for someone else to be able to run your implementation. This includes things like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Hardware&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runtime&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reproducible code snippet&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is no prize and everyone is a winner. Really, the idea is to solicit ideas and generate discussion.&lt;/p&gt;
&lt;p&gt;The next sections dive into the dataset generation process in more detail and an example of running the challenge using Dask.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="data-generation"&gt;
&lt;h2&gt;Data Generation&lt;/h2&gt;
&lt;p&gt;Our goal is to make the dataset easy for you to generate yourself, but also easy to access if you don’t have many TBs of data storage available. Using the 1BRC dataset as a template, we scaled up to one trillion rows, and wrote the data to Parquet on S3 (thanks to Jacob Tomlinson for the &lt;a class="reference external" href="https://github.com/coiled/1trc/blob/c1a3a39f869263cc13020a535dfe22b8774ca40b/generate_data.py"&gt;data generation script&lt;/a&gt;).&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="https://docs.coiled.io/_images/1trc-data-generation.png" src="https://docs.coiled.io/_images/1trc-data-generation.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;You’ll be working with 100,000 Parquet files, each with 10 million rows. The dataset is accessible on S3: &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-datasets-rp/1trc&lt;/span&gt;&lt;/code&gt;.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Here’s what you’ll be working with:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;100,000 Parquet files.&lt;/strong&gt; Each files has 10 million rows and is 24 MiB on disk, for a total of 2.5 TiB.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stored on S3 &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-datasets-rp/1trc&lt;/span&gt;&lt;/code&gt;.&lt;/strong&gt; This is a requester-pays bucket in AWS region US East (N. Virginia). Those using AWS can avoid incurring charges by reading in the data from a cloud VM that’s also running in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;us-east-1&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="choosing-good-chunk-sizes"&gt;
&lt;h3&gt;Choosing good chunk sizes&lt;/h3&gt;
&lt;p&gt;The first decision was how to split up a trillion rows of data. There’s not really one right answer here. You need small enough chunks that many could fit on a single worker’s available memory at one time. If the chunks are too small, this could mean an unnecessarily large task graph. If the chunks are too big, then you would be limited to specific high-memory machine types or risk running out of memory. For Dask, this usually means starting with somewhere around 100–500 MiB memory usage per chunk (note, this is when loaded into memory, not the size on disk).&lt;/p&gt;
&lt;p&gt;If you read a single file into memory, it takes up ~228 MB, which is a good place to start.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets-rp/1trc/measurements-0.parquet&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_usage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-pycon notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;&amp;lt;class &amp;#39;dask.dataframe.core.DataFrame&amp;#39;&amp;gt;&lt;/span&gt;
&lt;span class="go"&gt;Columns: 2 entries, station to measure&lt;/span&gt;
&lt;span class="go"&gt;dtypes: float64(1), string(1)&lt;/span&gt;
&lt;span class="go"&gt;memory usage: 228.5 MB&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="csv-vs-parquet"&gt;
&lt;h3&gt;CSV vs. Parquet&lt;/h3&gt;
&lt;p&gt;We first tried to use text files, but found Dask to be prohibitively slow at this scale. We generated 100,000 CSVs, each 131.6 MiB on disk for a total of 12.5 TiB. Creating the task graph for 1,000 files took ~30 seconds. There is certainly more we could dig into here, but we decided using Parquet is more realistic for working with this scale of data.&lt;/p&gt;
&lt;p&gt;CSVs are everywhere and fine solution if you’re not working with much data. Parquet is more performant, though, especially if you have large datasets. This is in part because Parquet is &lt;a class="reference external" href="https://docs.dask.org/en/latest/dataframe-best-practices.html#use-parquet"&gt;more compact to store and faster to read&lt;/a&gt;. Since we’re processing a lot more data than the 1BRC, it makes sense to use Parquet.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="storing-data-on-s3"&gt;
&lt;h3&gt;Storing data on S3&lt;/h3&gt;
&lt;p&gt;The dataset is publicly available on AWS S3 in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-datasets-rp/1trc&lt;/span&gt;&lt;/code&gt; in AWS region US East (N. Virginia). The dataset is publicly available in a requester-pays bucket, which means you don’t have to worry about storing the data, but you are responsible for any data transfer or download costs (more details in the &lt;a class="reference external" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/RequesterPaysBuckets.html"&gt;AWS documentation&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;If you’re using AWS, you can avoid data transfer costs by running your computation in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;us-east-1&lt;/span&gt;&lt;/code&gt;, the same region as the data.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="trc-with-dask"&gt;
&lt;h2&gt;1TRC with Dask&lt;/h2&gt;
&lt;p&gt;Here’s an example of how we ran this challenge using &lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; and &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;. It took 8.5 minutes and cost $3.26 in AWS cloud costs (10 minutes if you include cluster start time).&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_expr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;# Start Coiled cluster&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# 100 m6i.xlarge instances&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;us-east-1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# Faster data access and avoid data transfer costs&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets-rp/1trc/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dtype_backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;requester_pays&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;station&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;min&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;max&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;station&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Similar to the &lt;a class="reference internal" href="blog/1brc.html"&gt;&lt;span class="std std-doc"&gt;1BRC submission&lt;/span&gt;&lt;/a&gt;, most of the time is spent in I/O, in this case on &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt;. Looking at the task stream it seems the cluster resources are well utilized, since there’s little white space.&lt;/p&gt;
&lt;figure class="align-default" id="id2"&gt;
&lt;img alt="Screencast of the Dask dashboard running the 1TRC challenge. read_parquet shown in yellow, groupby-aggregation tasks shown in green." src="https://docs.coiled.io/_images/1trc.gif" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Dask dashboard during the 1TRC, most of the time is spent on &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt; tasks (yellow).&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;section id="optimizing-costs"&gt;
&lt;h3&gt;Optimizing costs&lt;/h3&gt;
&lt;p&gt;Processing 2.5 TB cost us $3.26. This is cheap. We can bring the cost down ~3x to $1.10 by:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Using &lt;a class="reference external" href="https://www.coiled.io/blog/save-money-with-spot"&gt;Spot instances&lt;/a&gt;, which are excess capacity offered at a discount.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using &lt;a class="reference external" href="https://blog.coiled.io/blog/dask-graviton.html"&gt;ARM-based instances&lt;/a&gt;, which are less expensive than Intel-based equivalents with the similar, or better, performance.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;            &lt;span class="c1"&gt;# Start Coiled cluster&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;# 100 m7g.xlarge instances&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;us-east-1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;# Faster data access and avoid data transfer costs&lt;/span&gt;
    &lt;span class="n"&gt;arm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                        &lt;span class="c1"&gt;# Use ARM-based instances&lt;/span&gt;
    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt; &lt;span class="c1"&gt;# Use spot, when available&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In addition to being less expensive, using more performant ARM-based instances lowers computation runtime to 5.8 minutes (8 minutes if you include cluster start time).&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Looking forward to seeing all your challenge submissions in the &lt;a class="reference external" href="https://github.com/coiled/1trc"&gt;1TRC repo&lt;/a&gt;. Thanks to Jacob Tomlinson for helping kick things off (looking forward to seeing a GPU implementation soon).&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/1trc.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Feb 5, 2024</summary>
    <published>2024-02-05T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/grocery-demand-forecasting.html</id>
    <title>Real-world Grocery Demand Forecasting</title>
    <updated>2024-01-31T00:00:00+00:00</updated>
    <author>
      <name>Jack Solomon</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jan 31, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;5m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="real-world-grocery-demand-forecasting"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://www.tryguac.co/"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://assets-global.website-files.com/6536b13ecd0da0b26d85f798/653e90c2deea6f44f7457f9b_jack-removebg-preview%201.webp" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Jack Solomon&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Every day, grocers are tasked with predicting the future: they have to predict how many items will sell in the future to order the correct amount of inventory today.&lt;/p&gt;
&lt;p&gt;Since grocery retailers sell fresh food, which expires within a few days of being on the shelf, grocers need to be hyper-accurate in predicting item-level demand for each day.&lt;/p&gt;
&lt;p&gt;If grocers over-predict, they have tons of unsold food that gets thrown out; today, if food waste was a country it would be the third largest emitter behind the US and China. On the other hand, if grocers under-predict, they’re left with empty shelves, which leads to immediate revenue loss but also long-term a drop in customer loyalty.&lt;/p&gt;
&lt;section id="parallel-time-series-forecasting-in-python"&gt;
&lt;h2&gt;Parallel Time Series Forecasting in Python&lt;/h2&gt;
&lt;p&gt;Fortunately, we can use machine learning algorithms with large, real-world datasets to make SKU-level predictions with greater accuracy and efficiency. In this article, we’ll discuss how we’re using models like XGBoost with Dask and Coiled for time series forecasting with large datasets in Python.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;&lt;a class="reference external" href="https://xgboost.readthedocs.io/en/stable/get_started.html"&gt;XGBoost&lt;/a&gt; (Extreme Gradient Boosting) is a powerful machine learning algorithm that uses an ensemble learning method combining multiple decision trees to make predictions. &lt;a class="reference external" href="https://docs.dask.org/en/stable/"&gt;Dask&lt;/a&gt; is a general purpose library for parallel computing. Dask is tightly &lt;a class="reference external" href="https://xgboost.readthedocs.io/en/stable/tutorials/dask.html"&gt;integrated with XGBoost&lt;/a&gt;, which makes it easy to train models at scale and run predictions on large datasets. With &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;, we can easily run Dask in the cloud.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="challenges-of-traditional-forecasting-methods"&gt;
&lt;h2&gt;Challenges of Traditional Forecasting Methods&lt;/h2&gt;
&lt;p&gt;The standard solution to demand forecasting is to use historical sales data to predict future demand. These traditional forecasting methods based exclusively on historical sales data have several limitations.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Line graph of forecasted sales and actual sales over time with shortages marked in red rectangles." src="https://docs.coiled.io/_images/inaccurate-forecast.png" style="width: 800px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Inaccurate forecasts lead to both excess inventory and shortages. Image from &lt;a class="reference external" href="https://nicolas-vandeput.medium.com/forecasting-demand-despite-shortages-fee899120c08"&gt;Nicolas Vandeput’s blog post&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;section id="inability-to-adapt-to-real-world-events"&gt;
&lt;h3&gt;Inability to Adapt to Real World Events&lt;/h3&gt;
&lt;p&gt;Standard time series forecasting relies on using historical data to make predictions. But history doesn’t always repeat itself.&lt;/p&gt;
&lt;p&gt;On one hand, trends change over time as macro factors like economic conditions change. On the other hand, specific events spring up that forecasting models have no knowledge of - things like major sports games, one-off concerts or conferences, or even just school term dates.&lt;/p&gt;
&lt;p&gt;So, to forecast accurately, models need knowledge of what’s happening in the real world.&lt;/p&gt;
&lt;p&gt;Is it going to be unseasonably warm tomorrow? Did school start back up and kids need snacks? Did a promotion on substitute products just end? Are the 49ers playing down the road from the local store? There are hundreds of real world factors behind a shopper’s every single purchase.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="time-consuming-and-prone-to-human-error"&gt;
&lt;h3&gt;Time-Consuming and Prone to Human Error&lt;/h3&gt;
&lt;p&gt;Forecasting models that are unable to account for real-world events (like holidays or promotions) require manual, ad-hoc adjustments. These adjustments not only require a lot of effort, but are also prone to human mistakes.&lt;/p&gt;
&lt;p&gt;That’s especially true given how much interplay there is between demand drivers. Maybe this year’s 4th of July celebrations will be small because of the bad weather and local road closures.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="limited-by-data-size-and-complexity"&gt;
&lt;h3&gt;Limited by Data Size and Complexity&lt;/h3&gt;
&lt;p&gt;Traditional forecasting methods like regression models are not designed to handle large and complex datasets and multivariate problems. As a result, they may not be able to capture all the relevant factors that influence demand, leading to less accurate predictions.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="real-world-example-grocery-demand-forecasting"&gt;
&lt;h2&gt;Real-World Example: Grocery Demand Forecasting&lt;/h2&gt;
&lt;p&gt;The problem of bad forecasting is especially acute in grocery. The spikiness that we see in the charts above has a large effect.  It’s the difference between having empty shelves in the guacamole section on the SuperBowl, or stocking and wasting too much guacamole during the rest of the year.&lt;/p&gt;
&lt;p&gt;Fortunately, the world is full of data, and we can use that data to get really well-informed models:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Sporting events&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Good: calendar of events, joined with proximity to local stadiums&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better: sports betting odds and activities on each game so we know when the stadium will be empty or full&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concerts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Good: Find out when concerts are in town&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better: And cross-reference Spotify data to see how popular the artists are to predict attendance&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ethnic holidays&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Good: a calendar of which days are public holidays&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better: a calendar of holidays for every ethnic group, combined with demographic information about the surrounding neighborhood&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At Guac we specialize in customizing algorithms with hyperlocal data for our different grocery customers.&lt;/p&gt;
&lt;p&gt;Take the following example of a real 2023 sales graph from one of our customers. Running a standard historics-based approach to forecasting, the model totally misses the increase in demand driven by a big 49ers vs. Steelers game on the weekend of 10 September.&lt;/p&gt;
&lt;figure class="align-default" id="id2"&gt;
&lt;img alt="Line graph of forecasted sales and actual sales over time." src="https://docs.coiled.io/_images/forecast-less-data.png" style="width: 800px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Actual (blue) and predicted (red) grocery sales before including data on sporting events.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;But once we enriched the data with information about the game and its expected popularity, we can almost perfectly capture the true sales on the day.&lt;/p&gt;
&lt;figure class="align-default" id="id3"&gt;
&lt;img alt="Line graph of forecasted sales and actual sales after enriching the dataset with hyperlocal data." src="https://docs.coiled.io/_images/forecast-more-data.png" style="width: 800px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Actual (blue) and predicted (green) grocery sales after augmenting the dataset with hyperlocal data. Accounting for the popularity of the 49ers vs. Steelers game provided much more accurate predictions.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="distributed-xgboost-for-large-tabular-datasets"&gt;
&lt;h2&gt;Distributed XGBoost for Large Tabular Datasets&lt;/h2&gt;
&lt;p&gt;Unfortunately, adding all of those extra features greatly increases the size of our data. For one of our customers, this meant a 15x increase in the size of the dataset from 5 GB to 75 GB.&lt;/p&gt;
&lt;p&gt;Setting up this infrastructure was a bit daunting.  We considered using Spark and Databricks, but that would have required rewriting a huge amount of our pandas-heavy codebase. We instead latched onto &lt;a class="reference external" href="https://docs.dask.org/en/stable/"&gt;Dask&lt;/a&gt; and &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Dask was great because …&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;It was almost a drop-in replacement for our existing pandas code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It had great integration with a wide range of ML libraries, such as XGBoost&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Coiled was great because …&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;It handled all the messy dev-ops work for us&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setup was super fast and we had our Dask clusters up and running in under a day&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Support has been really helpful, working with us on migrating our codebase and infrastructure&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If this kind of rich real-world demand forecasting is something that you could use, then talk to us at &lt;a class="reference external" href="https://www.tryguac.co/"&gt;Guac&lt;/a&gt;. We invite you to &lt;a class="reference external" href="https://www.tryguac.co/contact-us"&gt;try it out&lt;/a&gt; with a trial on your past sales data, showcasing its ability to cut food waste.&lt;/p&gt;
&lt;p&gt;If you’re looking for super-easy distributed XGBoost, look no further than &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;. It’s easy to get started and free for modest use.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/grocery-demand-forecasting.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jan 31, 2024Line graph of forecasted sales and actual sales over time.</summary>
    <published>2024-01-31T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/prefect-scheduled-jobs.html</id>
    <title>Schedule Python Jobs with Prefect and Coiled</title>
    <updated>2024-01-23T00:00:00+00:00</updated>
    <author>
      <name>James Bourbeau</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jan 23, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="schedule-python-jobs-with-prefect-and-coiled"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://jamesbourbeau.com"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/11656932?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;James Bourbeau&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;We show how to deploy Prefect workflows on the cloud with Coiled.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.prefect.io/"&gt;Prefect&lt;/a&gt; is a popular workflow management system. Its intuitive API makes writing workflows straightforward, especially for Python developers,
who can usually start running on their laptop in just a few minutes.&lt;/p&gt;
&lt;p&gt;It’s common to want to run a workflow, or specific task within a workflow, on a VM in the cloud instead of locally for different reasons:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;You want an always-on machine for long-running, or regularly scheduled, jobs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want to run close to your cloud-hosted data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want specific hardware (e.g. GPU, lots of memory)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want many machines for running tasks in parallel&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post shows how to deploy a Prefect workflow on the cloud using &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;. The workflow runs a regularly scheduled job on a dataset that’s updated daily. While the real-world example we show in this post highlights a geospatial use case, the overall pattern of running regularly scheduled jobs is common in many fields (e.g. machine learning, finance, remote sensing, sports betting, etc.) and looks something like the following:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;prefect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;


&lt;span class="c1"&gt;# Step 1: Find new data files to be processed.&lt;/span&gt;
&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_new_data_files&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;


&lt;span class="c1"&gt;# Step 2: Process input data file.&lt;/span&gt;
&lt;span class="c1"&gt;#         This often requires specific computing resources.&lt;/span&gt;
&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;


&lt;span class="c1"&gt;# Step 3: Combine tasks into a full workflow that&lt;/span&gt;
&lt;span class="c1"&gt;#         processes all new data files.&lt;/span&gt;
&lt;span class="nd"&gt;@flow&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_new_data_files&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Saved file &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 4: Run the workflow on some regular cadence. &lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;section id="workflow"&gt;
&lt;h2&gt;Workflow&lt;/h2&gt;
&lt;p&gt;Each day NASA satellites collect sea surface temperature measurements that are then processed and stored in a new file added to the &lt;a class="reference external" href="https://podaac.jpl.nasa.gov/dataset/MUR-JPL-L4-GLOB-v4.1"&gt;Global Sea Surface Temperature dataset&lt;/a&gt; on AWS S3. Our workflow consists of a daily job that finds the newly added file, does additional processing on that data, and saves the processed result to an S3 bucket.&lt;/p&gt;
&lt;p&gt;Following the pattern above we have:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# workflow.py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tempfile&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;earthaccess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;s3fs&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xarray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xr&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;prefect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="n"&gt;IN_BUCKET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;s3://podaac-ops-cumulus-protected/MUR-JPL-L4-GLOB-v4.1&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;OUT_BUCKET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;s3://openscapes-scratch/MUR-SST&amp;quot;&lt;/span&gt;


&lt;span class="c1"&gt;# Step 1: Find new data files to be processed.&lt;/span&gt;
&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_new_data_files&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# Get all files from data source bucket and output destination bucket&lt;/span&gt;
    &lt;span class="n"&gt;fs_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;earthaccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_s3fs_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;POCLOUD&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;infiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fs_in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IN_BUCKET&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/*.nc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;fs_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S3FileSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;outfiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fs_out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OUT_BUCKET&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/*.nc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="c1"&gt;# Find new files that haven&amp;#39;t been processed yet&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;infiles&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;outfiles&lt;/span&gt;


&lt;span class="c1"&gt;# Step 2: Process input data file.&lt;/span&gt;
&lt;span class="c1"&gt;#         We run this task on a VM in the same region as the data&lt;/span&gt;
&lt;span class="c1"&gt;#         that&amp;#39;s large enough to reliably process files.&lt;/span&gt;
&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# Same region as data&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.large&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# Big enough VM to process data&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Open file with `xarray`&lt;/span&gt;
    &lt;span class="n"&gt;fs_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;earthaccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_s3fs_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;POCLOUD&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fs_in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IN_BUCKET&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# Select specific region of interest + data validation&lt;/span&gt;
    &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;93&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;cond&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sea_ice_fraction&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isnan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sea_ice_fraction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analysed_sst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Save processed result to S3&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;local_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_netcdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fs_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S3FileSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;outfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OUT_BUCKET&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;fs_out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outfile&lt;/span&gt;


&lt;span class="c1"&gt;# Step 3: Combine tasks into a full workflow that&lt;/span&gt;
&lt;span class="c1"&gt;#         processes all new data files.&lt;/span&gt;
&lt;span class="nd"&gt;@flow&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_new_data_files&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Saved file &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 4: Run the workflow once a day.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NASA-SST&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;There are a couple of key points to highlight:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p&gt;We lightly annotate our &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function with the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator&lt;/a&gt;
to have it run on a separate cloud VM with enough resources to process our data files. We also make sure the functions runs in
the same region where our data is to avoid data transfer costs:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# Same region as data&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.large&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# Big enough VM to process data&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;Coiled Functions&lt;/a&gt; to learn more about
using serverless functions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since our dataset is updated once a day, we set the workflow’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;interval=&lt;/span&gt;&lt;/code&gt; to automatically run daily.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NASA-SST&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section id="deploy-on-the-cloud"&gt;
&lt;h2&gt;Deploy on the cloud&lt;/h2&gt;
&lt;p&gt;With our workflow ready to go, we now deploy it to an always-on cloud VM using the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;prefect&lt;/span&gt; &lt;span class="pre"&gt;serve&lt;/span&gt;&lt;/code&gt; CLI:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;prefect&lt;span class="w"&gt; &lt;/span&gt;serve&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="c1"&gt;# Run on the cloud&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--region&lt;span class="w"&gt; &lt;/span&gt;us-west-2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;# Same region as data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--vm-type&lt;span class="w"&gt; &lt;/span&gt;t3.medium&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;# Cheap always-on VM for serving the workflow&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# AWS credentials to write to output bucket&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;EARTHDATA_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;# Earthdata credentials to read NASA data bucket&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;EARTHDATA_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;workflow.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Because our resource-heavy &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function is dispatched to a separate VM using Coiled Functions, the rest of the workflow doesn’t need significant computing resources and can be run on a small, inexpensive &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;t3.medium&lt;/span&gt;&lt;/code&gt; instance (costs ~$0.04/hr or less than a dollar a day). We also include two sets of secrets, one for reading data from the NASA bucket and one for writing to our processed data bucket, using the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;-e&lt;/span&gt;&lt;/code&gt; option for securely setting environment variables secrets.&lt;/p&gt;
&lt;p&gt;Now once a day an &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.large&lt;/span&gt;&lt;/code&gt; instance is spun up to run our &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function on a new data file and then automatically spun down.
We can see our flow running in both Coiled and Prefect:&lt;/p&gt;
&lt;figure class="sphinx-subfigure align-default" id="prefect-flow-coiled-dashboard"&gt;
&lt;div class="sphinx-subfigure-grid ss-layout-default-AB outline" style="display: grid; gap: 3px; grid-gap: 3px;"&gt;
&lt;div class="sphinx-subfigure-area" style="display: flex; flex-direction: column; justify-content: center; align-items: center; grid-area: A;"&gt;
&lt;img alt="Workflow running in Coiled UI." src="https://docs.coiled.io/_images/prefect-flow-coiled-dashboard.png" style="width: 100%;" /&gt;
&lt;span class="caption"&gt;Workflow running in Coiled UI.&lt;/span&gt;
&lt;/div&gt;
&lt;div class="sphinx-subfigure-area" style="display: flex; flex-direction: column; justify-content: center; align-items: center; grid-area: B;"&gt;
&lt;img alt="Workflow running in Prefect UI." src="https://docs.coiled.io/_images/prefect-flow-prefect-ui.png" style="width: 100%;" /&gt;
&lt;span class="caption"&gt;Workflow running in Prefect UI.&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;We ran a common example of a regularly scheduled job on the cloud using Prefect and Coiled.
This was:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy&lt;/strong&gt;: Minimal code changes were needed to run Prefect code on the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cheap&lt;/strong&gt;: Costs ~$1/day to run.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We hope this post shows that with just a handful of lines of code you can run your Prefect
workflows on the cloud.
We also hope it provides a template you can copy and adapt for your own use case.&lt;/p&gt;
&lt;p&gt;Want to run this example yourself?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Get started with Coiled for free at &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;coiled.io/start&lt;/a&gt;. This example runs comfortably within the free tier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the code snippet above.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/prefect-scheduled-jobs.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jan 23, 2024</summary>
    <published>2024-01-23T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/1brc.html</id>
    <title>One Billion Row Challenge (1BRC) in Python with Dask</title>
    <updated>2024-01-16T00:00:00+00:00</updated>
    <author>
      <name>Sarah Johnson</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jan 16, 2024&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="one-billion-row-challenge-1brc-in-python-with-dask"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/scharlottej13"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/8620816?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Sarah Johnson&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Last week Gunnar Morling launched the &lt;a class="reference external" href="https://www.morling.dev/blog/one-billion-row-challenge/"&gt;One Billion Row Challenge&lt;/a&gt; and it’s been fun to follow along. Though the official challenge is limited to Java implementations, we were inspired by an unofficial &lt;a class="reference external" href="https://github.com/gunnarmorling/1brc/discussions/62"&gt;Python&lt;/a&gt; submission and have our own unofficial submission for Dask.&lt;/p&gt;
&lt;section id="dask-results"&gt;
&lt;h2&gt;Dask Results&lt;/h2&gt;
&lt;p&gt;We ran the challenge on an M1 Mac (8-core CPU, 16 GB memory). Dask came in at 32.8 seconds (± 2.43 seconds, averaged across 7 trials). Here’s our implementation:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;measurements.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;station&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;measure&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pyarrow&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dtype_backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pyarrow&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;station&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;min&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;max&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;droplevel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;station&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask is a general purpose Python library for parallel and distributed computing. It’s especially nice for handling large datasets that don’t fit into memory on your laptop. For &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe.html"&gt;tabular data&lt;/a&gt;, the syntax is designed to feel very similar to pandas, so it’s nearly a drop-in replacement.&lt;/p&gt;
&lt;p&gt;Where Dask does well, though, is when handling hundreds of GB of data distributed across many machines. We were actually pleasantly surprised to see this level of performance on a single machine for only a 13 GB dataset. This is largely due to a number of recent improvements in Dask like Arrow strings, new shuffling algorithms, and query optimization (see &lt;a class="reference internal" href="blog/dask-dataframe-is-fast.html"&gt;&lt;span class="doc"&gt;Dask DataFrame is Fast Now&lt;/span&gt;&lt;/a&gt; for more details on these engineering efforts).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-does-dask-compare"&gt;
&lt;h2&gt;How does Dask compare?&lt;/h2&gt;
&lt;p&gt;Amongst the other 1BRC Python submissions, Dask is pretty squarely in the middle. It’s faster than using Python’s multiprocessing (except for the PyPy3 implementation) and slower than DuckDB and Polars. This is not too surprising given Polars and DuckDB tend to be faster than Dask on a smaller scale, especially on a single machine.&lt;/p&gt;
&lt;figure class="align-default" id="brc-chart"&gt;
&lt;img alt="Bar chart comparing 1BRC runtime for various Python implementations." src="https://docs.coiled.io/_images/1brc-chart.svg" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;1BRC challenge performance for Python implementations of Polars, DuckDB, Dask, and Spark. Polars and DuckDB values are copied from &lt;a class="reference external" href="https://github.com/ifnesi/1brc?tab=readme-ov-file#performance-on-a-macbook-pro-m1-32gb"&gt;this repo&lt;/a&gt;.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We also ran the challenge for PySpark, which took ~2 minutes. We are by no means experts in this area though, and would welcome ideas for improvement. Here’s our implementation:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pyspark.sql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pyspark.sql.types&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StructType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FloatType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringType&lt;/span&gt;

&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;local[*]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getOrCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StructType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;station&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringType&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;measure&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FloatType&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;repos/1brc/measurements.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;createOrReplaceTempView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;measurements&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;SELECT station, MAX(measure) as max, MIN(measure) as min, AVG(measure) as mean&lt;/span&gt;
&lt;span class="s2"&gt;FROM measurements&lt;/span&gt;
&lt;span class="s2"&gt;GROUP BY station ORDER BY station;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="tpc-h-benchmarks-at-scale"&gt;
&lt;h2&gt;TPC-H Benchmarks at Scale&lt;/h2&gt;
&lt;p&gt;Digging a little deeper into Dask performance, we see most computation time is spent on I/O. Yes, this challenge is for fun! But a comparison predominantly based on which tool is faster at reading a CSV into memory does have its limitations. We thought we’d mention some other more meaningful performance comparisons.&lt;/p&gt;
&lt;figure class="align-default" id="dask-1brc"&gt;
&lt;img alt="Screencast of the Dask dashboard running the 1BRC challenge. read_csv shown in purple, groupby-aggregation tasks shown in green." src="https://docs.coiled.io/_images/1brc.gif" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Dask dashboard during the 1BRC, most of the time is spent on &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt; tasks (purple).&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We’ve run a more robust series of &lt;a class="reference internal" href="blog/tpch.html"&gt;&lt;span class="doc"&gt;TPC-H Benchmarks&lt;/span&gt;&lt;/a&gt; comparing Dask, Polars, Spark, and DuckDB ranging in scale from 10GB to 10TB. We ran these benchmarks on the cloud using &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;, which makes it easy to run your Python code in the cloud. Coiled creates ephemeral VMs in cloud account that match your local environment and then cleans everything up when you’re done.&lt;/p&gt;
&lt;p&gt;No project wins, and it’s interesting analyzing the results.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="run-the-challenge-yourself"&gt;
&lt;h2&gt;Run the challenge yourself&lt;/h2&gt;
&lt;p&gt;First, you’ll need to generate the dataset. For those who are also less familiar with Java, the &lt;a class="reference external" href="https://rmoff.net/2024/01/03/1%EF%B8%8F%E2%83%A3%EF%B8%8F-1brc-in-sql-with-duckdb/#setup"&gt;setup instructions&lt;/a&gt; in Robin Moffat’s post are quite helpful.&lt;/p&gt;
&lt;p&gt;Next, you’ll need a Python environment with latest version of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask&lt;/span&gt;&lt;/code&gt; installed:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dask[complete]&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then you can run the challenge for Dask:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;measurements.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;station&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;measure&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pyarrow&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dtype_backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pyarrow&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;station&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;min&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;max&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;droplevel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;station&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You might also consider running the &lt;a class="reference internal" href="blog/1trc.html"&gt;&lt;span class="doc"&gt;one trillion row challenge&lt;/span&gt;&lt;/a&gt; which scales up this dataset to one trillion rows, or ~ 2.5 TB.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="acknowledgments"&gt;
&lt;h2&gt;Acknowledgments&lt;/h2&gt;
&lt;p&gt;Thanks to both of the earlier Python submissions from &lt;a class="reference external" href="https://github.com/Butch78/1BillionRowChallenge"&gt;Matthew Aylward&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/ifnesi/1brc/tree/main"&gt;Italo Nesi&lt;/a&gt;. And, of course, to Gunnar Morling for holding the challenge in the first place.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/1brc.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jan 16, 2024</summary>
    <published>2024-01-16T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/xarray-at-scale.html</id>
    <title>Xarray at Large Scale: A Beginner’s Guide</title>
    <updated>2023-12-21T00:00:00+00:00</updated>
    <author>
      <name>Coiled Team</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Dec 21, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;9m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="xarray-at-large-scale-a-beginners-guide"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://www.coiled.io/"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://cloud.coiled.io/static/favicon-48.png" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Coiled Team&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="parallel-xarray-can-be-easy"&gt;
&lt;h2&gt;Parallel Xarray Can be Easy&lt;/h2&gt;
&lt;p&gt;You don’t need to be a distributed systems expert to process terabytes of geospatial data. The tools today are good enough to make this accessible to everyone, but most people still stay on a single computer, even though it’s often more expensive.&lt;/p&gt;
&lt;p&gt;In fact the video below condenses 250TiB of data, and cost only $25 to produce over 20 minutes, while it would have taken thousands of dollars and weeks of download time on a single machine. Let’s walk through the common toolset used today by Python researchers to process large volumes of geospatial raster data.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/rDvTqop5gi4?si=284cfltBRwVj3zuS" data-hj-allow-iframe="true" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;&lt;/section&gt;
&lt;section id="what-is-xarray"&gt;
&lt;h2&gt;What is Xarray?&lt;/h2&gt;
&lt;p&gt;Xarray is an open-source project and Python library that makes working with labeled multi-dimensional arrays simple, efficient, and fun. It’s well suited to work with raster geospatial data which are often used in climate and meteorological research, as well as other forms of environmental, oceanographic, and atmospheric data. Xarray brings the power of pandas to n-dimensional arrays, which makes it a great tool for handling complex datasets.&lt;/p&gt;
&lt;p&gt;Key features of Xarray include:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;N-Dimensional Data and Labels&lt;/strong&gt;: Xarray manipulates multi-dimensional arrays like NumPy, but with dimension labels, making data manipulation and analysis more intuitive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pandas-like Operations:&lt;/strong&gt; Xarray supports many of the same data manipulation operations as pandas, such as group-by, merge, and reshaping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Readers for Common Formats&lt;/strong&gt; like NetCDF, GeoTIFF, Zarr, HDF, and others.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integration with Other Libraries&lt;/strong&gt;: Xarray integrates well with other data analysis libraries like pandas, NumPy, and Matplotlib, making it a versatile tool for data science projects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parallel Computing:&lt;/strong&gt; Xarray can leverage Dask to support parallel computing, allowing efficient analysis of very large datasets.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a practical example, let’s go through reading a netCDF file and performing some simple analysis using Xarray:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xarray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xr&lt;/span&gt;
&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;example_data.nc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mean_temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;temperature&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mean_temperature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;See the &lt;a class="reference external" href="https://docs.xarray.dev/en/stable/getting-started-guide/quick-overview.html"&gt;Xarray documentation&lt;/a&gt; for more introductory-level examples.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-is-dask"&gt;
&lt;h2&gt;What is Dask?&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; works with Xarray by providing a backend for parallel and out-of-core computation. This integration allows Xarray to handle datasets that are larger than the available memory, which is essential for processing the large, multi-dimensional arrays commonly used in scientific fields like climate science, oceanography, and geospatial analysis.&lt;/p&gt;
&lt;section id="integration-mechanism"&gt;
&lt;h3&gt;Integration Mechanism:&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chunking:&lt;/strong&gt; When an Xarray dataset is loaded, it can be chunked into smaller parts using Dask. These chunks are essentially small, manageable blocks of the larger dataset.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lazy Evaluation:&lt;/strong&gt; Xarray operations on Dask-backed arrays become lazy. This means that computations are not executed immediately but are instead queued up as tasks in a Dask graph.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Task Graph:&lt;/strong&gt; Each operation on the dataset creates a task in the Dask graph. This graph represents the sequence of operations needed to obtain the final result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parallel Computation:&lt;/strong&gt; When a result is requested (e.g., for plotting, saving, or explicitly computing), Dask executes the task graph. The computations are carried out in parallel, with each chunk being processed independently. This parallel execution is key to handling large datasets efficiently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory Efficiency:&lt;/strong&gt; By working on small chunks, Dask ensures that the memory usage is kept in check, avoiding out-of-memory errors that would occur if the entire dataset were loaded into memory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="practical-usage"&gt;
&lt;h3&gt;Practical Usage:&lt;/h3&gt;
&lt;p&gt;In practical terms, this means that you can work with Xarray in a way that feels familiar (similar to using in-memory NumPy arrays) but on much larger datasets. The Dask integration is transparent, so you don’t need to manage the parallelism or the chunking directly; Xarray and Dask handle these aspects behind the scenes. This makes it possible to write code that scales from small, in-memory datasets on a single machine to large datasets that are distributed across a cluster, without changing your code.&lt;/p&gt;
&lt;p&gt;Overall, the synergy between Xarray and Dask empowers users to efficiently process and analyze large datasets that would otherwise be impractical or impossible to handle on standard hardware, making it a powerful combination for data-intensive scientific applications.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="example-how-to-load-many-netcdf-files-with-xarray-and-dask"&gt;
&lt;h2&gt;Example: How to load many NetCDF files with Xarray and Dask&lt;/h2&gt;
&lt;p&gt;To use Dask with Xarray, especially in a scenario where you want to open multiple netCDF files as a single dataset, you would use the &lt;a class="reference external" href="https://docs.xarray.dev/en/stable/generated/xarray.open_mfdataset.html"&gt;xarray.open_mfdataset&lt;/a&gt; function with a chunks argument. This chunks argument specifies how the dataset should be divided into Dask chunks.&lt;/p&gt;
&lt;p&gt;Here’s an example of how you might use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;xarray.open_mfdataset&lt;/span&gt;&lt;/code&gt; with Dask:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xarray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xr&lt;/span&gt;

&lt;span class="c1"&gt;# Specify the path to your netCDF files (can use a wildcard for multiple files)&lt;/span&gt;
&lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;path/to/your/netcdf/files/*.nc&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# Open the dataset with Dask chunking&lt;/span&gt;
&lt;span class="c1"&gt;# The &amp;#39;chunks&amp;#39; argument specifies the chunk size for each dimension&lt;/span&gt;
&lt;span class="c1"&gt;# For example, {&amp;#39;time&amp;#39;: 10} would chunk the data along the time dimension in blocks of 10&lt;/span&gt;
&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_mfdataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# At this point, computations on &amp;#39;ds&amp;#39; will use Dask for parallel, out-of-core computation&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This approach enables handling larger-than-memory datasets on your machine or scaling up to a cluster without changing your code significantly.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="deploy-dask-and-xarray-onto-many-machines"&gt;
&lt;h2&gt;Deploy Dask and Xarray onto many machines&lt;/h2&gt;
&lt;p&gt;You can use Xarray with Dask to process large volumes of data on a single machine.  This lets you process as much data as you can download and fit onto your hard drive. However, this has two limitations:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;You can only fit so much data on your hard drive (around 100 GiB probably)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downloading data can be slow and expensive&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Often it is better instead to run your computations close to where your data lives.  Often large datasets are stored either on HPC Network File Systems (NFS) or on cloud object stores, like AWS’s S3 or Google’s GCS.  It’s best to run your computations on data where the data is stored for reasons of scale, cost, and speed.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="deploy-xarray-and-dask-on-an-hpc-cluster"&gt;
&lt;h2&gt;Deploy Xarray and Dask on an HPC Cluster&lt;/h2&gt;
&lt;p&gt;It is easy to run Xarray and Dask on HPC machines using common job schedulers like SLURM, PBS, LSF, SGE, or others.  Typically we recommend using the library &lt;a class="reference external" href="https://jobqueue.dask.org/en/latest/"&gt;dask-jobqueue&lt;/a&gt; for this.&lt;/p&gt;
&lt;p&gt;Dask-jobqueue enables you to create and manage Dask clusters in HPC environments using common job schedulers like SLURM, PBS, SGE, or LSF. It allows dynamic scaling of Dask workers as HPC jobs, adhering to the HPC’s resource allocation and scheduling policies.&lt;/p&gt;
&lt;p&gt;Steps to Deploy:&lt;/p&gt;
&lt;section id="install-dask-jobqueue"&gt;
&lt;h3&gt;Install dask-jobqueue&lt;/h3&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;jobqueue&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="configure-dask-cluster"&gt;
&lt;h3&gt;Configure Dask Cluster&lt;/h3&gt;
&lt;p&gt;In your Python script, configure a Dask cluster that submits jobs via your HPC’s scheduler. For example, using SLURM:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_jobqueue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SLURMCluster&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SLURMCluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;regular&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cores&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;120GB&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;walltime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;02:00:00&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="scale-the-cluster"&gt;
&lt;h3&gt;Scale the Cluster&lt;/h3&gt;
&lt;p&gt;Adjust the number of worker jobs based on your needs.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="run-xarray-computations"&gt;
&lt;h3&gt;Run Xarray Computations&lt;/h3&gt;
&lt;p&gt;Perform your data analysis with Xarray, leveraging the Dask cluster for parallel processing.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xarray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xr&lt;/span&gt;

&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data.nc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This setup allows you to efficiently utilize HPC resources for processing large datasets with Xarray and Dask, scaling from a single machine to multiple nodes in an HPC environment.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="deploy-xarray-and-dask-on-aws"&gt;
&lt;h2&gt;Deploy Xarray and Dask on AWS&lt;/h2&gt;
&lt;p&gt;Agencies like NASA, NOAA, USGS, and others are &lt;a class="reference external" href="https://www.earthdata.nasa.gov/eosdis/cloud-evolution"&gt;pumping massive datasets to Amazon&lt;/a&gt; and making them publicly available. Researchers are using this data today to ask questions like the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;What does air pollution look like over the 9000 most populated cities on earth?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Where should we build factories to take advantage of cheap renewable energy while bypassing local utilities?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is the capacity of China’s fleet of solar farms, and how has this been changing over time?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How much oil has been flowing out of Venezuela on tanker ships?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How is drought likely to affect grain production in Northern Africa?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This data is plentiful and freely available, provided you can run computations close to it.&lt;/p&gt;
&lt;p&gt;Fortunately, there are several ways to run Xarray and Dask on AWS.&lt;/p&gt;
&lt;section id="dask-cloudprovider"&gt;
&lt;h3&gt;Dask-Cloudprovider&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Automates the setup of Dask clusters on AWS, including options like AWS Fargate for serverless deployment. See &lt;a class="reference external" href="https://cloudprovider.dask.org/en/latest/"&gt;the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code Sample:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_cloudprovider.aws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FargateCluster&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FargateCluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="dask-kubernetes"&gt;
&lt;h3&gt;Dask-Kubernetes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Ideal for deploying Dask within a Kubernetes environment on AWS (Amazon EKS), allowing integration with Kubernetes resources. See &lt;a class="reference external" href="https://kubernetes.dask.org/en/latest/"&gt;the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code Sample:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_kubernetes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KubeCluster&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KubeCluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_yaml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;worker-spec.yml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="coiled"&gt;
&lt;h3&gt;Coiled&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Provides a managed service for Dask on AWS, handling cluster provisioning and management, suitable for users preferring a simplified approach. See &lt;a class="reference external" href="https://docs.coiled.io/user_guide/index.html"&gt;the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code Sample:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Each method offers a different balance of control and convenience, allowing you to choose the one that best fits your project’s requirements and your level of expertise with AWS and Dask.&lt;/p&gt;
&lt;p&gt;We of course like &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt; the best.  It’s the only option that’s &lt;a class="reference external" href="https://www.coiled.io/security"&gt;secure out of the box&lt;/a&gt;, avoids common costly mistakes like idle clusters or lingering network resources, and it’s also the easiest to set up.  It’s nice knowing though that you can always go to OSS options if you prefer.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="should-i-use-hpc-or-cloud"&gt;
&lt;h2&gt;Should I use HPC or Cloud?&lt;/h2&gt;
&lt;p&gt;Often people want to know where they should run their computations.  If you’re a user the answer is simple:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Run wherever your IT admins are willing to give you resources 🙂&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you have choices, then run wherever your data is.  Data gravity is a big deal.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So if you’re processing data that just came out of a simulation on a big HPC machine, and the data is sitting right there on the NFS, then use HPC.  If you’re processing data that is hosted on AWS by NASA or NOAA or ESA then please run on the cloud.&lt;/p&gt;
&lt;p&gt;If you’re a data provider and you’re planning where to host data then the answer comes down to reuse:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Is this dataset going to be generated once and then read once?  If so, then host the data wherever it’s generated, often this is HPC.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the dataset going to be generated once but then used many times, possibly by many different groups?  If so, then the cloud might lower barriers to easier sharing and collaboration.  There’s no data web portal you’ll be able to build which will be nearly as interoperable or accessible as just dumping raw files into an &lt;a class="reference external" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/RequesterPaysBuckets.html"&gt;S3 bucket with requester pays public access&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section id="how-much-does-the-cloud-cost"&gt;
&lt;h2&gt;How much does the cloud cost?&lt;/h2&gt;
&lt;p&gt;This varies by region and tier, but we’ll include some rule of thumb numbers below to store 1 TB of data:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Storage: $10-20&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Egress: $100&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloud Compute: $0.10&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Storing large volumes of data in the cloud can be moderately expensive, but generally these costs don’t compare to, say, the costs of an FTE.  Egress costs can be very substantial if the primary way that people access your data is to download it outside of the cloud, or to different regions than where the data is stored.&lt;/p&gt;
&lt;p&gt;Once data is stored in the cloud though, it is incredibly cheap to process, at &lt;a class="reference internal" href="blog/ten-cents.html"&gt;&lt;span class="doc"&gt;around the cost of $0.10 per 1 TB&lt;/span&gt;&lt;/a&gt;, if you do everything right.  The cost to rent an EC2 instance in the same region, where accessing the data is fast and cheap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If done correctly, processing cloud data is incredibly cheap.&lt;/strong&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-process-250-tb-for-25"&gt;
&lt;h2&gt;Example: Process 250 TB for $25&lt;/h2&gt;
&lt;p&gt;As an example, we’ll include below some code that we ran to produce this video:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Start a Coiled cluster&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nwm-1979-2020&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# close to data&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Access 250 TB NOAA National Water Model in the cloud with Xarray&lt;/span&gt;
&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_zarr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;fsspec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_mapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3://noaa-nwm-retrospective-2-1-zarr-pds/rtout.zarr&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;consolidated&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;896&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;x&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;y&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zwattablrt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1979-02-01&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;2020-12-31&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# water table depth&lt;/span&gt;

&lt;span class="c1"&gt;# Merge on US counties&lt;/span&gt;
&lt;span class="n"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fsspec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requester_pays&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;counties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rioxarray&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_rasterio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3://nwm-250m-us-counties/Counties_on_250m_grid.tif&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;auto&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;squeeze&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counties_aligned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;align&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;override&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Calculate average US county water table depth&lt;/span&gt;
&lt;span class="n"&gt;county_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counties_aligned&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;county_mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xarray&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xarray_reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;counties_aligned&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;county&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mean&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expected_groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;county_id&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;county_mean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This example processes 250 TiB for $25 in 20 minutes on 200 cloud machines.  If you have an AWS account you should be able to run it yourself (you’ll have to &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;set up Coiled&lt;/a&gt; first, which takes around 5 minutes).  You can read more on technical details in &lt;a class="reference internal" href="blog/coiled-xarray.html"&gt;&lt;span class="doc"&gt;our blog post&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-is-pangeo"&gt;
&lt;h2&gt;What is Pangeo?&lt;/h2&gt;
&lt;p&gt;When Xarray and Dask started to take off in the geoscience community Xarray and
Dask maintainers got together and started Pangeo, a community organization for
large scale geoscience development.&lt;/p&gt;
&lt;p&gt;Since then the Pangeo community has grown to researchers and developers at
dozens of organizations working on …&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OSS software&lt;/strong&gt;: with developers collaborating on libraries like Xarray,
Dask, Zarr, and other projects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;community support&lt;/strong&gt; including frequent showcase meetings describing
technical or scientific research, as well as online forums to help new
researchers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distributed infrastructure&lt;/strong&gt; with publicly hosted cloud infrastructure&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the strongest aspects of Pangeo today is the community.  We’ll include a
few links below for further study:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://pangeo.io"&gt;Pangeo.io&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://discourse.pangeo.io/"&gt;Pangeo Forum&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Easy-to-use tools like Xarray and Dask represent a significant leap in data processing, making the &lt;strong&gt;analysis of extensive geospatial datasets feasible and affordable&lt;/strong&gt;. By moving computations closer to where the data is stored, whether in HPC environments or cloud platforms like AWS, &lt;strong&gt;researchers can now handle vast datasets efficiently&lt;/strong&gt;. This democratizes access to high-performance computing, allowing a broader range of researchers to engage in complex data analysis tasks without the burden of prohibitive costs or the need for specialized knowledge in distributed systems. As technology continues to evolve, it’s clear that tools like Xarray and Dask will play a crucial role in &lt;strong&gt;unlocking new possibilities in data-driven research&lt;/strong&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/xarray-at-scale.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Dec 21, 2023</summary>
    <category term="geospatial" label="geospatial"/>
    <category term="xarray" label="xarray"/>
    <published>2023-12-21T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/polars-queries-cloud-coiled.html</id>
    <title>Polars queries in the cloud with Coiled</title>
    <updated>2023-11-17T00:00:00+00:00</updated>
    <author>
      <name>Nathan Ballou</name>
    </author>
    <content type="html">&lt;script type="application/ld+json"&gt;
{
  "@context": "https://schema.org",
  "@type": "TechnicalArticle",
  "headline": "Polars queries in the cloud with Coiled",
  "description": "Process large datasets in the cloud with Polars and Coiled serverless functions. Query 150GB of Parquet data 5x faster than local processing.",
  "author": {
    "@type": "Person",
    "name": "Nathan Ballou"
  },
  "datePublished": "2023-11-17",
  "dateModified": "2023-11-17",
  "about": [
    {
      "@type": "Thing",
      "name": "Polars",
      "description": "Fast DataFrame library for Python and Rust"
    },
    {
      "@type": "Thing",
      "name": "Serverless Computing",
      "description": "Cloud computing model for running functions without managing servers"
    },
    {
      "@type": "Thing",
      "name": "Data Processing",
      "description": "Techniques for processing and analyzing large datasets"
    },
    {
      "@type": "Thing",
      "name": "Cloud Computing",
      "description": "Computing services delivered over the internet"
    }
  ],
  "teaches": [
    "How to process large datasets with Polars in the cloud",
    "Using serverless functions for data processing",
    "Querying Parquet files stored in S3",
    "Cost-effective cloud data processing strategies"
  ],
  "audience": {
    "@type": "Audience",
    "audienceType": "data engineers, data scientists, python developers"
  },
  "educationalLevel": "intermediate",
  "learningResourceType": "tutorial",
  "programmingLanguage": "Python",
  "operatingSystem": "Linux, macOS, Windows",
  "softwareRequirements": "Python, Polars, Coiled"
}
&lt;/script&gt;&lt;section id="polars-queries-in-the-cloud-with-coiled"&gt;

&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Data&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Local&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Coiled&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Speed Up&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;150 GB Parquet&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;30+ mins&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;7 mins&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;5x&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Code snippet of using the coiled.function decorator to run a query with Polars on a large VM in the cloud" src="https://docs.coiled.io/_images/polars_coiled_functions.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Local machines can struggle to process large datasets due to memory and network limitations. &lt;a class="reference internal" href="user_guide/functions.html#coiled-functions"&gt;&lt;span class="std std-ref"&gt;Coiled Functions&lt;/span&gt;&lt;/a&gt; provide a cloud-based solution that allows for efficient and cost-effective handling of such extensive datasets, overcoming the constraints of local hardware for complex data processing tasks. Incorporating libraries like &lt;a class="reference external" href="https://www.pola.rs/"&gt;Polars&lt;/a&gt; can further enhance this approach, leveraging optimized computation capabilities to process data more quickly and efficiently.&lt;/p&gt;
&lt;p&gt;In this post we’ll use Coiled Functions to process the 150 GB &lt;a class="reference external" href="https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page"&gt;Uber-Lyft dataset&lt;/a&gt; on a single cloud machine with Polars.&lt;/p&gt;
&lt;section id="query-s3-data-locally-with-polars"&gt;
&lt;h2&gt;Query S3 data locally with Polars&lt;/h2&gt;
&lt;p&gt;The process starts with creating queries as though we would for local execution against the dataset.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Define the S3 path and storage options and load the data into memory&lt;/span&gt;
    &lt;span class="n"&gt;s3_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;s3://coiled-datasets/uber-lyft-tlc/*&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aws_region&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s3_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;compute_percentage_of_tipped_rides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lazy_df&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Compute the percentage of rides with a tip for each license number&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lazy_df&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_columns&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tipped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tipped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tipped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;percentage_tipped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;query_results&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Load and process the dataset to compute the results&lt;/span&gt;
    &lt;span class="n"&gt;lazy_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;compute_percentage_of_tipped_rides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lazy_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query is pretty basic; it is meant as a simple example of how we can process these large files. If we execute this query locally, we would pull all of the data onto the machine, process it, and return a small fraction of the data. Unfortunately for me, at least, this would hit the physical memory limits of my machine. It would also take a long time, as I would have to download the whole dataset and be limited by my network bandwidth. In other situations this would also be an expensive operation as I would have to pay egress costs on the data. Let’s see how Coiled can help solve these issues.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="query-s3-data-in-the-cloud-with-coiled-serverless-functions"&gt;
&lt;h2&gt;Query S3 data in the cloud with Coiled serverless functions&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference internal" href="user_guide/functions.html#coiled-functions"&gt;&lt;span class="std std-ref"&gt;Coiled Functions&lt;/span&gt;&lt;/a&gt; allow us to use cloud machines that are large enough to process this dataset. Coiled connects to AWS or GCP, so we can access any virtual machine (VM) that is available there. We can also &lt;a class="reference external" href="https://blog.coiled.io/blog/data-proximate-computing.html"&gt;co-locate the VM with the data&lt;/a&gt;, speeding up processing and avoiding egress costs.&lt;/p&gt;
&lt;p&gt;All we need to do is add a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator to our main function. The decorator tells Coiled that it should spin up a large VM on AWS, run the query there, and then return the result locally.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.4xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# VM with 64GB RAM&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# AWS region to match the data location&lt;/span&gt;
    &lt;span class="n"&gt;keepalive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;5 minutes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# VM keepalive time for potential subsequent queries&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;query_results&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Load and process the dataset to compute the desired metric&lt;/span&gt;
    &lt;span class="n"&gt;lazy_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;compute_percentage_of_tipped_rides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lazy_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function()&lt;/span&gt;&lt;/code&gt; takes a &lt;a class="reference external" href="https://docs.coiled.io/user_guide/api.html#coiled.function"&gt;variety of arguments&lt;/a&gt;. Let’s take a look at a few of the important ones:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;vm_type&lt;/span&gt;&lt;/code&gt;: This specifies the type of VM that we want the computation to run on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;region&lt;/span&gt;&lt;/code&gt;: This specifies the AWS or GCP region that we want to start the VM in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;keepalive&lt;/span&gt;&lt;/code&gt;: This keeps the VM alive so that we can run multiple queries against the data in memory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s execute our queries and pull the results back to our local machine:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_results&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HV0002&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.08440046492889111&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HV0003&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1498555901186066&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HV0004&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.09294857737045926&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HV0005&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1912300216459857&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;That’s all you need to do to run this function in the cloud. There is no need to adjust the other functions or manually spin up a VM.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-just-happened"&gt;
&lt;h2&gt;What just happened&lt;/h2&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function()&lt;/span&gt;&lt;/code&gt; first scanned my local environment for the installed packages and replicated those on the VM that I requested. We didn’t need to specify a Docker container or a specific Python environment (though we could). Coiled then started a VM in AWS with the specified EC2 instance. The VM took about two minutes to start up, and then downloading the dataset and running the computations in the cloud took another five minutes. Not too bad for processing 150 GB! Finally, Coiled returned the results back to our local machine.&lt;/p&gt;
&lt;p&gt;If we look at the Coiled Dashboard, we can see that all the data was pulled into the VM in the cloud, processed, and returned.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Plot from the Coiled dashboard showing the memory increase on our VM" src="https://docs.coiled.io/_images/coiled_functions_polars_memory.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;VM Memory Utilization Increasing as the Computation Runs&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Coiled would normally shut down the VM immediately after the Python interpreter shuts down in order to reduce costs. We specified
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;keepalive=&amp;quot;5&lt;/span&gt; &lt;span class="pre"&gt;minutes&amp;quot;&lt;/span&gt;&lt;/code&gt; to keep the VM running in the cloud so that we could use the same VM to do other queries. This avoids the normal two minute boot time; we call this “warm start.”&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;functions&lt;/span&gt;&lt;/code&gt; and Polars provide a solution for processing large datasets in the cloud. This approach allows data practitioners to overcome local machine limitations and reduce operational costs. This example demonstrates how simple it is to execute large-scale queries using Coiled. By deploying a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator, we can swiftly transition from local to cloud processing, gaining the benefits of powerful cloud computing resources. The outcome is a faster, more scalable data processing workflow that enhances productivity and focuses on data analysis rather than infrastructure challenges.&lt;/p&gt;
&lt;p&gt;Want to run this example yourself?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Get started with Coiled for free at &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;coiled.io/start&lt;/a&gt;. This example runs comfortably within the free tier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy and paste the first code snippet and add the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function()&lt;/span&gt;&lt;/code&gt; decorator.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can check out the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/labs/functions.html"&gt;docs&lt;/a&gt;
or take a look at how to utilize Coiled Functions to run the same query &lt;a class="reference external" href="https://blog.coiled.io/blog/coiled-run-duckdb.html"&gt;using DuckDB&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/polars-queries-cloud-coiled.html"/>
    <summary>DataCode snippet of using the coiled.function decorator to run a query with Polars on a large VM in the cloud</summary>
    <category term="cloud-computing" label="cloud-computing"/>
    <category term="data-processing" label="data-processing"/>
    <category term="dataframes" label="dataframes"/>
    <category term="polars" label="polars"/>
    <category term="s3" label="s3"/>
    <category term="serverless" label="serverless"/>
    <published>2023-11-17T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/processing-terabyte-scale-nasa-cloud-datasets-with-coiled.html</id>
    <title>Processing Terabyte-Scale NASA Cloud Datasets with Coiled</title>
    <updated>2023-11-01T00:00:00+00:00</updated>
    <author>
      <name>James Bourbeau</name>
    </author>
    <content type="html">&lt;section id="processing-terabyte-scale-nasa-cloud-datasets-with-coiled"&gt;

&lt;p&gt;&lt;em&gt;We show how to run existing NASA data workflows on the cloud, in parallel, with minimal code changes using Coiled. We also discuss cost optimization.&lt;/em&gt;&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Data&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Local&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Coiled&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Speed Up&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Cost&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;500 GB NetCDF&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;383 mins&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;9 mins&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;42x&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;$0.36&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;People often run the same function over many files in cloud storage. This looks something like the following:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Step 1: Get a list of all of our files (typically thousands of NetCDF files).&lt;/span&gt;
&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Create a function to process each file.&lt;/span&gt;
&lt;span class="c1"&gt;#         Then either return the processed result&lt;/span&gt;
&lt;span class="c1"&gt;#         or save it to some external data store like S3.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="c1"&gt;# Step 3: Process all of the files.&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 4: Analyze results locally.&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This straightforward but important pattern occurs frequently in groups working with &lt;a class="reference external" href="https://www.earthdata.nasa.gov/"&gt;NASA Earthdata&lt;/a&gt; and NASA Distributed Active Archive Centers (DAACs) to move scientific workloads to the cloud. Here are some examples:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extract greenhouse gasses over urban areas from &lt;a class="reference external" href="https://www.earthdata.nasa.gov/sensors/tropomi"&gt;TROPOMI&lt;/a&gt; L2 data&lt;/strong&gt;: TROPOMI measures trace gasses and aerosols relevant for air quality and climate. It generates a large volume of &lt;a class="reference external" href="https://www.earthdata.nasa.gov/engage/open-data-services-and-software/data-information-policy/data-levels"&gt;L2 data&lt;/a&gt; stored as millions of files on S3. In our particular project we’re interested in greenhouses over highly populated urban areas, and how they affect quality of life. We need to select those files over many cities and then construct a timeseries over them to see how air quality changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Grid sea ice freeboard data from NASA’s &lt;a class="reference external" href="https://icesat-2.gsfc.nasa.gov/"&gt;ICESat-2&lt;/a&gt; mission&lt;/strong&gt;: This &lt;a class="reference external" href="https://github.com/nsidc/NSIDC-Data-Tutorials/tree/main/notebooks/ICESat-2_Cloud_Access/h5cloud"&gt;workflow&lt;/a&gt; from NSIDC researchers loads &lt;a class="reference external" href="https://nsidc.org/data/atl10/versions/5"&gt;L3 sea ice freeboard HDF5 data&lt;/a&gt;, selects a lat-lon bounding box of interest, and regrids the dataset to the EASE-Grid v2 6.25 km projected grid using drop-in-the-bucket resampling. The resulting processed dataset is then written out to a local Parquet file to be used in further analysis. This is particularly valuable as it allows researchers to easily create their own specialized datasets with spatial and temporal resolutions relevant for their analysis.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post we’ll show how to run this same “function on many files” analysis pattern on the cloud with Coiled, which you can copy and modify for your own use case. We’ll also highlight cost optimization strategies for processing terabyte-scale data for fractions of a dollar.&lt;/p&gt;
&lt;figure class="align-default" id="cost-duration-comparison"&gt;
&lt;img alt="Comparing cost and duration between running the same workflow locally on a laptop (data egress costs), running on AWS, and running with cost optimizations on AWS." src="https://docs.coiled.io/_images/geospatial-for-loop-comparison.svg" style="width: 85%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Comparing cost and duration between running the same workflow locally on a laptop (data egress costs), running on AWS, and running with cost optimizations on AWS.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;section id="processing-locally"&gt;
&lt;h2&gt;Processing Locally&lt;/h2&gt;
&lt;p&gt;In this example we process many NetCDF files stored on S3 from the &lt;a class="reference external" href="https://podaac.jpl.nasa.gov/dataset/MUR-JPL-L4-GLOB-v4.1"&gt;MUR Global Sea Surface Temperature NASA dataset&lt;/a&gt; to look at surface temperature variation over the US Great Lakes region:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tempfile&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;earthaccess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xarray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xr&lt;/span&gt;

&lt;span class="c1"&gt;# Step 1: Get a list of all files.&lt;/span&gt;
&lt;span class="c1"&gt;#         Use earthaccess to authenticate and find data files (total of 500 GB).&lt;/span&gt;
&lt;span class="n"&gt;granules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;earthaccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;short_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MUR-JPL-L4-GLOB-v4.1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;temporal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2020-01-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2021-12-31&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 2: Create a function to process each file.&lt;/span&gt;
&lt;span class="c1"&gt;#         Load and subset each data granule / file.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;granule&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;earthaccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;granule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;93&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;cond&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sea_ice_fraction&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isnan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sea_ice_fraction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analysed_sst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 3: Run function on each file in a loop&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;granule&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;granules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;granule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 4: Combine and plot results&lt;/span&gt;
&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This specific example uses &lt;a class="reference external" href="https://nsidc.github.io/earthaccess/"&gt;earthaccess&lt;/a&gt; to authenticate with NASA Earthdata and download our dataset (~500 GB of NetCDF files) and Xarray to load and select the region of the data we’re interested in, but we’ve seen many different approaches here, including file formats like HDF, Zarr, and GeoTIFF, as well as libraries like Xarray, rasterio, and pandas. In each case though the underlying pattern is the same.&lt;/p&gt;
&lt;p&gt;We then combine the results from processing each data file and plot the results.&lt;/p&gt;
&lt;figure class="align-default" id="function-adaptive-scaling"&gt;
&lt;img alt="Standard deviation of sea surface temperature over the Great Lakes region in the US." src="https://docs.coiled.io/_images/geospatial-sst-great-lakes.png" style="width: 100%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Standard deviation of sea surface temperature over the Great Lakes region in the US.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Running this locally on my laptop takes ~6.4 hours and costs NASA ~$25 in data egress costs (based on a $0.05/GB egress charge rate – normal is $0.10/GB, but presumably NASA gets a good deal).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="processing-on-the-cloud-with-coiled"&gt;
&lt;h2&gt;Processing on the Cloud with Coiled&lt;/h2&gt;
&lt;p&gt;Let’s run the exact same analysis on the cloud. Running on the cloud helps in a couple of ways:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Proximate Computing&lt;/strong&gt;: Running computations closer to where the data is stored increases performance and avoids data transfer costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale&lt;/strong&gt;: Distributing the processing over many machines in parallel lets us tackle larger volumes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using Coiled to run the same workflow on the cloud involves lightly annotating our &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function with the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;# Run in the same region as data&lt;/span&gt;
    &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;earthaccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth_environ&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  &lt;span class="c1"&gt;# Forward Earthdata auth to cloud VMs&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;granule&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Keep everything inside the function the same&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This tells Coiled to run our processing function on a VM in AWS and then return the result back to our local machine.&lt;/p&gt;
&lt;p&gt;Additionally, we switch to using the Coiled Function &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.map()&lt;/span&gt;&lt;/code&gt; method (similar to &lt;a class="reference external" href="https://docs.python.org/3/library/functions.html#map"&gt;Python’s builtin &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map&lt;/span&gt;&lt;/code&gt; function&lt;/a&gt;) to run our &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function across all the input files in parallel:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Step 3: Run function on each file in a loop
&lt;del&gt;results = []&lt;/del&gt;
&lt;del&gt;for granule in granules:&lt;/del&gt;
&lt;del&gt;    result = process(granule)&lt;/del&gt;
&lt;del&gt;    results.append(result)&lt;/del&gt;
results = process.map(granules)     # This runs on the cloud in parallel&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these two small code changes, the same data processing runs in parallel across many VMs on AWS. The runtime drops to ~9 minutes (a factor of ~42x speedup) and costs ~$1.52 (a factor ~16x less).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="optimizing-costs"&gt;
&lt;h2&gt;Optimizing Costs&lt;/h2&gt;
&lt;p&gt;Processing 500 GB of data costs us $1.52.  This is cheap.&lt;/p&gt;
&lt;p&gt;We bring this cost down further to $0.36 with the following techniques:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spot&lt;/strong&gt;: Use &lt;a class="reference external" href="https://www.coiled.io/blog/save-money-with-spot"&gt;spot instances&lt;/a&gt;, which are excess capacity offered at a discount.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ARM&lt;/strong&gt;: Use ARM-based instances, which are less expensive than their Intel-based equivalents, often with &lt;a class="reference external" href="https://blog.coiled.io/blog/dask-graviton.html"&gt;similar, or better, performance&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instance Type Selection&lt;/strong&gt;: Use single-core instances (only available when using ARM), because this particular computation doesn’t require large computing resources.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this additional configuration made to the Coiled Functions decorator, our full cost-optimized cloud workflow looks like the following:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tempfile&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;earthaccess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xarray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xr&lt;/span&gt;

&lt;span class="c1"&gt;# Step 1: Get a list of all files.&lt;/span&gt;
&lt;span class="c1"&gt;#         Use earthaccess to authenticate and find data files (total of 500 GB).&lt;/span&gt;
&lt;span class="n"&gt;granules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;earthaccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;short_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MUR-JPL-L4-GLOB-v4.1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;temporal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2020-01-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2021-12-31&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 2: Create a function to process each file.&lt;/span&gt;
&lt;span class="c1"&gt;#         Load and subset each data granule / file.&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;# Run in the same region as data&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;earthaccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth_environ&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  &lt;span class="c1"&gt;# Forward Earthdata auth to cloud VMs&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# Use spot instances when available&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;arm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                            &lt;span class="c1"&gt;# Use ARM-based instances&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;cpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                               &lt;span class="c1"&gt;# Use Single-core instances&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;granule&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;earthaccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;granule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;93&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;cond&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sea_ice_fraction&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isnan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sea_ice_fraction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analysed_sst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 3: Run function on each file in parallel&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;granules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# This runs on the cloud in parallel&lt;/span&gt;
&lt;/span&gt;

&lt;span class="c1"&gt;# Step 4: Combine and plot results&lt;/span&gt;
&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;and, while code runtime stays the same at ~9 minutes, cost drops to ~$0.36 (a factor of ~70x less than running locally).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;We ran a common data subsetting-style workflow on NASA data in the cloud with Coiled. This demonstrated the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy&lt;/strong&gt;: Minimal code changes to migrate existing code to run on the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fast&lt;/strong&gt;: Acceleration of the workflow runtime by ~42x.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cheap&lt;/strong&gt;: Cost reduction of ~70x.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We hope this post shows that with just a handful of lines of code you can migrate your data workflow to the cloud in a straightforward manner. We also hope it provides a template you can copy and adapt for your own analysis use case.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Comparing cost and duration between running the same workflow locally on a laptop, running on AWS, and running with cost optimizations on AWS." src="https://docs.coiled.io/_images/geospatial-for-loop-comparison.svg" style="width: 85%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Comparing cost and duration between running the same workflow locally on a laptop (data egress costs), running on AWS, and running with cost optimizations on AWS.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Want to run this example yourself?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Get started with Coiled for free at &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;coiled.io/start&lt;/a&gt;. This example runs comfortably within the free tier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy and paste the code snippet above.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="acknowledgements"&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;This work was done in collaboration with &lt;a class="reference external" href="https://nasa-openscapes.github.io"&gt;NASA Openscapes&lt;/a&gt;, a community &lt;a class="reference external" href="https://openscapes.org/blog/2023-08-01-nasa-champions/"&gt;supporting research teams’ cloud migration&lt;/a&gt; with &lt;a class="reference external" href="https://openscapes.org/blog/2023-10-13-nasa-jupyterhub-coiled/"&gt;different Cloud environments and technologies&lt;/a&gt;, including Coiled.&lt;/p&gt;
&lt;p&gt;We’d also like to thank &lt;a class="reference external" href="https://github.com/asteiker"&gt;Amy Steiker&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/betolink"&gt;Luis Lopez&lt;/a&gt;, and &lt;a class="reference external" href="https://github.com/andypbarrett"&gt;Andy Barrett&lt;/a&gt; from NSIDC and &lt;a class="reference external" href="https://github.com/aronnem"&gt;Aronne Merrelli&lt;/a&gt; from the University of Michigan for sharing their scientific use cases which motivated this post.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/processing-terabyte-scale-nasa-cloud-datasets-with-coiled.html"/>
    <summary>We show how to run existing NASA data workflows on the cloud, in parallel, with minimal code changes using Coiled. We also discuss cost optimization.Comparing cost and duration between running the same workflow locally on a laptop, running on AWS, and running with cost optimizations on AWS.</summary>
    <published>2023-11-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/jupyter-notebook-gpu.html</id>
    <title>Run Jupyter Notebooks on a GPU on the Cloud</title>
    <updated>2023-10-10T00:00:00+00:00</updated>
    <author>
      <name>Sarah Johnson</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Oct 10, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="run-jupyter-notebooks-on-a-gpu-on-the-cloud"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/scharlottej13"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/8620816?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Sarah Johnson&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;GPUs accelerate tasks like ML training, computer vision, and analytics, but can be challenging to access. Using the cloud can open up access to GPUs, but comes with its own challenges including:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complex Cloud Infrastructure&lt;/strong&gt;: AWS SageMaker, for example, requires navigating AWS IAM roles, VPC settings, and other cloud configuration steps before even starting a GPU instance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limited Customization&lt;/strong&gt;: Tools like SageMaker and Google Colab provide managed environments, but they can feel restrictive, making it hard to install custom dependencies or optimize GPU usage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Poor Developer Experience&lt;/strong&gt;: Compared to tools like Jupyter Notebooks, the UI for SageMaker or Colab can feel clunky and unintuitive, adding friction to the development process.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Coiled can launch a GPU-enabled Jupyter Notebook for you, from your own AWS, GCP, or Azure account, in a couple minutes.&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled notebook start --gpu --sync
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default" id="coiled-notebook-start"&gt;
&lt;img alt="Screencast of using `coiled notebook start` to show how to start a jupyter notebook on a GPU." src="https://docs.coiled.io/_images/jupyter-notebook-gpu.gif" style="width: 800px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;This has a few benefits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GPU access&lt;/strong&gt;: You’ll get temporary access to a GPU machine for as long as the notebook is running. When you’re done, it’ll turn off to minimize costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Software synchronization&lt;/strong&gt;: You’ll get all of your current Python packages, including the same version of libraries like PyTorch, but those libraries will be properly configured (with CUDA drivers and everything) to operate on the underlying GPU. You don’t need to mess around with Docker.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File synchronization&lt;/strong&gt;: You’ll also get all of your local files, including your notebooks, live-synced up to the cloud machine running Jupyter. Additionally, any changes you make in the notebook on the cloud machine will be synchronized back down to your computer, saving all of your changes on your local hard drive.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This creates a seamless experience for interactive work.&lt;/p&gt;
&lt;section id="getting-started"&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;If you haven’t already signed up for Coiled, it’s easy to &lt;a class="reference internal" href="user_guide/setup/index.html"&gt;&lt;span class="doc"&gt;get started&lt;/span&gt;&lt;/a&gt;. You’ll get 500 free CPU hours each month, though you’ll still have to pay your cloud provider.&lt;/p&gt;
&lt;p&gt;Next, install Coiled alongside additional notebook dependencies like jupyterlab and your machine learning library of choice.&lt;/p&gt;
&lt;div class="sd-tab-set docutils"&gt;
&lt;input checked="checked" id="sd-tab-item-0" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-0"&gt;
conda&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;conda&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.11&lt;span class="w"&gt; &lt;/span&gt;pytorch&lt;span class="w"&gt; &lt;/span&gt;torchvision&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;jupyterlab&lt;span class="w"&gt; &lt;/span&gt;jupyter-server-proxy
$&lt;span class="w"&gt; &lt;/span&gt;conda&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;env
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;input id="sd-tab-item-1" name="sd-tab-set-0" type="radio"&gt;
&lt;label class="sd-tab-label" for="sd-tab-item-1"&gt;
pip&lt;/label&gt;&lt;div class="sd-tab-content docutils"&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;coiled[notebook]&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;torch&lt;span class="w"&gt; &lt;/span&gt;torchvision
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then start a JupyterLab instance on a GPU-enabled VM on the cloud:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;notebook&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;--gpu
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Under the hood, Coiled handles:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Provisioning a cloud VM with GPU hardware. In this case, a g4dn.xlarge (Tesla T4) instance on AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up the appropriate NVIDIA drivers, CUDA runtime, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatically installing the same packages you have locally on the cloud VM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Making sure to install the CUDA-compiled version of PyTorch on the cloud VM.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also include additional arguments like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--sync&lt;/span&gt;&lt;/code&gt; to live-sync your local files, including your Jupyter Notebooks, to the cloud machine. Any changes you make in the notebook running remotely will be synchronized back down to your computer. You’ll need Mutagen installed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--vm-type&lt;/span&gt;&lt;/code&gt; to request a specific VM type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--container&lt;/span&gt;&lt;/code&gt; to specify a Docker container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--region&lt;/span&gt;&lt;/code&gt; to specify a region. We find GPUs are often easier to get in AWS region &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;us-west-2&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;a class="reference internal" href="user_guide/notebooks.html"&gt;&lt;span class="doc"&gt;our documentation&lt;/span&gt;&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-define-a-pytorch-model"&gt;
&lt;h2&gt;Example: Define a PyTorch Model&lt;/h2&gt;
&lt;p&gt;Now that we have a notebook running, we can define a PyTorch neural network and use it for model inference. We’ve adapted this example from this &lt;a class="reference external" href="https://pytorch.org/tutorials/beginner/basics/buildmodel_tutorial.html"&gt;introductory PyTorch tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch.utils.data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DataLoader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torchvision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transforms&lt;/span&gt;

&lt;span class="c1"&gt;# Select what hardware to use&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# NVIDIA GPU&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backends&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mps&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;# Apple silicon GPU&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;# CPU&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;= }&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;NeuralNetwork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flatten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linear_relu_stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReLU&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReLU&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;logits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linear_relu_stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;logits&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We can then use the model to generate predictions based on the input data.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NeuralNetwork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pred_probab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Softmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y_pred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pred_probab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;This example was primarily illustrative, to show how you can get up and running with Jupyter Notebooks on GPUs. For more extensive machine learning examples you might consider:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/pytorch.html"&gt;&lt;span class="doc"&gt;Train a PyTorch model on the CIFAR10 dataset&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/mlops-with-mlflow.html"&gt;&lt;span class="doc"&gt;Experiment tracking with MLflow&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/hpo.html"&gt;&lt;span class="doc"&gt;Hyperparameter Optimization with Optuna and XGBoost&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/jupyter-notebook-gpu.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Oct 10, 2023</summary>
    <category term="gpu" label="gpu"/>
    <category term="jupyternotebook" label="jupyter notebook"/>
    <published>2023-10-10T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/ten-cents.html</id>
    <title>Ten Cents Per Terabyte</title>
    <updated>2023-10-06T00:00:00+00:00</updated>
    <author>
      <name>Matthew Rocklin</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Oct 6, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;2m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="ten-cents-per-terabyte"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://matthewrocklin.com/"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://matthewrocklin.com/_static/profile.jpg" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Matthew Rocklin&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default" id="back-of-the-envelope-calculation"&gt;
&lt;img alt="Back-of-the-envelope calculation for a simple workload bound primarily by network bandwidth. Calculation is 1 TB / (60 MB/s) / 3600 s/hr * $0.02 / hr = $0.10" src="https://docs.coiled.io/_images/ten-cents.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;The optimal cost of cloud computing&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I often get asked the question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; &lt;em&gt;What should the following workflow cost in the cloud?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If the computation is some sophisticated ML workflow then the answer is who
knows 🤷?  We’ll have to run it and see.  But most workloads are simple and so,
when tuned, are primarily bound by S3 bandwidth.  In this case the answer is
pretty easy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; &lt;em&gt;If you do everything right, the cost is ten cents per terabyte.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let’s do a back-of-the-envelope calculation:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;~60 MB/s S3 bandwidth for a single core VM&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;s&gt;$0.05 per hour&lt;/s&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;s&gt;$0.04 per hour if you use ARM&lt;/s&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;$0.02 per hour if you use ARM + Spot (&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m7g.medium&lt;/span&gt;&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;1&lt;/span&gt; &lt;span class="pre"&gt;TB&lt;/span&gt; &lt;span class="pre"&gt;/&lt;/span&gt; &lt;span class="pre"&gt;(60&lt;/span&gt; &lt;span class="pre"&gt;MB/s)&lt;/span&gt; &lt;span class="pre"&gt;/&lt;/span&gt; &lt;span class="pre"&gt;3600&lt;/span&gt; &lt;span class="pre"&gt;s/hr&lt;/span&gt; &lt;span class="pre"&gt;*&lt;/span&gt; &lt;span class="pre"&gt;$0.02&lt;/span&gt; &lt;span class="pre"&gt;/&lt;/span&gt; &lt;span class="pre"&gt;hr&lt;/span&gt; &lt;span class="pre"&gt;=&lt;/span&gt; &lt;span class="pre"&gt;$0.10&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This assumes linear scaling, which for these workloads is usually a good
assumption.&lt;/p&gt;
&lt;section id="but-only-if-you-do-everything-right"&gt;
&lt;h2&gt;But only if you do everything right&lt;/h2&gt;
&lt;p&gt;We recently wrote about &lt;a class="reference internal" href="blog/coiled-xarray.html"&gt;&lt;span class="doc"&gt;scaling an Xarray computation out to 250 TB&lt;/span&gt;&lt;/a&gt;.
After optimization it cost $25, so right on target.&lt;/p&gt;
&lt;p&gt;However, before optimization that workload didn’t run at all. After some work
it ran, but was slow and expensive.  It was only after more tuning that the
workload ran at the magic ten-cents-per-terabyte number.  Tuning is hard.&lt;/p&gt;
&lt;p&gt;We see this often.  People come with wildly unoptimized cloud workflows running
on EC2 or AWS Lambda or Databricks or something, asking if we can make them
cheaper.  We’re often able to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/costs/index.html"&gt;get these computations down to a few cents&lt;/a&gt; (most
problems are small).&lt;/p&gt;
&lt;p&gt;Common problems include:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Downloading data locally rather than operating near the data (Egress costs
can be $0.10 per &lt;strong&gt;GB&lt;/strong&gt; rather than TB)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Really inefficient pure-Python code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Many small reads to S3, especially with non-cloud-optimized file formats or
bad partitioning&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provisioning big machines for small tasks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provisioning small machines for big tasks and swapping to disk&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Idling resources (the harder a system is to turn on, the more people avoid turning it off)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not using ARM (recompiling software is hard)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not using Spot (failing machines are annoying)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But of course, the biggest cost here is human time.  People spend days (weeks?)
wrangling unfamiliar cloud services (EC2, Batch, Lambda, SageMaker)
unsuccessfully, at the personnel cost of thousands of dollars (people are
expensive).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="optimize-for-ease-visibility-and-flexibility"&gt;
&lt;h2&gt;Optimize for Ease, Visibility, and Flexibility&lt;/h2&gt;
&lt;p&gt;I like compute platforms that optimize for …&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ease&lt;/strong&gt; of use and rapid iteration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visibility&lt;/strong&gt; of performance metrics&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt; of hardware&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This combination enables fast iteration cycles that enable humans to explore
the full space of solutions (code + hardware) effectively.&lt;/p&gt;
&lt;p&gt;Putting on my sales hat, that’s roughly what we’ve built at
&lt;a class="reference external" href="https://coiled.io"&gt;Coiled&lt;/a&gt; for Python code at scale.  It’s been a joy to use.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/ten-cents.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Oct 6, 2023</summary>
    <published>2023-10-06T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-expr-tpch-dask.html</id>
    <title>TPC-H Benchmarks for Query Optimization with Dask Expressions</title>
    <updated>2023-10-05T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;section id="tpc-h-benchmarks-for-query-optimization-with-dask-expressions"&gt;

&lt;figure class="align-default" id="tpch-dask-confidence"&gt;
&lt;img alt="https://docs.coiled.io/_images/tpch-comparison-dask-confidence.png" src="https://docs.coiled.io/_images/tpch-comparison-dask-confidence.png" style="width: 800px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/dask-contrib/dask-expr"&gt;Dask-expr&lt;/a&gt; is an ongoing effort to add a
&lt;a class="reference internal" href="blog/dask-expr-introduction.html"&gt;&lt;span class="doc"&gt;logical query optimization layer&lt;/span&gt;&lt;/a&gt; to Dask DataFrames.
We now have the first benchmark results to share that were run against the current DataFrame
implementation.&lt;/p&gt;
&lt;p&gt;Dask-expr is up to 3 times faster and more memory efficient in its current state than the status quo.
This is a very promising result for our future development efforts.&lt;/p&gt;
&lt;section id="results"&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;We are comparing Dask 2023.09.02 with the main branch of Dask-expr. Both implementations
will use the &lt;a class="reference internal" href="blog/shuffling-large-data-at-constant-memory.html"&gt;&lt;span class="doc"&gt;P2P shuffling algorithm&lt;/span&gt;&lt;/a&gt;. The results were produced on 100GB of data, e.g. scale
100 for the &lt;a class="reference external" href="https://www.tpc.org/tpch/"&gt;TPC-H&lt;/a&gt; benchmarks. The cluster had 20 workers with 16GB each.&lt;/p&gt;
&lt;figure class="align-default" id="tpch-comparison"&gt;
&lt;img alt="https://docs.coiled.io/_images/tpch-comparison-dask.png" src="https://docs.coiled.io/_images/tpch-comparison-dask.png" style="width: 800px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;(Runtime on a per-query basis of Dask DataFrame and Dask-expr)&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We can see that Dask-expr performs better on every single query, up to a 3-times improvement
compared to the status quo.&lt;/p&gt;
&lt;p&gt;Dask-expr reorders the query that was given by the user to only execute things that
are strictly necessary. It will, for example, drop unnecessary columns and thus reduce
the amount of data that is required. We can see that the biggest improvements are for
the queries that contain merge operations. These queries write a lot of data to disk
and send it over the network. Reducing the size of the data that is transferred has
a big impact on the performance.&lt;/p&gt;
&lt;p&gt;The reordering results in a reduced memory footprint as well. This is especially helpful
for users that struggle with memory pressure on their clusters.&lt;/p&gt;
&lt;figure class="align-default" id="tpch-comparison-memory"&gt;
&lt;img alt="https://docs.coiled.io/_images/tpch-comparison-dask-memory.png" src="https://docs.coiled.io/_images/tpch-comparison-dask-memory.png" style="width: 800px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;(Memory usage on a per query basis of Dask DataFrame and Dask-expr)&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We did another run on 1TB of data, which showed similar results but with a few notable differences:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Getting Dask-expr to compute the results successfully was very easy. 20 workers very sufficient to
compute the results, which is the same number of workers as we used for the 100GB benchmarks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The original version needed a bigger cluster with 50 workers and with 32GB memory each.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some queries didn’t complete the original version at all, because there were memory spikes on
single clusters. Dask-expr was able to complete all queries successfully.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Summarizing, getting these queries to complete was significantly easier with Dask-expr.&lt;/p&gt;
&lt;p&gt;These results show us that we are on the right course and motivates
us to improve the performance of Dask-expr further. There is still a lot of untapped potential.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="about-the-benchmarks"&gt;
&lt;h2&gt;About the benchmarks&lt;/h2&gt;
&lt;p&gt;The TPC-H benchmarks are a set of queries that are commonly used to compare performance of
different databases. Thus, they are very &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;merge&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;groupby&lt;/span&gt;&lt;/code&gt; heavy. Historically, the current
implementation didn’t perform very well on these types of queries. The
&lt;a class="reference internal" href="blog/shuffling-large-data-at-constant-memory.html"&gt;&lt;span class="doc"&gt;new P2P shuffling algorithm&lt;/span&gt;&lt;/a&gt; that was introduced earlier this year and now Dask-expr improves
the performance of Dask a lot.&lt;/p&gt;
&lt;p&gt;These queries only capture a small part of the &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe.html"&gt;Dask DataFrame API&lt;/a&gt; but are helpful to compare
performance.&lt;/p&gt;
&lt;p&gt;The full implementation of all queries is available &lt;a class="reference external" href="https://github.com/coiled/benchmarks/tree/63ca3c20cfd6c8352eebf880211e41a85793be32/tests/tpch"&gt;here&lt;/a&gt;.
A single example query is copied below, so you can get a sense of what they look like:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;var1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1995-03-15&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;var2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;BUILDING&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;lineitem_ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lineitem&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;orders_ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;orders&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cutomer_ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;flineitem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lineitem_ds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lineitem_ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;l_shipdate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;var1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;forders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders_ds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;orders_ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;o_orderdate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;var1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;fcustomer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cutomer_ds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cutomer_ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_mktsegment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;var2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;jn1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fcustomer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c_custkey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;o_custkey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;jn2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flineitem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;o_orderkey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;l_orderkey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;jn2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;revenue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jn2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;l_extendedprice&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;jn2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;l_discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jn2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;l_orderkey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;o_orderdate&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;o_shippriority&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;revenue&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;revenue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;l_orderkey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;revenue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;o_orderdate&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;o_shippriority&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="what-s-next"&gt;
&lt;h2&gt;What’s next&lt;/h2&gt;
&lt;p&gt;We have some additional steps planned over the next couple of weeks. These include:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Benchmarking Dask expressions against Spark&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scaling the benchmarks to 1TB, 10TB and 100TB.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are hoping that we can get additional insights into our implementation and are hoping to identify
bottlenecks.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="can-i-use-dask-expr"&gt;
&lt;h2&gt;Can I use Dask-expr?&lt;/h2&gt;
&lt;p&gt;Though Dask-expr is still under active development, the project is in a state where users can
try it out. The API coverage is pretty good already. We are still adding new
optimizations, so performance will improve in the future.&lt;/p&gt;
&lt;p&gt;You can try it out with:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We don’t expect that there exists any hard-to-find-bugs. We did not
reimplement any of the algorithms from Dask DataFrame, so we don’t expect hard to identify issues
or an unexpected loss of data.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Dask Expressions provides a faster and more memory efficient implementation of Dask DataFrames.
The project is still under active development, but it already outperforms the current status quo
by a significant margin on the TPC-H benchmarks.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-expr-tpch-dask.html"/>
    <summary>Dask-expr is an ongoing effort to add a
logical query optimization layer to Dask DataFrames.
We now have the first benchmark results to share that were run against the current DataFrame
implementation.</summary>
    <category term="benchmarks" label="benchmarks"/>
    <category term="dask" label="dask"/>
    <category term="performance" label="performance"/>
    <category term="queryoptimizer" label="query optimizer"/>
    <published>2023-10-05T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/coiled_observability_chunksize.html</id>
    <title>Coiled observability wins: Chunksize</title>
    <updated>2023-09-19T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;section id="coiled-observability-wins-chunksize"&gt;

&lt;p&gt;Distributed computing is hard, distributed debugging is even harder. Dask tries to simplify this
process as much as possible. Coiled adds additional observability features for your Dask clusters
and processes them to help users understand their workflows better.&lt;/p&gt;
&lt;p&gt;This series of posts will illustrate how we are building metrics that help us detect clusters that are
underutilized or run into errors. We are using this information to help users with optimizing
their workflows.&lt;/p&gt;
&lt;p&gt;This post will illustrate how small chunks of data can slow your workflow down and lead to
underutilizing your resources.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;Coiled collect all metrics that we describe in these posts. We only use them to
monitor the health of Dask clusters and to help users improve their Dask workflows. Coiled
users can turn the metric collection off at any time without compromising their workflows.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;section id="using-bigger-chunks-to-avoid-bottlenecks-on-the-scheduler"&gt;
&lt;h2&gt;Using bigger chunks to avoid bottlenecks on the scheduler&lt;/h2&gt;
&lt;p&gt;Dask processes your data in chunks. Every chunk creates one task per operation
on your data (we will ignore fusion of tasks for simplicity). The processing time per task grows
with the size of your chunks. Finding the appropriate size for these chunks is not always trivial.&lt;/p&gt;
&lt;p&gt;A common guideline for tasks within a Dask workflow is that tasks should run for at least 100ms.
The scheduler has some overhead while figuring out how to schedule your workflow efficiently. Having
tasks that run significantly faster would strain your scheduler.&lt;/p&gt;
&lt;p&gt;Dask tries to optimize the default chunksize, but this might conflict with
other parameters. One common example is many small input files. Dask generally parallelizes over
your files to speed up the I/O step. This will result in small chunks in this case.&lt;/p&gt;
&lt;p&gt;Let’s look at some of our observations from clusters that had tasks with a processing time
significantly below that. We will create an example with synthetic data here.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;da&lt;/span&gt;

&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This example creates a random Dask array with tiny chunks. Processing one of these chunks takes
only 2ms, which is very short for a Dask task. Let’s look at how our task stream looks while
processing this query:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/chunksize_task_stream.png" src="https://docs.coiled.io/_images/chunksize_task_stream.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;This is a snapshot from the Dask Dashboard. We can see that the task stream has many small dots
and a lot of white in between. The white spots represent periods where your workers are idle
because they don’t have anything to process. This is generally a sign that your resources aren’t
used efficiently. This workflow is clearly constrained by the scheduler.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="automatically-detecting-these-patterns-with-coiled"&gt;
&lt;h2&gt;Automatically detecting these patterns with Coiled&lt;/h2&gt;
&lt;p&gt;Coiled allows us to monitor clusters of our customers and to detect some of these patterns. This
is a tool we use to help customers to run their code faster and use their resources more
efficiently.&lt;/p&gt;
&lt;p&gt;We have two metrics that allow us to spot these types of clusters automatically. The first collects
the number of tasks on the scheduler that are queued. This metric notifies us when the number of
tasks reaches a certain threshold.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/chunksize_queued_tasks.png" src="https://docs.coiled.io/_images/chunksize_queued_tasks.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;That is not a foolproof system, large task graphs can have a lot of tasks while using an efficient
chunksize. We have another metric that checks CPU utilization on the scheduler periodically
and flags clusters that have long periods of time when the scheduler uses close to 100% CPU. This
is a sign that the scheduler constrains the workload on your cluster.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/chunksize_scheduler_cpu.png" src="https://docs.coiled.io/_images/chunksize_scheduler_cpu.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Again, this is not foolproof. There could be a number of reasons why scheduler utilization is so high. Using both
metrics together enables us to flag these clusters with a high degree of certainty.&lt;/p&gt;
&lt;p&gt;We will then reach out to our users if we detect this pattern on one of their clusters. One of our
engineers will work with them to make their workload more efficient and thus run their workflow
more efficiently.&lt;/p&gt;
&lt;p&gt;In this scenario, we would recommend increasing the chunksize of the individual chunks.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;da&lt;/span&gt;

&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This now runs in a couple seconds, rather than minutes.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/chunksize_before_after.png" src="https://docs.coiled.io/_images/chunksize_before_after.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;The scheduler is also pretty bored while processing our query. That’s exactly what we were aiming
for.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Coiled offers us a lot of insights into Dask clusters and their metrics. We can use this to identify
clusters that aren’t used as efficiently as possible. Having relatively small chunksizes can lead
to underutilizing your resources and thus spend more time than necessary on a give computation.&lt;/p&gt;
&lt;p&gt;Monitoring the number of queued tasks and scheduler CPU allows us to detect such clusters
automatically and help users adjust their code to make better use of their hardware.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled_observability_chunksize.html"/>
    <summary>Distributed computing is hard, distributed debugging is even harder. Dask tries to simplify this
process as much as possible. Coiled adds additional observability features for your Dask clusters
and processes them to help users understand their workflows better.</summary>
    <category term="coiled" label="coiled"/>
    <category term="dask" label="dask"/>
    <category term="observability" label="observability"/>
    <published>2023-09-19T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/parallel-coiled-functions.html</id>
    <title>Parallel Serverless Functions at Scale</title>
    <updated>2023-09-07T00:00:00+00:00</updated>
    <author>
      <name>James Bourbeau</name>
    </author>
    <content type="html">&lt;section id="parallel-serverless-functions-at-scale"&gt;

&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Data&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Local&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Coiled&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Speed Up&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;500 GB NetCDF&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;305 mins&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;4.7 mins&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;65x&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;The cloud offers amazing scale, but it can be difficult for Python data developers to use.
This post walks through how to use &lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;Coiled Functions&lt;/a&gt;
to run your existing code in parallel on the cloud with minimal code changes.&lt;/p&gt;
&lt;section id="example-processing-many-parquet-files-on-s3"&gt;
&lt;h2&gt;Example: Processing Many Parquet Files on S3&lt;/h2&gt;
&lt;p&gt;In the code snippet below we load &lt;a class="reference external" href="https://aws.amazon.com/marketplace/pp/prodview-okyonroqg5b2u"&gt;NYC Taxi Data&lt;/a&gt;
from AWS (data lives in region &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;us-east-1&lt;/span&gt;&lt;/code&gt;), process that data, and then upload the processed data back to AWS.
This type of data processing / cleaning is fairly common in data engineering / science workloads.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;s3fs&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;

&lt;span class="c1"&gt;# Collect files to process&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S3FileSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://nyc-tlc/trip data/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Data is in us-east-1&lt;/span&gt;
&lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yellow_tripdata_201&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;   &lt;span class="c1"&gt;# Data files from the 2010s&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yellow_tripdata_202&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;   &lt;span class="c1"&gt;# Data files from the 2020s&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Define our processing function&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tip_amount&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;outfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;s3://oss-shared-scratch/jrbourbeau/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outfile&lt;/span&gt;

&lt;span class="c1"&gt;# Process files&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;outfile&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Finished &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Done!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Running this code on my laptop (M1 MacBook Pro) takes a little over five hours.&lt;/p&gt;
&lt;p&gt;This is slow for a couple of reasons:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;We spend time transferring data from AWS to my laptop and back.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data files process sequentially, one at a time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s use Coiled Functions to address both these issues.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="run-functions-on-the-cloud"&gt;
&lt;h2&gt;Run Functions on the Cloud&lt;/h2&gt;
&lt;p&gt;To run the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function on the cloud, we lightly annotate it
with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator. Now &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; automatically runs on a VM in
AWS and returns the result locally.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# Same region as data&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Enough memory to process files&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Everything inside the function stays the same&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Note that we set &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;region=&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;vm_type=&amp;quot;m6i.xlarge&amp;quot;&lt;/span&gt;&lt;/code&gt; to make sure the VM the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function
runs on is in the same region as our data and has enough memory to process the data files
(&lt;a class="reference external" href="https://aws.amazon.com/ec2/instance-types/m6i/"&gt;m6i.xlarge instances&lt;/a&gt; have 16 GiB of memory).&lt;/p&gt;
&lt;p&gt;Adding the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator reduces the overall runtime to 35.9 minutes
(8.5x faster than the original version that ran locally). This is a nice performance improvement,
but we can do even better by scaling out and running in parallel.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="run-in-parallel"&gt;
&lt;h2&gt;Run in Parallel&lt;/h2&gt;
&lt;p&gt;By default &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorated functions will run in the cloud, serially, and return the results
back to your local machine. When we want to apply our function many times across different inputs,
we can use the Coiled Funtion &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.map&lt;/span&gt;&lt;/code&gt; method to run our function in parallel across inputs on multiple VMs.&lt;/p&gt;
&lt;p&gt;In this example, we swap out &lt;a class="reference external" href="https://docs.python.org/3/library/functions.html#map"&gt;Python’s builtin &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map&lt;/span&gt;&lt;/code&gt; function&lt;/a&gt;
for, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process.map&lt;/span&gt;&lt;/code&gt; (everything else stays the same):&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# results = map(process, filenames)  # Replace this&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# With this  &lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now the number of VMs used to run &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; calls will adaptively scale up and down depending on the workload
(see &lt;a class="reference external" href="https://docs.dask.org/en/latest/how-to/adaptive.html"&gt;Adaptive Deployments in the Dask docs&lt;/a&gt;
for more information), with each VM running a separate &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; call across inputs.&lt;/p&gt;
&lt;p&gt;Running the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function in parallel, we briefly scale up to 33 VMs, and the overall runtime
dropped to 4.7 minutes (65x faster than the original version that ran locally).&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Running a Coiled Function in the same region as your data" src="https://docs.coiled.io/_images/parallel-coiled-functions.png" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;VMs adaptively scaling up to process files quickly in parallel and then scaling down after all files are processed&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;In this post we demonstrated how to take existing Python code that runs locally and run it in parallel
on the cloud with Coiled Functions. With minimal code changes, we were able to improve performance by a factor
of 65x, without needing to maintain any additional cloud infrastructure. This makes developers’ lives a little
easier and lowers the bar for transitioning code to run on the cloud.&lt;/p&gt;
&lt;figure class="align-default" id="runtime"&gt;
&lt;img alt="Comparing code runtime between a laptop, single cloud VM, and multiple cloud VMs in parallel" src="https://docs.coiled.io/_images/parallel-coiled-functions-runtime.svg" style="width: 75%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Comparing code runtime between a laptop, single cloud VM, and multiple cloud VMs in parallel&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details class="sd-sphinx-override sd-dropdown sd-card sd-mb-3"&gt;
&lt;summary class="sd-summary-title sd-card-header"&gt;
&lt;span class="sd-summary-text"&gt;Full example&lt;/span&gt;&lt;span class="sd-summary-state-marker sd-summary-chevron-right"&gt;&lt;svg version="1.1" width="1.5em" height="1.5em" class="sd-octicon sd-octicon-chevron-right" viewBox="0 0 24 24" aria-hidden="true"&gt;&lt;path d="M8.72 18.78a.75.75 0 0 1 0-1.06L14.44 12 8.72 6.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l6.25 6.25a.75.75 0 0 1 0 1.06l-6.25 6.25a.75.75 0 0 1-1.06 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/summary&gt;&lt;div class="sd-summary-content sd-card-body docutils"&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;s3fs&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;

&lt;span class="c1"&gt;# Collect files to process&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S3FileSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://nyc-tlc/trip data/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Data is in us-east-1&lt;/span&gt;
&lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yellow_tripdata_201&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;   &lt;span class="c1"&gt;# Data files from the 2010s&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yellow_tripdata_202&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;   &lt;span class="c1"&gt;# Data files from the 2020s&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Define our processing function&lt;/span&gt;
&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# Same region as data&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Enough memory to process files&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tip_amount&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;outfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;s3://oss-shared-scratch/jrbourbeau/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outfile&lt;/span&gt;

&lt;span class="c1"&gt;# Process files&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;outfile&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Finished &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Done!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;p&gt;Want to run this example yourself?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Get started with Coiled for free at &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;coiled.io/start&lt;/a&gt;. This example runs comfortably within the free tier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy and paste the code snippet above.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/parallel-coiled-functions.html"/>
    <summary>DataComparing code runtime between a laptop, single cloud VM, and multiple cloud VMs in parallel</summary>
    <category term="coiled" label="coiled"/>
    <category term="functions" label="functions"/>
    <category term="labs" label="labs"/>
    <category term="parallel" label="parallel"/>
    <published>2023-09-07T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/coiled-xarray.html</id>
    <title>Processing a 250 TB dataset with Coiled, Dask, and Xarray</title>
    <updated>2023-09-05T00:00:00+00:00</updated>
    <author>
      <name>Sarah Johnson</name>
    </author>
    <content type="html">&lt;section id="processing-a-250-tb-dataset-with-coiled-dask-and-xarray"&gt;

&lt;p&gt;&lt;em&gt;We processed 250TB of geospatial cloud data in twenty minutes on the cloud with Xarray, Dask, and Coiled. We do this to demonstrate scale and to think about costs.&lt;/em&gt;&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Data&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Local&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Coiled&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Speed Up&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Cost&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;252 TB Zarr&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;500h+&lt;a class="footnote-reference brackets" href="#id2" id="id1" role="doc-noteref"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;1&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;6 mins&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;5,000x&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;$25&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Many people use &lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; with &lt;a class="reference external" href="https://docs.xarray.dev/en/stable/"&gt;Xarray&lt;/a&gt; to process large datasets for geospatial analyses, however, there is &lt;a class="reference external" href="https://medium.com/pangeo/dask-distributed-and-pangeo-better-performance-for-everyone-thanks-to-science-software-63f85310a36b"&gt;pain when operating at a large scale&lt;/a&gt;. This comes up in a few ways:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Large task graphs due to too-small chunks causing the scheduler to become a bottleneck&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running out of memory due to one of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Dask being too eager to load in data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rechunking&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unmanaged memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Too-large chunks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many of these problems are now fixed (e.g. &lt;a class="reference external" href="https://www.coiled.io/blog/reducing-dask-memory-usage"&gt;too-eager data loading&lt;/a&gt;), but not all. We wanted to run a big-but-simple Xarray problem to experience this pain ourselves and demonstrate feasibility. We’re also interested in optimizing costs, so we decided to play around with that too.&lt;/p&gt;
&lt;p&gt;In this example we’ll process 250 TB of data from the National Water Model, calculating the ground saturation level in every US county. The calculation will take ~20 minutes and cost ~$25.&lt;/p&gt;
&lt;section id="national-water-model-dataset"&gt;
&lt;h2&gt;National Water Model Dataset&lt;/h2&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://water.noaa.gov/about/nwm"&gt;National Water Model&lt;/a&gt; (NWM) is a complex hydrological modeling framework that simulates observed and forecasted streamflow across the US at a fine spatiotemporal scale. Many organizations use the NWM to make decisions around water management and to predict droughts and flooding. For more background, read &lt;a class="reference external" href="https://www.nytimes.com/interactive/2023/08/28/climate/groundwater-drying-climate-change.html"&gt;America Is Draining Its Groundwater Like There’s No Tomorrow from The New York Times&lt;/a&gt;, published just last week.&lt;/p&gt;
&lt;p&gt;In this example, we use a dataset from a 42-year retrospective simulation available on the &lt;a class="reference external" href="https://registry.opendata.aws/nwm-archive/"&gt;AWS Open Data Registry&lt;/a&gt;. These historical data models provide context for real-time estimations and as a historical baseline for training predictive models.&lt;/p&gt;
&lt;p&gt;We calculate the mean depth to soil saturation for each U.S. county:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Years: 1979-2020&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Temporal resolution: every 3 hours&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spatial resolution: 250m grid&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data size: 252 terabytes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="processing-one-year-of-data"&gt;
&lt;h2&gt;Processing one year of data&lt;/h2&gt;
&lt;p&gt;We first run this computation for one year (drawn from &lt;a class="reference external" href="https://xarray.dev/blog/flox"&gt;this example&lt;/a&gt; by Deepak Cherian). We process 6 TB of data, which takes about 8 minutes and costs $0.77 on a Coiled cluster with 40 workers, each with 16 GiB of memory. You can run this example yourself in &lt;a class="reference external" href="https://docs.coiled.io/user_guide/xarray.html"&gt;our documentation&lt;/a&gt;. This analysis produces the plot below:&lt;/p&gt;
&lt;figure class="align-default" id="us-county-heat-map"&gt;
&lt;img alt="County-level heat map of the continental US showing mean depth to soil saturation (in meters) in 2020." src="https://docs.coiled.io/_images/xarray-map.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Since this is only a subset of the full dataset, we thought it’d be fun to run this for the full 42 years rather than just 1.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="scaling-up-to-the-full-dataset"&gt;
&lt;h2&gt;Scaling up to the full dataset&lt;/h2&gt;
&lt;p&gt;We now run this computation on all 42 years of the 250 TB dataset. This takes 24 minutes and costs $25 using 200 workers each with 64 GiB of memory. You can see &lt;a class="reference external" href="https://github.com/coiled/examples/tree/main/national-water-model"&gt;the example here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This presents a few challenges at first:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Large task graph (~344 MB)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running out of memory (and spilling to disk)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unnecessary rechunking&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="align-default" id="cpu-memory-before"&gt;
&lt;img alt="Distribution of worker CPU and memory usage." src="https://docs.coiled.io/_images/xarray-sad-cluster.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Median CPU usage is low with a large range. Memory usage is high and consistently spilling to disk, which significantly slows runtime.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So we make a few adjustments.&lt;/p&gt;
&lt;section id="larger-chunk-sizes"&gt;
&lt;h3&gt;Larger chunk sizes&lt;/h3&gt;
&lt;p&gt;By default each chunk size is 200 MiB, resulting in a Dask graph of 1.3 million chunks. By increasing the chunksize to 840 MiB, we reduce the Dask graph to 320k chunks:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_zarr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;fsspec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_mapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://noaa-nwm-retrospective-2-1-zarr-pds/rtout.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;consolidated&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;896&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="memory-optimized-vms"&gt;
&lt;h3&gt;Memory-optimized VMs&lt;/h3&gt;
&lt;p&gt;We further improve memory usage with memory-optimized instance types with a higher memory to CPU ratio. Since peak RAM was ~20 GB, we use r7g.2xlarge:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scheduler_vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r7g.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r7g.2xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="launch-from-a-remote-vm"&gt;
&lt;h3&gt;Launch from a remote VM&lt;/h3&gt;
&lt;p&gt;Lastly, we use &lt;a class="reference external" href="https://docs.coiled.io/user_guide/cli-jobs.html"&gt;Coiled Run&lt;/a&gt; to submit this workflow from a VM in the cloud to speed up uploading the task graph to the scheduler.&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;coiled&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="n"&gt;us&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;east&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="n"&gt;m6g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlarge&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;xarray&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;water&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask uploads the task graph from the client to the scheduler, and if this were running locally we’d be relying on home internet speeds (which aren’t bad for me in Seattle, but this step helps speed things up a bit).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="results"&gt;
&lt;h3&gt;Results&lt;/h3&gt;
&lt;p&gt;This improves runtime and CPU- and memory-utilization quite a bit.&lt;/p&gt;
&lt;figure class="align-default" id="cpu-memory-after"&gt;
&lt;img alt="Distribution of worker CPU and memory usage." src="https://docs.coiled.io/_images/xarray-happier-cluster.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Median CPU usage is much higher at ~55%. Memory usage is consistently between 5 and 20 GB and we’re no longer spilling to disk.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="optimizing-costs"&gt;
&lt;h2&gt;Optimizing costs&lt;/h2&gt;
&lt;p&gt;In addition to trying to improve performance, we also want to optimize costs. There are a few ways we do this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Use &lt;strong&gt;spot&lt;/strong&gt; instances, which are excess capacity offered at a discount.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;strong&gt;ARM&lt;/strong&gt;-based instances, which are less expensive than their Intel-based equivalents, often with similar, or better, performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a &lt;strong&gt;region&lt;/strong&gt; close to our data to avoid &lt;a class="reference external" href="https://blog.cloudflare.com/aws-egregious-egress/"&gt;egress charges&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# choose region close to data&lt;/span&gt;
    &lt;span class="n"&gt;worker_vm_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r7g.2xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# ARM&lt;/span&gt;
    &lt;span class="n"&gt;compute_purchase_option&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt; &lt;span class="c1"&gt;# spot instances, replaced with on-demand&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We save $25 by using r6g ARM-based instance types rather than the x86-based equivalent. This is in part because runtime is nearly 10 minutes faster for the ARM-based instance types (others have &lt;a class="reference external" href="https://www.learnaws.org/2022/10/22/r6g-r6a-r6i/"&gt;similar findings&lt;/a&gt;).&lt;/p&gt;
&lt;figure class="align-default" id="optimize-cost"&gt;
&lt;img alt="" src="https://docs.coiled.io/_images/xarray-costs.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Comparing r6i x86-based vs. r6g and r7g ARM-based EC2 instance types.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We also use more spot instances, since r6g instances have a much lower frequency of spot interruption than r6i (&amp;lt;5% vs. &amp;gt;20%, respectively). Switching to the newer r7g ARM Graviton3 instances saves us an additional $9, mostly due to a faster runtime of ~6 minutes.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We ran a large Xarray workflow with three goals in mind:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Demonstrate feasibility&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Expose rough edges at scale&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explore cost dimension of these problems.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mission accomplished. We processed a quarter-petabyte for $25.&lt;/p&gt;
&lt;p&gt;We also found opportunities for optimization like large graph sizes, chunksize tuning, and hardware choices. We hope  this article helps point others in this situation in good directions. It’s also helping to inform the internal Dask team’s work prioritization for the coming couple of months.&lt;/p&gt;
&lt;p&gt;In the future we’d like to run similar calculations, but with a more complex workflow (like rechunking). We hope that that exposes even more opportunities for optimization.&lt;/p&gt;
&lt;p&gt;Want to try this experiment yourself?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/xarray.html"&gt;Download the notebook&lt;/a&gt; to run one year or &lt;a class="reference external" href="https://github.com/coiled/examples/tree/main/national-water-model"&gt;the script to run all 40 years&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Follow the &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;Coiled setup process&lt;/a&gt; (this runs comfortably within the Coiled free tier).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;hr class="footnotes docutils" /&gt;
&lt;aside class="footnote-list brackets"&gt;
&lt;aside class="footnote brackets" id="id2" role="doc-footnote"&gt;
&lt;span class="label"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;&lt;a role="doc-backlink" href="#id1"&gt;1&lt;/a&gt;&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;Assuming 1 Gbit/s internet connection speed as the bottleneck.&lt;/p&gt;
&lt;/aside&gt;
&lt;/aside&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled-xarray.html"/>
    <summary>We processed 250TB of geospatial cloud data in twenty minutes on the cloud with Xarray, Dask, and Coiled. We do this to demonstrate scale and to think about costs.County-level heat map of the continental US showing mean depth to soil saturation (in meters) in 2020.</summary>
    <category term="costs" label="costs"/>
    <category term="xarray" label="xarray"/>
    <published>2023-09-05T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/coiled-run-scikit-learn.html</id>
    <title>Reduce training time for CPU intensive models with scikit-learn and Coiled Functions</title>
    <updated>2023-09-01T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Sep 1, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="reduce-training-time-for-cpu-intensive-models-with-scikit-learn-and-coiled-functions"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/phofl"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/61934744?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Patrick Hoefler&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Code snippet adding coiled.function decorator to scikit-learn model training." src="https://docs.coiled.io/_images/coiled-functions-scikit-learn-snippet.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;You can use &lt;a class="reference external" href="https://docs.coiled.io/user_guide/labs/run.html"&gt;Coiled Run&lt;/a&gt;
and &lt;a class="reference external" href="https://docs.coiled.io/user_guide/labs/functions.html"&gt;Coiled Functions&lt;/a&gt;
for easily running scripts and functions on a VM in the cloud.&lt;/p&gt;
&lt;p&gt;In this post we’ll use Coiled Functions to train a RandomForestClassifer on a cloud-hosted
machine that has enough cores to speed up our training process. The model parallelizes very well,
which means that training time on my local machine is only bound by the number of cores available.
Offloading this step onto a machine with 128 cores will save a lot of time during the training
process.&lt;/p&gt;
&lt;section id="getting-started"&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;We will use random generated data for the purpose of this blog post. We will first train the model
locally, before we use Coiled Funtions to offload the calculation to AWS.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.ensemble&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RandomForestClassifier&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.datasets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_classification&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_classification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_samples&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RandomForestClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;clf&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We use 2 million samples and 30 features. This model takes around 12 minutes on my MacBook Air with
8 cores. The runtime can grow increasingly large if our dataset increases in size. We can speed up
that process through using a more powerful VM.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="using-coiled-functions-to-train-the-model-on-a-large-vm"&gt;
&lt;h2&gt;Using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;functions&lt;/span&gt;&lt;/code&gt; to train the model on a large VM&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/labs/run.html"&gt;Coiled Functions&lt;/a&gt; come into the equation since we need access to a machine with a lot of cores.
Coiled can connect to AWS or GCP and thus, use all resources that are available there.
Let’s train the same model as above, but this time on an EC2 instance with 128 cores.&lt;/p&gt;
&lt;p&gt;Adding a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator to the function that executes our training step is the only
modification we have to do. The decorator will tell Coiled that it should spin up a large VM on
AWS and train the model there, and then return the trained model to our local machine.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.ensemble&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RandomForestClassifier&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.datasets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_classification&lt;/span&gt;

&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c6i.32xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 128 cores, compute optimized&lt;/span&gt;
    &lt;span class="n"&gt;keepalive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;5 minutes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# keep alive to train more models if necessary&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_classification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_samples&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RandomForestClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;clf&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s execute the training step and return the model back to our local machine:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;RandomForestClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s take a look at the CPU utilization during the training step:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Plot from the Coiled dashboard showing the utilization of our Cores." src="https://docs.coiled.io/_images/coiled-functions-scikit-learn.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;There is no need to adjust the other functions. Coiled will run our function on a VM in the cloud with
enough resources.&lt;/p&gt;
&lt;p&gt;Let’s take a brief look at the arguments to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function()&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;vm_type&lt;/span&gt;&lt;/code&gt;: This specifies the type of &lt;a class="reference external" href="https://aws.amazon.com/ec2/instance-types/"&gt;EC2 instance&lt;/a&gt;.
We are looking for an instance that as many cores as possible to speed up our training step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;keepalive&lt;/span&gt;&lt;/code&gt;: Keeps the VM alive so that we can run multiple queries against the data in memory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function()&lt;/span&gt;&lt;/code&gt; will now start a VM in AWS with the specified EC2 instance. The VM is normally up
and running in 1-2 minutes. Coiled will scan our local environment and replicate the same
dependencies on this machine. We don’t have to specify an explicit Python environment. Inputs of
your function are serialized and sent to the VM
as well. Coiled will return our results back to our local machine.&lt;/p&gt;
&lt;p&gt;Coiled would normally shut down the VM immediately after the Python interpreter finishes. This is mostly to
reduce costs. We specified
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;keepalive=&amp;quot;5&lt;/span&gt; &lt;span class="pre"&gt;minutes&amp;quot;&lt;/span&gt;&lt;/code&gt; to keep the VM alive for a few minutes after our Python interpreter
finished. This ensures that new local runs can connect to the same VM avoiding
the boot time of up to 2 minutes; we call this a warm start.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;functions&lt;/span&gt;&lt;/code&gt; enables you to run queries on a machine with as many cores as you need.
This grants you access to computational resources that can be hard to replicate locally.&lt;/p&gt;
&lt;p&gt;For more resources:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Check out the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/labs/functions.html"&gt;documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Coiled functions to train a &lt;a class="reference internal" href="blog/coiled-run-pytorch.html"&gt;&lt;span class="doc"&gt;neural net on a powerful GPU&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Process &lt;a class="reference internal" href="blog/coiled-run-duckdb.html"&gt;&lt;span class="doc"&gt;hundreds of GBs of data with DuckDB&lt;/span&gt;&lt;/a&gt; using Coiled functions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled-run-scikit-learn.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Sep 1, 2023</summary>
    <category term="coiled" label="coiled"/>
    <category term="jobs" label="jobs"/>
    <category term="scikit-learn" label="scikit-learn"/>
    <published>2023-09-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/fine-performance-metrics.html</id>
    <title>Fine Performance Metrics and Spans</title>
    <updated>2023-08-23T00:00:00+00:00</updated>
    <author>
      <name>Guido Imperiale</name>
    </author>
    <content type="html">&lt;section id="fine-performance-metrics-and-spans"&gt;

&lt;p&gt;While it’s trivial to measure the end-to-end runtime of a Dask workload, the next
logical step - breaking down this time to understand if it could be faster - has
historically been a much more arduous task that required a lot of intuition and legwork,
for novice and expert users alike. We wanted to change that.&lt;/p&gt;
&lt;p&gt;There are a few questions that someone who wants to optimize the performance of
their Dask workflow typically asks:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;How much time am I losing to &lt;a class="reference internal" href="blog/gil-monitoring.html"&gt;&lt;span class="std std-doc"&gt;GIL contention&lt;/span&gt;&lt;/a&gt;?
Is it a problem?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How much time am I wasting in network transfer? Should I spend money increasing
network throughput, or focus on optimizing data locality? What’s the potential
benefit to end-to-end runtime?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I see a gray bar in the memory plot on the dashboard! This means that
&lt;a class="reference external" href="https://distributed.dask.org/en/stable/worker-memory.html"&gt;Dask is spilling data to disk&lt;/a&gt;
as there isn’t enough RAM available. But how much of the end-to-end runtime is
&lt;em&gt;actually&lt;/em&gt; wasted on it? Should I spend money increasing available RAM, or developer
time trying to reduce the memory footprint, or is it just not enough to bother?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The worker logs are saying that the event loop is getting blocked for many seconds at
a time! Should I worry? Again, how much time, &lt;em&gt;exactly&lt;/em&gt;, am I losing to this?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I just changed my algorithm, the version of Dask, or the version of other dependencies
and now the end-to-end runtime is slower! &lt;em&gt;Where&lt;/em&gt; am I spending more time than before?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I just doubled the size of my cluster but my workload is nowhere near twice as fast!
&lt;em&gt;Where&lt;/em&gt; did I accrue new inefficiency?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We recently introduced two complementary tools, &lt;strong&gt;Fine Performance Metrics&lt;/strong&gt; and
&lt;strong&gt;Spans&lt;/strong&gt;, to answer these questions. They are available to all Dask users.&lt;/p&gt;
&lt;iframe
    width="560"
    height="315"
    src="https://www.youtube.com/embed/eUnvdAoMibE?start=1529"
    data-hj-allow-iframe="true"
    style="margin: 0 auto 20px auto; display: block;"
    frameborder="0"
    allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
    allowfullscreen&gt;&lt;/iframe&gt;
&lt;section id="fine-performance-metrics"&gt;
&lt;h2&gt;Fine Performance Metrics&lt;/h2&gt;
&lt;p&gt;Since version 2023.7.0, Dask automatically collects metrics which break down the end-to-end
runtime of a computation by task and, within each task, by a series of &lt;em&gt;activities&lt;/em&gt;
taken to complete it.&lt;/p&gt;
&lt;p&gt;In order to observe these metrics, you can simply&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;run your workload end-to-end&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;open the Dask dashboard (default for a LocalCluster: &lt;a class="reference external" href="http://localhost:8787"&gt;http://localhost:8787&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;select &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;More...&lt;/span&gt;&lt;/code&gt; -&amp;gt; &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Fine&lt;/span&gt; &lt;span class="pre"&gt;Performance&lt;/span&gt; &lt;span class="pre"&gt;Metrics&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Alternatively, if you’re using JupyterLab and
&lt;a class="reference external" href="https://github.com/dask/dask-labextension"&gt;dask-labextension&lt;/a&gt;, you can drag the
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Fine&lt;/span&gt; &lt;span class="pre"&gt;Performance&lt;/span&gt; &lt;span class="pre"&gt;Metrics&lt;/span&gt;&lt;/code&gt; widget onto your Jupyter dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Populated Fine Performance Metrics dashboard" src="https://docs.coiled.io/_images/seconds-full.png" /&gt;&lt;/p&gt;
&lt;p&gt;The grand total of the time shown should roughly add up to the end-to-end runtime of
your workload, multiplied by the number of CPU cores on the cluster.&lt;/p&gt;
&lt;p&gt;The central panel (&lt;strong&gt;Task execution, by activity&lt;/strong&gt;) shows what &lt;em&gt;activities&lt;/em&gt; the cluster
spent its time on, cumulatively for all currently visible functions. The most important
activities are:&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;dl class="simple myst"&gt;
&lt;dt&gt;thread-cpu&lt;/dt&gt;&lt;dd&gt;&lt;p&gt;CPU time spent by tasks while running on workers. This is typically “good” time; in
other words it’s the same time you would have spent if you ran the workload serially
on a single CPU - but parallelized over however how many CPUs are available on your
cluster.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;thread-noncpu&lt;/dt&gt;&lt;dd&gt;&lt;p&gt;Difference between wall clock time and CPU time spent by tasks while running on
workers. This is typically I/O time, GPU time, CPU contention, or GIL contention. If
you observe large amounts of this in your overall workload, you probably want to break
it down by task and isolate those that are known to perform I/O or GPU activity.&lt;/p&gt;
&lt;p&gt;If you observe large amounts of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;thread-noncpu&lt;/span&gt;&lt;/code&gt; time in what should be pure CPU tasks,
the first thing you should investigate is &lt;a class="reference internal" href="blog/gil-monitoring.html"&gt;&lt;span class="std std-doc"&gt;GIL contention&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;idle&lt;/dt&gt;&lt;dd&gt;&lt;p&gt;Time during which a worker had a free thread, but nothing to run on it. This is
typically caused by the workload not being able to fully utilize all threads on the
cluster, network latency between scheduler and workers, or excessive CPU load on the
scheduler. This measure does not include time spent while the whole cluster was
completely idle.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;disk-read, disk-write, compress, decompress&lt;/dt&gt;&lt;dd&gt;&lt;p&gt;Time spent
&lt;a class="reference external" href="https://distributed.dask.org/en/stable/worker-memory.html"&gt;spilling data to disk&lt;/a&gt;
due to not having enough memory available.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;executor, offload, other&lt;/dt&gt;&lt;dd&gt;&lt;p&gt;This is overhead from the Dask code and should be typically negligible. However, it
can be inflated by GIL contention and by spill/unspill activity.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;The left panel (&lt;strong&gt;Task execution, by function&lt;/strong&gt;) shows the same information as the
central one, but broken down by function.&lt;/p&gt;
&lt;p&gt;The right panel (&lt;strong&gt;Send data, by activity&lt;/strong&gt;) shows network transfer time. Note that most
of it should be pipelined with task execution, so it may not have an impact. You should
worry about this only if you have a very large &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;idle&lt;/span&gt;&lt;/code&gt; time.&lt;/p&gt;
&lt;p&gt;There is a filter that allows you to show only selected tasks. In this sample
workflow, you can observe that most of the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;thread-noncpu&lt;/span&gt;&lt;/code&gt; time is concentrated - as
expected - in tasks that are known to be I/O heavy. Here they are singled out:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Fine Performance Metrics dashboard, just the I/O functions" src="https://docs.coiled.io/_images/seconds-IO.png" /&gt;&lt;/p&gt;
&lt;p&gt;And here’s all other functions that take a non-trivial amount of time:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Fine Performance Metrics dashboard, non-I/O functions" src="https://docs.coiled.io/_images/seconds-not-IO.png" /&gt;&lt;/p&gt;
&lt;p&gt;In this sample workflow, this plot tells us an important piece of information: why is
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;astype&lt;/span&gt;&lt;/code&gt;, which is a pure CPU function, spending so much time occupying workers’ threads
but without accruing any CPU time? The answer, almost certainly, is that it doesn’t
properly release the GIL.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;Fine Performance Metrics collect more than wall clock timings. We can change the
unit to bytes:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Fine Performance Metrics dashboard, unit=bytes" src="https://docs.coiled.io/_images/bytes.png" /&gt;&lt;/p&gt;
&lt;p&gt;The above gives us insights on spill-to-disk activity.
In this workflow, in 99% of the cases there was enough RAM to accommodate all the data
and it was unnecessary to retrieve it from disk; in other words we have &lt;em&gt;cache hits&lt;/em&gt; for
99% of the data, which means that we would not get any benefit if we increased RAM, but
we would likely start seeing slowdowns if we reduced it.&lt;/p&gt;
&lt;p&gt;This is corroborated by the time plots shown above, which show negligible time spent in
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;disk-read&lt;/span&gt;&lt;/code&gt; or &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;disk-write&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="spans"&gt;
&lt;h2&gt;Spans&lt;/h2&gt;
&lt;p&gt;It can be non-trivial to correlate a task to an instruction on the client.
The same function (e.g. a certain reduction) could also be applied in different sections
of the code, to very different data. This problem becomes more severe as the complexity
of the algorithm running on Dask increases.&lt;/p&gt;
&lt;p&gt;Alternatively - or in addition - to the above, you may be in a multitenancy setup, where
part of the load on the cluster was not caused by your client code, and you want to
concentrate only on your own problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spans&lt;/strong&gt; let you break down fine performance metrics into macro-blocks (e.g. data load,
preprocessing, etc.) by attaching meaningful tags to them.&lt;/p&gt;
&lt;p&gt;To do so, you can use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;span&lt;/span&gt;&lt;/code&gt; context manager inside the client code.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.config&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;

&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;optimization.fuse.active&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;# Don&amp;#39;t lose task annotations&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Alice&amp;#39;s workflow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data load&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ML preprocessing&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;preprocess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Model training&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;        
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The example will create the following spans on the scheduler:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Alice's&lt;/span&gt; &lt;span class="pre"&gt;workflow&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Alice's&lt;/span&gt; &lt;span class="pre"&gt;workflow&lt;/span&gt; &lt;span class="pre"&gt;/&lt;/span&gt; &lt;span class="pre"&gt;data&lt;/span&gt; &lt;span class="pre"&gt;load&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Alice's&lt;/span&gt; &lt;span class="pre"&gt;workflow&lt;/span&gt; &lt;span class="pre"&gt;/&lt;/span&gt; &lt;span class="pre"&gt;ML&lt;/span&gt; &lt;span class="pre"&gt;preprocessing&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Alice's&lt;/span&gt; &lt;span class="pre"&gt;workflow&lt;/span&gt; &lt;span class="pre"&gt;/&lt;/span&gt; &lt;span class="pre"&gt;Model&lt;/span&gt; &lt;span class="pre"&gt;training&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of the spans will be mapped to the tasks matching the segment of the Dask graph
that was defined inside its context manager. The parent span will be mapped to all tasks
of its children.&lt;/p&gt;
&lt;p&gt;Tags are arbitrary and nothing stops you from parameterizing them; for example&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;s workflow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Which may give you &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Alice's&lt;/span&gt; &lt;span class="pre"&gt;workflow&lt;/span&gt; &lt;span class="pre"&gt;/&lt;/span&gt; &lt;span class="pre"&gt;data&lt;/span&gt; &lt;span class="pre"&gt;load&lt;/span&gt;&lt;/code&gt;, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Bob's&lt;/span&gt; &lt;span class="pre"&gt;workflow&lt;/span&gt; &lt;span class="pre"&gt;/&lt;/span&gt; &lt;span class="pre"&gt;data&lt;/span&gt; &lt;span class="pre"&gt;load&lt;/span&gt;&lt;/code&gt;, etc.
This is useful for example if you want to observe either all the workload
submitted by Alice, while hiding Bob’s activity, or alternatively all the data loading
activity, regardless of who submitted it. You could use tags to store information on
what dataset you’re processing, etc.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="viewing-the-spans"&gt;
&lt;h2&gt;Viewing the spans&lt;/h2&gt;
&lt;p&gt;You can filter by span tags in the Fine Performance Metrics dashboard widget to
filter your workload:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Span selection in the Fine Performance Metrics dashboard" src="https://docs.coiled.io/_images/spans.png" style="width: 282.0px; height: 260.4px;" /&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="the-coiled-dashboard"&gt;
&lt;h2&gt;The Coiled dashboard&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://coiled.io"&gt;Coiled&lt;/a&gt; users enjoy full integration of both of these new features
into the Coiled Dashboard. In addition to Fine Performance Metrics, the Coiled Dashboard
also uses Spans to highlight portions of the workload plot and to tag code snippets.&lt;/p&gt;
&lt;p&gt;The functionality works out of the box; all you need to do is make sure you’re running
dask 2023.7.0 or later:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Coiled Dashboard" src="https://docs.coiled.io/_images/coiled-dashboard.png" /&gt;&lt;/p&gt;
&lt;p&gt;You can get started with Coiled for free at &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;coiled.io/start&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;These two new features should drastically improve users’ understanding of Dask
performance. Are they useful to you? Are they easy to use? Is there a killer feature
that you feel it’s missing? &lt;a class="reference external" href="https://dask.discourse.group/"&gt;Let us know!&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/fine-performance-metrics.html"/>
    <summary>While it’s trivial to measure the end-to-end runtime of a Dask workload, the next
logical step - breaking down this time to understand if it could be faster - has
historically been a much more arduous task that required a lot of intuition and legwork,
for novice and expert users alike. We wanted to change that.Populated Fine Performance Metrics dashboard</summary>
    <category term="coiled" label="coiled"/>
    <category term="dask" label="dask"/>
    <category term="distributed" label="distributed"/>
    <category term="monitoring" label="monitoring"/>
    <category term="observability" label="observability"/>
    <category term="performance" label="performance"/>
    <published>2023-08-23T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/data-proximate-computing.html</id>
    <title>Data-proximate Computing with Coiled Functions</title>
    <updated>2023-08-10T00:00:00+00:00</updated>
    <author>
      <name>James Bourbeau</name>
    </author>
    <content type="html">&lt;section id="data-proximate-computing-with-coiled-functions"&gt;

&lt;p&gt;&lt;em&gt;Coiled Functions make it easy to improve performance and reduce costs by moving your computations next to your cloud data.&lt;/em&gt;&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/data-proximate.png" src="https://docs.coiled.io/_images/data-proximate.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;It’s common practice for data scientists and researchers to analyze data on their local work computer (often a laptop). This works great for data that’s stored locally on their machine. However, increasingly data is moving to cloud storage system like AWS S3 and Google Cloud Storage. When analyzing cloud data on a laptop, users end up needing to transfer their data from cloud storage to their laptop, which is both slow and expensive.&lt;/p&gt;
&lt;p&gt;This post walks through how to use &lt;a class="reference external" href="https://docs.coiled.io/user_guide/labs/functions.html"&gt;Coiled Functions&lt;/a&gt; to move your data-intensive computations to the cloud, alongside your data, to reduce costs and improve performance.&lt;/p&gt;
&lt;section id="example-analyzing-nyc-taxi-data"&gt;
&lt;h2&gt;Example: Analyzing NYC taxi data&lt;/h2&gt;
&lt;p&gt;In the code snippet below we load &lt;a class="reference external" href="https://aws.amazon.com/marketplace/pp/prodview-okyonroqg5b2u"&gt;NYC Taxi Data&lt;/a&gt; from AWS (data lives in us-east-1), process that data, and then upload the processed version of the data back to AWS. This type of data processing / cleaning is fairly common in data science workloads.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;s3fs&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;

&lt;span class="n"&gt;t_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Get starting time&lt;/span&gt;

&lt;span class="c1"&gt;# Collect files to process&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S3FileSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://nyc-tlc/trip data/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Data is in us-east-1&lt;/span&gt;
&lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yellow_tripdata_2022&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Define our processing function&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tip_amount&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;outfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;s3://oss-shared-scratch/jrbourbeau/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Finished &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Process files&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Done!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Print total runtime&lt;/span&gt;
&lt;span class="n"&gt;t_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Took &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t_end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t_start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;0.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; minutes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Running this code on my laptop (M1 MacBook Pro) in Madison, WI takes 11.25 minutes.&lt;/p&gt;
&lt;p&gt;In this case, most of the time is spent transferring data from AWS to my laptop and then uploading the processed data on my laptop back to AWS.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="run-functions-on-the-cloud"&gt;
&lt;h2&gt;Run functions on the cloud&lt;/h2&gt;
&lt;p&gt;Given our data is already on AWS, let’s update our &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function to also run on the cloud in AWS. We can use Coiled Functions to decorate our existing &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function and have it automatically run on a VM in AWS.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# Same region as data&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.large&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Enough memory to process files&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Everything inside the function stays the same&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Note that we set &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;region=&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;vm_type=&amp;quot;m6i.large&amp;quot;&lt;/span&gt;&lt;/code&gt; to make sure the VM this &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;process&lt;/span&gt;&lt;/code&gt; function runs on is in the same region as our data and has enough memory to process the data files (&lt;a class="reference external" href="https://aws.amazon.com/ec2/instance-types/m6i/"&gt;m6i.large instances&lt;/a&gt; have 8 GiB of memory).&lt;/p&gt;
&lt;p&gt;When I add the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator and rerun the script, it takes 2.82 minutes to run (note that includes the startup time for the cloud VM), which is about a 4x overall speedup. We also never had to transfer data outside of AWS, so we didn’t need to worry about data transfer costs.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Moving computations that process cloud-based data to be co-located alongside the data can both improve code runtime performance and reduce data transfer costs.&lt;/p&gt;
&lt;p&gt;Coiled Functions is a simple API for moving existing Python functions to run on the cloud with minimal code changes. This makes developers’ lives a little easier and lowers the bar for transitioning code to run on the cloud.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="learn-more"&gt;
&lt;h2&gt;Learn more&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Check out the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/labs/functions.html"&gt;Coiled Functions documentation&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;See the &lt;a class="reference external" href="https://blog.coiled.io/blog/coiled-run-pytorch.html"&gt;“How to Train a Neural Network on a GPU in the Cloud with Coiled Functions”&lt;/a&gt; blogpost from Patrick Hoefler for another handy use case for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We highly value user feedback. If you try Coiled Functions, feel free to let us know about your experience in the &lt;a class="reference external" href="https://github.com/coiled/feedback/issues"&gt;coiled/feedback GitHub repo&lt;/a&gt; or by emailing &lt;a class="reference external" href="mailto:support&amp;#37;&amp;#52;&amp;#48;coiled&amp;#46;io"&gt;support&lt;span&gt;&amp;#64;&lt;/span&gt;coiled&lt;span&gt;&amp;#46;&lt;/span&gt;io&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get started with Coiled for free at &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;coiled.io/start&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/data-proximate-computing.html"/>
    <summary>Coiled Functions make it easy to improve performance and reduce costs by moving your computations next to your cloud data.</summary>
    <category term="coiled" label="coiled"/>
    <category term="functions" label="functions"/>
    <category term="labs" label="labs"/>
    <published>2023-08-10T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dagster-onlineapp.html</id>
    <title>Dask, Dagster, and Coiled for Production Analysis at OnlineApp</title>
    <updated>2023-08-09T00:00:00+00:00</updated>
    <author>
      <name>Lucas Gabriel</name>
    </author>
    <content type="html">&lt;section id="dask-dagster-and-coiled-for-production-analysis-at-onlineapp"&gt;

&lt;section id="tl-dr"&gt;
&lt;h2&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;We show a simple integration between Dagster and Dask+Coiled. We discuss how this made a common problem, processing a large set of files every month, really easy.&lt;/p&gt;
&lt;figure class="align-default" id="diagram"&gt;
&lt;img alt="Conceptual diagram showing how to use Dagster with Coiled and Dask." src="https://docs.coiled.io/_images/dagster-diagram.svg" style="width: 800px;" /&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="the-user-and-the-problem"&gt;
&lt;h2&gt;The User and the Problem&lt;/h2&gt;
&lt;p&gt;Olá 👋 I’m &lt;a class="reference external" href="https://www.linkedin.com/in/lucasgabrielb/"&gt;Lucas&lt;/a&gt;, the data science tech lead at &lt;a class="reference external" href="https://www.onlineapp.com.br/"&gt;OnlineApp&lt;/a&gt;, a B2B company serving the Brazilian market.&lt;/p&gt;
&lt;p&gt;Every month, the Brazilian government publishes a large set of CSV files with information on Brazilian companies that we use to better understand our market. We have lots of services that want to read this data, but before that’s possible we need to pre-process the data a bit:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Filter out some rows and columns that aren’t of interest to us;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clean some values in several columns;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Join against other internal datasets we have;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Convert to Parquet and store in our own S3 storage;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We have many other services in our company that then rely on this data.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="architecture"&gt;
&lt;h2&gt;Architecture&lt;/h2&gt;
&lt;p&gt;For regular jobs like this that run every month, we typically use &lt;a class="reference external" href="https://dagster.io/blog/introducing-dagster"&gt;Dagster&lt;/a&gt;. We like Dagster because it’s easy to use and feels natural. This runs on a VM in the cloud, so we have fast access to other cloud data (like these CSV files).&lt;/p&gt;
&lt;p&gt;Normally, we would use pandas for this kind of work, but this set of files is too large to fit into memory (around ≅150 million rows with several columns), so we switched to &lt;a class="reference external" href="https://docs.dask.org/en/stable/"&gt;Dask&lt;/a&gt;. Unfortunately, the dataset is also too large for our VM running Dagster, so we use &lt;a class="reference external" href="https://docs.coiled.io/user_guide/index.html"&gt;Coiled&lt;/a&gt; to ask for a cluster of larger machines, each with a fair amount of memory.&lt;/p&gt;
&lt;p&gt;In principle, we want to do something like this:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dagster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;


&lt;span class="nd"&gt;@op&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run_operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Processes all CSV files and saves the result as Parquet.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sa-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;64 GiB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;# any other configuration options you need&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;# custom code specific to our problem&lt;/span&gt;

    &lt;span class="n"&gt;df_result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This would work, but we wanted to make this more natural by integrating it with Dagster. We’re excited to share what we did. It wasn’t a lot of code and resulted in a smooth experience.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dagster-coiled-integration"&gt;
&lt;h2&gt;Dagster + Coiled Integration&lt;/h2&gt;
&lt;p&gt;Dagster has the concept of a Resource.  Resources typically model external components that Assets and Ops (short for “Operations”, like in the example above) interact with. For example, a resource might be a connection to a data warehouse like Snowflake or a service like a REST API.&lt;/p&gt;
&lt;p&gt;To make Dask+Coiled’s resources more reusable and also take advantage of all the extra functionality that Dagster provides when working with resources (more on that later), we transformed our Coiled cluster into a Dagster resource. This is straightforward to do:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;contextlib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;contextmanager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dagster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="nd"&gt;@resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;config_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;n_workers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;worker_memory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;dask_coiled_cluster_resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Yields a Dask cluster client.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dagster-dask-cluster-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;n_workers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;worker_memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;worker_memory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sa-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# use your preferred region&lt;/span&gt;
        &lt;span class="n"&gt;compute_purchase_option&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# save money with spot&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cluster created, dashboard link: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dashboard_link&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To use this resource we add it to our job definition:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dagster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;

&lt;span class="nd"&gt;@job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;resource_defs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;dask_cluster&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dask_coiled_cluster_resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;resources&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;dask_cluster&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;# set default values, but you can change these&lt;/span&gt;
                &lt;span class="c1"&gt;# in the Dagster UI or later in the code&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;config&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
                    &lt;span class="s2"&gt;&amp;quot;n_workers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;worker_memory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;64GiB&amp;quot;&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;my_job&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;My job DAGs definition.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_op&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And finally, in the operations that need to have a Dask cluster to run computations, we inform these dependencies as follows:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dagster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;


&lt;span class="nd"&gt;@op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# specify the resource key that you want to use in this op&lt;/span&gt;
    &lt;span class="n"&gt;required_resource_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dask_cluster&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;my_op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Run my computation with a Dask cluster.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="c1"&gt;# every computation inside this op will be executed with the Dask cluster&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="c1"&gt;# we can also access and use our cluster client directly&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dask_cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And that’s it, we now have full integration between Dagster, Dask and Coiled. Very easy to set up and maintain.&lt;/p&gt;
&lt;p&gt;As a bonus, this is how we define a &lt;a class="reference external" href="https://docs.dask.org/en/stable/deploying-python.html"&gt;local Dask cluster&lt;/a&gt; for development and testing purposes:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dagster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;contextlib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;contextmanager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;


&lt;span class="nd"&gt;@resource&lt;/span&gt;
&lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;dask_local_cluster_resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;Yields a local Dask cluster client.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cluster created, dashboard link: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dashboard_link&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="results"&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;We now have nodes in our Dagster graph that are annotated as Dask+Coiled nodes. When Dagster runs these, they fire up a temporary Dask cluster, do larger scale processing than our Dagster VM can handle, and then clean up.&lt;/p&gt;
&lt;p&gt;This image shows how the operation resource is listed in the Dagster UI:&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Screenshot from Dagster UI showing the Coiled operation resource." src="https://docs.coiled.io/_images/dagster-operation.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;(Click to enlarge)&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;And this is the Dagster Launchpad, here we can change several configurations before running our job, and one of those, thanks to defining our Coiled cluster as a resource, is choosing our cluster size, in a simple and parameterized way:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Screenshot from Dagster UI showing how to configure Coiled cluster options including number of workers and memory for each worker." src="https://docs.coiled.io/_images/dagster-launchpad.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;We’re now able to use our existing infrastructure to handle much larger datasets than we were able to use previously. Everything was very easy and cheap.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="in-conclusion"&gt;
&lt;h2&gt;In Conclusion&lt;/h2&gt;
&lt;p&gt;In this post we walked through an example of how to use Dagster and Dask+Coiled to orchestrate a recurring monthly data processing workflow. With Dask and Coiled, we were able to process the data in parallel using a cluster of VMs in the cloud. Dagster is able to take advantage of these cloud resources, enabling us to process much larger datasets than we otherwise could have.&lt;/p&gt;
&lt;p&gt;We hope others looking to scale their Dagster workflows to the cloud will find this guide useful. For more resources on getting started with Coiled, check out our &lt;a class="reference external" href="https://www.coiled.io/start"&gt;getting started guide&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dagster-onlineapp.html"/>
    <summary>We show a simple integration between Dagster and Dask+Coiled. We discuss how this made a common problem, processing a large set of files every month, really easy.Conceptual diagram showing how to use Dagster with Coiled and Dask.</summary>
    <category term="dagster" label="dagster"/>
    <published>2023-08-09T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/coiled-run-duckdb.html</id>
    <title>Process Hundreds of GB of Data with DuckDB in the Cloud</title>
    <updated>2023-08-07T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Aug 7, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="process-hundreds-of-gb-of-data-with-duckdb-in-the-cloud"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/phofl"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/61934744?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Patrick Hoefler&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Code snippet of using the coiled.function decorator to run a query with DuckDB on a large VM in the cloud." src="https://docs.coiled.io/_images/duckdb-coiled-functions.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;DuckDB is great tool for running efficient queries on large datasets. When you want cloud data proximity or need more RAM, Coiled makes it easy to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;run your Python function in the cloud&lt;/a&gt;. In this post we’ll use Coiled Functions API to process the 150 GB &lt;a class="reference external" href="https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page"&gt;Uber-Lyft dataset&lt;/a&gt;
on a single machine with &lt;a class="reference external" href="https://duckdb.org"&gt;DuckDB&lt;/a&gt;.&lt;/p&gt;
&lt;section id="query-parquet-files-with-duckdb"&gt;
&lt;h2&gt;Query Parquet files with DuckDB&lt;/h2&gt;
&lt;p&gt;We start with creating the SQL queries that we want to run against the data locally. We’ll use the DuckDB Python API.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Load data into memory so that subsequent queries are fast&lt;/span&gt;
  &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;    CREATE TABLE test AS &lt;/span&gt;
&lt;span class="sd"&gt;    SELECT * FROM read_parquet(&amp;quot;s3://coiled-datasets/uber-lyft-tlc/*&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;compute_percentage_of_tipped_rides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Run the actual query&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;       SELECT hvfhs_license_num, sum(tipped) / count(tipped)&lt;/span&gt;
&lt;span class="sd"&gt;        FROM (select &lt;/span&gt;
&lt;span class="sd"&gt;           *,  &lt;/span&gt;
&lt;span class="sd"&gt;           CASE WHEN tips &amp;gt; 0.0 then 1 ELSE 0 end as tipped&lt;/span&gt;
&lt;span class="sd"&gt;           from test) GROUP BY hvfhs_license_num&lt;/span&gt;
&lt;span class="sd"&gt;       &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;create_conn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;duckdb&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;duckdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;query_results&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_conn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;compute_percentage_of_tipped_rides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;These queries aren’t particularly fancy, they are meant to illustrate how we can process these
files. If we execute these queries as is, it would pull all the data onto our machine. The whole
dataset won’t fit in memory on most workstations, so let’s look at how Coiled can make this work. The loading
would take a long time, even if we had enough memory.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-do-i-query-data-stored-in-s3-using-duckdb"&gt;
&lt;h2&gt;How do I query data stored in S3 using DuckDB?&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;Coiled Functions&lt;/a&gt; come into the equation since we
need access to machines that have enough resources and are also close to our data.
Coiled can connect to AWS and thus, use all resources that are available there. These steps also work for data in Google Cloud Storage or Azure Blob Storage. We will go through the necessary steps execute these queries on a VM in the same region as our data
with enough memory available.&lt;/p&gt;
&lt;p&gt;The dataset is stored in the S3 bucket &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-datasets/uber-lyft-tlc&lt;/span&gt;&lt;/code&gt; in AWS region &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;us-east-2&lt;/span&gt;&lt;/code&gt;.
We’ll have to adapt our &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;create_conn&lt;/span&gt;&lt;/code&gt; function to use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;load_aws_credentials&lt;/span&gt;&lt;/code&gt; and set the region.
This &lt;a class="reference external" href="https://duckdb.org/docs/extensions/aws"&gt;DuckDB AWS extension&lt;/a&gt; builds on the httpfs extension to add authentication.
Coiled forwards your credentials as short-lived (refreshable) STS tokens, which is more secure than putting long-lived AWS access keys (which consist of access key ID and secret access key).&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;create_conn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;duckdb&lt;/span&gt;
    
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;duckdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CALL load_aws_credentials()&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The next step is adding the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator to the function that executes our queries.
This syntax will tell Coiled that it should spin up a large VM on AWS and run the query there, and then return the result locally.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.16xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 256 GB of RAM&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# region of our data&lt;/span&gt;
    &lt;span class="n"&gt;keepalive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;5 minutes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# keep alive to run multiple queries if necessary&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;query_results&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_conn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;compute_percentage_of_tipped_rides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s execute our queries and pull the results back to our local machine:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_results&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HV0005&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1912300216459857&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HV0003&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1498555901186066&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HV0004&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.09294857737045926&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HV0002&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.08440046492889111&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The data are now all in memory on our VM in the cloud:&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Plot from the Coiled dashboard showing 150 GB of memory usage on our VM." src="https://docs.coiled.io/_images/coiled_run_duckdb_memory.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;Memory usage goes up to 150 GB as we load the dataset on a big VM in the cloud.&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;There is no need to adjust the other functions. Coiled will run our query on a VM in the cloud with
enough resources and close to our data.&lt;/p&gt;
&lt;p&gt;Let’s take a brief look at the arguments to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function()&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;vm_type&lt;/span&gt;&lt;/code&gt;: This specifies the type of &lt;a class="reference external" href="https://aws.amazon.com/ec2/instance-types/"&gt;AWS EC2 instance&lt;/a&gt;.
We are looking for an instance that has enough memory to hold our data. This instance has 256GB,
so this should be sufficient.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;region&lt;/span&gt;&lt;/code&gt;: The region specifies the AWS region that our VM is started in. Our data are also in
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;keepalive&lt;/span&gt;&lt;/code&gt;: Keeps the VM alive so that we can run multiple queries against the data in memory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function()&lt;/span&gt;&lt;/code&gt; will now start a VM in AWS with the specified EC2 instance. The VM is normally up
and running in 1-2 minutes. Coiled will scan our local environment and replicate the same
dependencies on this machine. We don’t have to specify an explicit Python environment. Inputs of
your function are serialized and sent to the VM
as well. Coiled will return our results back to our local machine.&lt;/p&gt;
&lt;p&gt;Coiled would normally shut down the VM immediately after the Python interpreter finishes. This is mostly to
reduce costs. We specified
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;keepalive=&amp;quot;5&lt;/span&gt; &lt;span class="pre"&gt;minutes&amp;quot;&lt;/span&gt;&lt;/code&gt; to keep the VM alive for a few of minutes after our Python interpreter
finished. This ensures that new local runs can connect to the same VM avoiding
the boot time of up to 2 minutes; we call this a warm start.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;You can use Coiled serverless functions to run queries on a machine with as much memory as you want.
This grants you access to computational resources that can be very close to your data. Doing
data processing in the cloud becomes very easy with this functionality. Check out &lt;a class="reference internal" href="user_guide/functions.html#coiled-functions"&gt;&lt;span class="std std-ref"&gt;the docs&lt;/span&gt;&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;Want to run this example yourself? Get started with Coiled for free at &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;coiled.io/start&lt;/a&gt;. This example run comfortably within the free tier.&lt;/p&gt;
&lt;p&gt;Additional use cases:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/blog/coiled-functions-polars.html"&gt;Query data stored in S3 with Polars&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Process 5,000 files in parallel on the cloud. Run the example yourself in the &lt;a class="reference external" href="https://github.com/coiled/examples/blob/ea7855d41eba9ee6d938ef559ee1863d26d9a0fe/arxiv-matplotlib.ipynb"&gt;coiled/examples GitHub repo&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/blog/coiled-run-scikit-learn.html"&gt;Train a RandomForest classifier with scikit-learn&lt;/a&gt; on multiple cores in the cloud&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled-run-duckdb.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Aug 7, 2023</summary>
    <category term="coiled" label="coiled"/>
    <category term="duckdb" label="duckdb"/>
    <published>2023-08-07T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-expr-introduction.html</id>
    <title>High Level Query Optimization in Dask</title>
    <updated>2023-08-04T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;section id="high-level-query-optimization-in-dask"&gt;

&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/dask-expr-introduction-title.png" src="https://docs.coiled.io/_images/dask-expr-introduction-title.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;section id="introduction"&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dask DataFrame doesn’t currently optimize your code for you (like Spark or a SQL database would).
This means that users waste a lot of computation. Let’s look at a common example
which looks ok at first glance, but is actually pretty inefficient.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/uber-lyft-tlc/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# unnecessarily reads all rows and columns&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hvfhs_license_num&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;HV0003&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="c1"&gt;# could push the filter into the read parquet call&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numeric_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                &lt;span class="c1"&gt;# should read only necessary columns&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We can make this run much faster with a few simple steps:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/uber-lyft-tlc/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;==&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;HV0003&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tips&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Currently, Dask DataFrame wouldn’t optimize this for you, but a new effort that is built around
logical query planning in Dask DataFrame will do this for you. This article introduces some of
those changes that are developed in &lt;a class="reference external" href="https://github.com/dask-contrib/dask-expr"&gt;dask-expr&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can install and try &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask-expr&lt;/span&gt;&lt;/code&gt; with:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We are using the &lt;a class="reference external" href="https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page"&gt;NYC Taxi&lt;/a&gt;
dataset in this post.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-expressions"&gt;
&lt;h2&gt;Dask Expressions&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/dask-contrib/dask-expr"&gt;Dask expressions&lt;/a&gt; provides a logical query planning layer on
top of Dask DataFrames. Let’s look at our initial example and investigate how we can improve the efficiency
through a query optimization layer. As noted initially, there are a couple of things that aren’t ideal:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;We are reading all rows into memory instead of filtering while reading the parquet files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We are reading all columns into memory instead of only the columns that are necessary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We are applying the filter and the aggregation onto all columns instead of only &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The query optimization layer from &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask-expr&lt;/span&gt;&lt;/code&gt; can help us with that. It will look at this expression
and determine that not all rows are needed. An intermediate layer will transpile the filter into
a valid filter-expression for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/uber-lyft-tlc/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;==&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;HV0003&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numeric_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This still reads every column into memory and will compute the sum of every numeric column. The
next optimization step is to push the column selection into the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt; call as well.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/uber-lyft-tlc/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;==&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;HV0003&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numeric_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is a basic example that you could rewrite by hand. Use cases that are closer to real
workflows might potentially have hundreds of columns, which makes rewriting them very strenuous
if you need a non-trivial subset of them.&lt;/p&gt;
&lt;p&gt;Let’s take a look at how we can achieve this. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask-expr&lt;/span&gt;&lt;/code&gt; records the expression as given by the
user in an expression tree:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pprint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;Projection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tips&amp;#39;&lt;/span&gt;
  &lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;numeric_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;ReadParquet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3://coiled-datasets/uber-lyft-tlc/&amp;#39;&lt;/span&gt;
      &lt;span class="n"&gt;EQ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HV0003&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;Projection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hvfhs_license_num&amp;#39;&lt;/span&gt;
          &lt;span class="n"&gt;ReadParquet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3://coiled-datasets/uber-lyft-tlc/&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This tree represents the expression as is. We can observe that we would read the whole dataset into
memory before we apply the projections and filters. One observation of note: It seems like we
are reading the dataset twice, but Dask is able to fuse tasks that are doing the same to avoid
computing these things twice. Let’s reorder the expression to make it more efficient:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;simplify&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pprint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;numeric_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
  &lt;span class="n"&gt;ReadParquet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3://coiled-datasets/uber-lyft-tlc/&amp;#39;&lt;/span&gt; 
               &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tips&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
               &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hvfhs_license_num&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;==&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;HV0003&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; 

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This looks quite a bit simpler. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask-expr&lt;/span&gt;&lt;/code&gt; reordered the query and pushed the filter and the column
projection into the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt; call. We were able to remove quite a few steps from our expression
tree and make the remaining expressions more efficient as well. This represents the steps that
we did manually in the beginning. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask-expr&lt;/span&gt;&lt;/code&gt; performs these steps for arbitrary many columns without
increasing the burden on the developers.&lt;/p&gt;
&lt;p&gt;These are only the two most common and easy to illustrate optimization techniques from &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask-expr&lt;/span&gt;&lt;/code&gt;.
Some other useful optimizations are already available:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;len(...)&lt;/span&gt;&lt;/code&gt; will only use the Index to compute the length; additionally we can ignore many operations
that won’t change the shape of a DataFrame, like a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;replace&lt;/span&gt;&lt;/code&gt; call.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;set_index&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;sort_values&lt;/span&gt;&lt;/code&gt; won’t eagerly trigger computations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better informed selection of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;merge&lt;/span&gt;&lt;/code&gt; algorithms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;…&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are still adding more optimization techniques to make Dask DataFrame queries more efficient.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="try-it-out"&gt;
&lt;h2&gt;Try it out&lt;/h2&gt;
&lt;p&gt;The project is in a state where interested users should try it out. We published a couple of
releases. The API covers a big chunk of the Dask DataFrame API, and we keep adding more.
We have already observed very impressive performance improvements for workflows that would benefit
from query optimization. Memory usage is down for these workflows as well.&lt;/p&gt;
&lt;p&gt;We are very much looking for feedback and potential avenues to improve the library. Please give it
a shot and share your experience with us.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask-expr&lt;/span&gt;&lt;/code&gt; is not integrated into the main Dask DataFrame implementation yet. You can install it
with:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The API is very similar to what Dask DataFrame provides. It exposes mostly the same methods as
Dask DataFrame does. You can use the same methods in most cases.&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_expr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can find a list of supported operations in the
&lt;a class="reference external" href="https://github.com/dask-contrib/dask-expr#api-coverage"&gt;Readme&lt;/a&gt;. This project is still very much
in progress. The API might change without warning. We are aiming for weekly releases to push new
features out as fast as possible.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="why-are-we-adding-this-now"&gt;
&lt;h2&gt;Why are we adding this now?&lt;/h2&gt;
&lt;p&gt;Historically, Dask focused on flexibility and smart scheduling instead of query optimization.
The distributed scheduler built into Dask uses sophisticated algorithms to ensure ideal scheduling
of individual tasks. It tries to ensure that your resources are utilized as efficient as possible.
The graph construction process enables Dask users to build very
flexible and complicated graphs that reach beyond SQL operations. The flexibility that is provided
by the &lt;a class="reference external" href="https://docs.dask.org/en/latest/futures.html"&gt;Dask futures API&lt;/a&gt; requires very intelligent
algorithms, but it enables users to build highly sophisticated graphs. The following picture shows
the graph for a credit risk model:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/graph_credit_risk_model.png" src="https://docs.coiled.io/_images/graph_credit_risk_model.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;The nature of the powerful scheduler and the physical optimizations enables us to build very
complicated programs that will then run efficiently. Unfortunately, the nature of these optimizations
does not enable us to avoid scheduling work that is not necessary. This is where the current effort
to build high level query optimization into Dask comes in.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Dask comes with a very smart distributed scheduler but without much logical query planning. This
is something we are rectifying now through building a high level query optimizer into Dask
DataFrame. We expect to improve performance and reduce memory usage for an average Dask workflow.&lt;/p&gt;
&lt;p&gt;This API is read for interested users to play around with. It covers a good chunk of the DataFrame
API. The library is under active development, we expect to add many more interesting things over
the coming weeks and months.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-expr-introduction.html"/>
    <summary>Dask DataFrame doesn’t currently optimize your code for you (like Spark or a SQL database would).
This means that users waste a lot of computation. Let’s look at a common example
which looks ok at first glance, but is actually pretty inefficient.</summary>
    <category term="dask" label="dask"/>
    <category term="performance" label="performance"/>
    <category term="queryoptimizer" label="query optimizer"/>
    <published>2023-08-04T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/easy-serverless.html</id>
    <title>Easy Heavyweight Serverless Functions</title>
    <updated>2023-08-01T00:00:00+00:00</updated>
    <author>
      <name>Matthew Rocklin</name>
    </author>
    <content type="html">&lt;section id="easy-heavyweight-serverless-functions"&gt;

&lt;section id="tl-dr"&gt;
&lt;h2&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;What is the easiest way to run Python code in the cloud, especially for compute jobs?&lt;/p&gt;
&lt;p&gt;We briefly compare common options (Lambda, EC2, Fargate, Modal) and then pitch a new contender: Coiled Run.&lt;/p&gt;
&lt;div class="sd-container-fluid sd-sphinx-override sd-mb-4 docutils"&gt;
&lt;div class="sd-row sd-row-cols-1 sd-row-cols-xs-1 sd-row-cols-sm-1 sd-row-cols-md-2 sd-row-cols-lg-2 sd-g-0 sd-g-xs-0 sd-g-sm-0 sd-g-md-0 sd-g-lg-0 docutils"&gt;
&lt;div class="sd-col sd-d-flex-column sd-p-0 docutils"&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/easy-serverless-gpu.svg" src="https://docs.coiled.io/_images/easy-serverless-gpu.svg" style="width: 600px; height: 200px;" /&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-column sd-p-0 docutils"&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/easy-serverless-function.svg" src="https://docs.coiled.io/_images/easy-serverless-function.svg" style="width: 600px; height: 200px;" /&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="motivation-to-run-python-code-in-the-cloud"&gt;
&lt;h2&gt;Motivation to Run Python Code in the Cloud&lt;/h2&gt;
&lt;p&gt;We want to run jobs in the cloud for a few common reasons:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data proximity:&lt;/strong&gt; Our data lives in the cloud, and we want to process it where it lives to avoid costly egress charges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speed and Scale:&lt;/strong&gt; We have many things to process and don’t want to wait&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hardware access:&lt;/strong&gt; We want GPUs or big-memory machines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always-on Coordination:&lt;/strong&gt; We want to respond to actions in the real world,
like in a web server or in response to data arriving&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This post focuses on the first three use cases which require more heavyweight computing.  As an example, maybe we want to load, manipulate, and store many files living in cloud storage:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;                         &lt;span class="c1"&gt;# Expensive function&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://my-bucket/*.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Lots of files&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                     &lt;span class="c1"&gt;# Run many times&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s see what kinds of options we have.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="cloud-infrastructure-to-run-single-jobs"&gt;
&lt;h2&gt;Cloud Infrastructure to Run Single Jobs&lt;/h2&gt;
&lt;p&gt;In general we have two classes of options:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Big VMs (EC2, ECS)&lt;/strong&gt; which give you a big VM&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hardware flexibility: you can use any hardware available in the cloud&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cheap: typically $0.05 per CPU-hour&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Annoying to set up / inaccessible&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Takes at least a minute to turn on (but more likely an hour of human time)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Serverless technologies (AWS Lambda)&lt;/strong&gt; which run a function for you, hiding the VM&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Much easier to use&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turns on quickly (seconds)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Easier, but not yet easy.  These are still kinda annoying to set up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hardware limitations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Expensive: typically $0.20 per CPU-hour, can’t use Spot&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most people I see using cloud for ad-hoc computing choose VMs.  They spin up a
big instance, use it for a while, and then spin it down (hopefully).  People
who start with serverless tend to move over to VMs, usually due to some
flexibility limitation. AWS Lambda is often great, except if any of the
following are true:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;You need a big machine&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want accelerated hardware like GPUs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have a large software environment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your functions last tens of minutes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your functions last more than a minute, and you’re cost conscious&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="third-party-orchestrators"&gt;
&lt;h2&gt;Third party orchestrators&lt;/h2&gt;
&lt;p&gt;Products like Argo, Prefect, Airflow, Coiled+Dask (us!), Kubeflow,
Anyscale+Ray, Dagster and more will also happily run jobs for you, but this is
as part of a broader abstraction.  If you just want to run a single job,
they’re all overkill.&lt;/p&gt;
&lt;p&gt;The best product experience I’ve seen in this space is Modal Labs.  It’s pretty
slick.  Like the others though, it still has non-trivial ceremony.  It also has
some other limitations like you have to run in their cloud accounts (so data
privacy is a concern), it’s only on one region (so data egress costs can be
non-trivial) and they cost $0.20 per CPU-hour (which is a 4x markup).
Generally a great product experience though, and we hear only positive things.
I encourage people to check them out.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="coiled-run"&gt;
&lt;h2&gt;Coiled Run&lt;/h2&gt;
&lt;p&gt;So now we get to our new solution.  There are two APIs, they look like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CLI:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled run echo &amp;quot;Hello, world&amp;quot;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Python:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;512 GiB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello, world!&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Coiled was originally built to deploy distributed Dask clusters in the cloud, a
harder problem than serverless execution.  However, the infrastructure we built
for Dask transfers over surprisingly well.  We started this effort because we
saw Coiled users spinning up single-node Dask clusters (rather than
hundred-node clusters) to run a single task (rather than millions).  We
took the hint and built these new APIs on top of the existing
infrastructure.  Here you go:&lt;/p&gt;
&lt;/section&gt;
&lt;section id="run-a-script"&gt;
&lt;h2&gt;Run a script&lt;/h2&gt;
&lt;p&gt;The easiest thing is to run a standalone script in the cloud.  Here we run a script in Frankfurt on a machine with 128 cores.&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled run python myscript.py --region eu-central-1 --vm-type m6i.32xlarge
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Optionally specify useful keywords:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled run python myscript.py \
    --region us-east-1 \
    --vm-type m6i.32xlarge \
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled run python pytorch-train.py \
    --region us-west-1 \
    –-vm-type g5.2xlarge \  # GPU instance type
    --container ...
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Doesn’t even need to be a Python script&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ coiled run echo &amp;quot;Hello, world&amp;quot;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled creates a VM, synchronizes all of your local software packages, cloud credentials, files, etc.., runs your script, and then shuts down the VM.&lt;/p&gt;
&lt;p&gt;Here are some more fully worked examples:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/examples/blob/main/run/nyc.py"&gt;Process parquet data on S3&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/gpu-job.html"&gt;&lt;span class="doc"&gt;Run a script on a GPU&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="run-a-python-function"&gt;
&lt;h2&gt;Run a Python function&lt;/h2&gt;
&lt;p&gt;Alternatively you can run your Python code locally, but decorate individual functions to run remotely, as with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;my_function&lt;/span&gt;&lt;/code&gt; below.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;256 GiB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here are some more fully worked examples.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="blog/parallel-coiled-functions.html"&gt;&lt;span class="doc"&gt;Process many Parquet files on S3&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/pytorch.html"&gt;&lt;span class="doc"&gt;Train a PyTorch model with a GPU&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="pros-and-cons"&gt;
&lt;h2&gt;Pros and Cons&lt;/h2&gt;
&lt;p&gt;These APIs were accidental, but turned out to be easy and efficient cloud functions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Easiest API we can find&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any hardware / architecture (uses EC2 instances)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runs securely in your account&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runs in any region, close to your data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Base cloud cost (uses EC2 instances)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Minute-long start times for your first function call (but subsequent
calls have only millisecond delays)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Third-party service (requires separate sign-up/accounting)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This essentially offers the flexibility and cost efficiency of EC2 instances, but without the setup pain.&lt;/p&gt;
&lt;p&gt;We think that the UX is smooth, mostly inheriting from other features we built for Dask clusters like the following:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automatic package synchronization:&lt;/strong&gt; we transmit the same package versions you have locally to your cloud workers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Credential forwarding:&lt;/strong&gt; we securely forward your AWS permissions to remote workers (safely using AWS STS)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cost/user management controls:&lt;/strong&gt; we help you constrain costs on a per-user basis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;General progress / UX polish:&lt;/strong&gt; things just look nice.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section id="opportunity"&gt;
&lt;h2&gt;Opportunity&lt;/h2&gt;
&lt;p&gt;We’re excited about Coiled functions because they are …&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy&lt;/strong&gt; to use for Python developers unfamiliar with cloud computing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flexible&lt;/strong&gt; with their ability to use any hardware in any region&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cheap&lt;/strong&gt; being backed by foundational technologies like EC2 Fleets.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dask is super-powerful (far more powerful than what’s described in this post) but there are lots of folks for whom Dask is overkill.  We hope that these simpler interfaces are a good onramp to data-proximate and parallel cloud computing.&lt;/p&gt;
&lt;p&gt;Interested in playing?  The following should work if you have cloud credentials:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
coiled&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# this will connect Coiled to your cloud account&lt;/span&gt;
coiled&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, world&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For more on how to get started, see&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;Coiled Start&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;Coiled Functions Documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/cli-jobs.html"&gt;Coiled Run Documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/easy-serverless.html"/>
    <summary>What is the easiest way to run Python code in the cloud, especially for compute jobs?</summary>
    <published>2023-08-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/coiled-run-pytorch.html</id>
    <title>How to Train a Neural Network on a GPU in the Cloud with coiled functions</title>
    <updated>2023-07-24T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jul 24, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="how-to-train-a-neural-network-on-a-gpu-in-the-cloud-with-coiled-functions"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/phofl"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/61934744?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Patrick Hoefler&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/coiled-run-pytorch.png" src="https://docs.coiled.io/_images/coiled-run-pytorch.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;We recently pushed out two new and experimental features &lt;a class="reference external" href="https://docs.coiled.io/user_guide/cli-jobs.html"&gt;coiled run&lt;/a&gt;
and &lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;coiled functions&lt;/a&gt;
which is a deviation of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;run&lt;/span&gt;&lt;/code&gt;. We are excited about both of them because they:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Allow users to scale up any given program on any hardware in the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make GPUs easily accessible without going through the pains of setting up environments in the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post will provide an example how to utilize &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;functions&lt;/span&gt;&lt;/code&gt; to seamlessly train a
&lt;strong&gt;neural network&lt;/strong&gt; on a GPU that is hosted in the cloud.&lt;/p&gt;
&lt;section id="getting-started"&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;We have to start with creating a model on our local machine before we can start worrying about
training it. This blog post is not dedicated to figuring out a fancy model, we will utilize the
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Net&lt;/span&gt;&lt;/code&gt; model that is given in the &lt;a class="reference external" href="https://pytorch.org/tutorials/beginner/introyt/introyt1_tutorial.html#training-your-pytorch-model"&gt;PyTorch tutorials&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can simply add the model definition to our python script. There is no need to do anything
different. Similarly, we will use the transformer that is given there as well.&lt;/p&gt;
&lt;p&gt;The next step is creating a function that we can use to train the model:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Net&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;trainset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torchvision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datasets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CIFAR10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trainloader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;trainset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;criterion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CrossEntropyLoss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SGD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;momentum&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trainloader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;criterion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We can now train our model:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This will train our model on the CPU of our local machine. The training is reasonably quick for such
a small model, but training time will grow exponentially as our model gets larger or if we are
using a significantly bigger dataset. Training the model on the CPU won’t be sufficient anymore.
Additionally, there are a lot of machines out there that don’t have GPUs built into them. For example, I’m using a MacBook Pro with an M2 CPU, which means my machine doesn’t support &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;cuda&lt;/span&gt;&lt;/code&gt;.
Consequently, we need a different solution to make these steps accessible for folks who don’t have
access to a local GPU.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="using-coiled-functions-to-train-the-model-on-a-cloud-hosted-gpu"&gt;
&lt;h2&gt;Using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;functions&lt;/span&gt;&lt;/code&gt; to train the model on a cloud-hosted GPU&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/functions.html"&gt;Coiled functions&lt;/a&gt; come into the equation if you
need access to resources that aren’t available
locally. Coiled can connect to AWS or GCP and thus, use all resources that are available there.
We will go through the necessary steps to train our model on a GPU that is hosted on AWS instead
of our local CPU.&lt;/p&gt;
&lt;p&gt;The first step includes defining a Python environment to run our computations. We simply include
PyTorch, CUDA, and Coiled, that’s it. Generally, you should use the same Python version that is
installed locally.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_software_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pytorch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;conda&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;channels&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pytorch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;nvidia&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;conda-forge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;defaults&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;python=3.11&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;coiled&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;pytorch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;torchvision&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;torchaudio&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;cudatoolkit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;pynvml&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;gpu_enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled will create a Python environment for you. This step is only necessary when running your
script for the first time. The resulting environment is cached which makes further runs more
efficient.&lt;/p&gt;
&lt;p&gt;The next step is adding the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;#64;coiled.function&lt;/span&gt;&lt;/code&gt; decorator to our training
function that tells our program we want to execute said function on a machine in the cloud.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;g5.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# GPU instance type&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;software&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pytorch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Additionally, we have to tell PyTorch that we want to train the model on the GPU.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;
    &lt;span class="c1"&gt;# tell PyTorch to use the GPU&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuda:0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Putting this all together:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch.nn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;nn&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch.nn.functional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;F&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch.optim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;optim&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torchvision&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torchvision.transforms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;transforms&lt;/span&gt;

&lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToTensor&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="n"&gt;transforms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Normalize&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mf"&gt;0.4914&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.4822&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.4465&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2470&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2435&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2616&lt;/span&gt;&lt;span class="p"&gt;))])&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Net&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Net&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conv1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conv2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxPool2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conv2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conv2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conv1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conv2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;

&lt;span class="nd"&gt;@coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;g5.xlarge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;software&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pytorch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuda:0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Net&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;trainset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torchvision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datasets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CIFAR10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trainloader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;trainset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;criterion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CrossEntropyLoss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SGD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;momentum&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trainloader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;criterion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s take a brief look at the arguments to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function()&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;vm_type&lt;/span&gt;&lt;/code&gt;: This specifies the type of &lt;a class="reference external" href="https://aws.amazon.com/ec2/instance-types/"&gt;EC2 instance&lt;/a&gt;.
We are looking for an instance that has a GPU attached to it. The G5 family has Nvidia GPUs
attached to it. The smallest version is sufficient for our example, but you can choose instances
with up to 8 GPUs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;region&lt;/span&gt;&lt;/code&gt;: The region specifies the AWS region that our VM is started in. We observed that &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;quot;us-west-2&amp;quot;&lt;/span&gt;&lt;/code&gt;
is a region where GPUs are easier to get.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;software&lt;/span&gt;&lt;/code&gt;: This specifies the coiled software environment that is installed. This corresponds
to the environment that we previously created.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled.function()&lt;/span&gt;&lt;/code&gt; will now start a VM in AWS with the specified EC2 instance. The VM is normally up
and running in 1-2 minutes. The previously specified Python environment is installed automatically.
Coiled executes the function on said VM. Inputs of your function are serialized and sent to the VM
as well. It makes sense to download the training data on the VM to reduce time that is spent sending
data to AWS. The function returns our model back to our local machine so that we can use it locally
without depending on AWS.&lt;/p&gt;
&lt;p&gt;Coiled will shut down the VM immediately after the Python interpreter finishes. This is mostly to
reduce costs. You can specify a certain amount of time that the VM is kept alive through
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;keepalive=&amp;quot;5&lt;/span&gt; &lt;span class="pre"&gt;minutes&amp;quot;&lt;/span&gt;&lt;/code&gt;. This ensures that new local runs can connect to the same VM avoiding
the boot time of up to 2 minutes; we call this a warm start.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;functions&lt;/span&gt;&lt;/code&gt; enables you to seamlessly port the training process for a neural network
from your local machine to AWS or GCP. This grants everyone access to multiple GPUs or huge
machines independent of the local machine that is actually used. Training a neural network on a
GPU becomes as easy as adding a decorator to the training function.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled-run-pytorch.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jul 24, 2023</summary>
    <category term="coiled" label="coiled"/>
    <category term="gpu" label="gpu"/>
    <category term="machinelearning" label="machine learning"/>
    <published>2023-07-24T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-upstream-performance-tests.html</id>
    <title>Dask performance benchmarking put to the test: Fixing a pandas bottleneck</title>
    <updated>2023-06-23T00:00:00+00:00</updated>
    <author>
      <name>Hendrik Makait</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jun 23, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="dask-performance-benchmarking-put-to-the-test-fixing-a-pandas-bottleneck"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Patrick Hoefler, Hendrik Makait&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Getting notified of a significant performance regression the day before release sucks, but quickly identifying and resolving it feels great!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We were getting set up at our booth at JupyterCon 2023 when we received a notification:
An engineer on our team had spotted a significant performance regression in Dask.
With an impact of 40% increased runtime, it blocked the release planned for the next day!&lt;/p&gt;
&lt;figure class="align-default" id="performance-regression"&gt;
&lt;img alt="The performance benchmarking shows a 40% increase in runtime" src="https://docs.coiled.io/_images/performance-regression.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Luckily, the other attendees still focused on coffee and breakfast, so we commandeered an abandoned table next to our booth and got to work.&lt;/p&gt;
&lt;section id="performance-testing-at-coiled"&gt;
&lt;h2&gt;Performance testing at Coiled&lt;/h2&gt;
&lt;p&gt;The performance problem &lt;a class="reference external" href="https://github.com/coiled/benchmarks/issues/840"&gt;had been flagged&lt;/a&gt; by the automated performance testing for Dask that we developed at &lt;a class="reference external" href="https://www.coiled.io"&gt;Coiled&lt;/a&gt;.
If you have not read Guido Imperiale’s &lt;a class="reference external" href="https://blog.coiled.io/blog/performance-testing.html"&gt;blog post&lt;/a&gt; on our approach to performance testing, here is a summary:
With &lt;a class="reference external" href="https://github.com/coiled/benchmarks"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled/benchmarks&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;, we created a benchmark suite that contains a variety of common workloads and operations with Dask, including standardized ones like the &lt;a class="reference external" href="https://github.com/h2oai/db-benchmark"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;h2oai/db-benchmark&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It also contains tooling that allows us to do two things:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Automatically &lt;a class="reference external" href="https://blog.coiled.io/blog/performance-testing.html#nightly-tests"&gt;detect performance regressions&lt;/a&gt; in Dask and raise them as issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://blog.coiled.io/blog/performance-testing.html#a-b-tests"&gt;Run A/B tests&lt;/a&gt; to assess the performance impact of different versions of Dask, upstream packages, or cluster configurations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the former started this journey, the latter will also come in handy soon.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="identifying-the-problem"&gt;
&lt;h2&gt;Identifying the problem&lt;/h2&gt;
&lt;p&gt;Our automated regression testing had alerted us that &lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/895a13db09eb3172155e7b1260a5698f2284f5b7/tests/benchmarks/test_h2o.py#L140-L151"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;test_h2o.py::test_q8&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; had experienced &lt;a class="reference external" href="https://github.com/dask/community/issues/322#issuecomment-1542560550"&gt;a significant increase&lt;/a&gt; in runtime across all data sizes and file formats.
From the &lt;a class="reference external" href="https://benchmarks.coiled.io"&gt;historical report&lt;/a&gt; of our benchmarking suite, we could see that &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask/dask&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask/distributed&lt;/span&gt;&lt;/code&gt; were unlikely to be the culprit:
Nothing had changed on &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask/dask&lt;/span&gt;&lt;/code&gt; when the performance started to degrade, and there was only one unrelated change on &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask/distributed&lt;/span&gt;&lt;/code&gt;.
That left us with the Coiled platform and upstream packages as possible candidates.&lt;/p&gt;
&lt;p&gt;After digging deeper into the cluster data, we noticed that &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pandas&lt;/span&gt;&lt;/code&gt; had been upgraded from &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;1.5.3&lt;/span&gt;&lt;/code&gt; to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;2.0.1&lt;/span&gt;&lt;/code&gt;.
A major upgrade to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pandas&lt;/span&gt;&lt;/code&gt; at the same time a dataframe-based workload shows degrading performance? That’s suspicious!&lt;/p&gt;
&lt;p&gt;To confirm this suspicion, we ran an A/B test based on the current Dask release (&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;2023.4.1&lt;/span&gt;&lt;/code&gt; at the time), testing the impact of the pandas upgrade.
&lt;a class="reference external" href="https://github.com/coiled/benchmarks/actions/runs/4946428740"&gt;The results&lt;/a&gt; were clear: The runtime increased significantly with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pandas=2.0.1&lt;/span&gt;&lt;/code&gt; (sample).&lt;/p&gt;
&lt;figure class="align-default" id="ab-test"&gt;
&lt;img alt="The A/B test shows a significant increase in runtime for pandas=2.0.1" src="https://docs.coiled.io/_images/ab-test.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Having shown that &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pandas&lt;/span&gt;&lt;/code&gt; caused for the performance degradation and that we could reproduce it with the current Dask release, our release process for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;2023.5.0&lt;/span&gt;&lt;/code&gt; &lt;a class="reference external" href="https://github.com/dask/community/issues/322#issuecomment-1543878628"&gt;was cleared&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To further analyze the problem, we also derived a &lt;a class="reference external" href="https://matthewrocklin.com/minimal-bug-reports.html#minimal-complete-verifiable-examples"&gt;minimal local reproducer&lt;/a&gt; from the original workload:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/h2o-benchmark/N_1e7_K_1e2_parquet/*.parquet&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id6&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;v2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;v3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isna&lt;/span&gt;&lt;span class="p"&gt;()][[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id6&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;v3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id6&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nlargest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id6&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Int64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;v3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;float64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)[[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="investigating-the-pandas-performance-degradation"&gt;
&lt;h2&gt;Investigating the pandas performance degradation&lt;/h2&gt;
&lt;p&gt;The only user-visible thing that changed between pandas 1.5.3 and pandas 2.0.1 was the default value
of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;group_keys&lt;/span&gt;&lt;/code&gt; in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;GroupBy&lt;/span&gt;&lt;/code&gt;. Switching to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;group_keys=False&lt;/span&gt;&lt;/code&gt; with version 2.0.1
got us back to the initial runtime.
Now that we knew that pandas was to blame for the performance degradation, we had to create a
reproducer in plain pandas to help fix the issue.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)),&lt;/span&gt; 
        &lt;span class="s2"&gt;&amp;quot;bar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nlargest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;group_keys=False&lt;/span&gt;&lt;/code&gt;: approx. 11 seconds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;group_keys=True&lt;/span&gt;&lt;/code&gt;: approx. 15 seconds&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Experimenting a bit showed us that the bottleneck got even worse while increasing the number of
groups during the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;groupby&lt;/span&gt;&lt;/code&gt; calculation. We settled on this version which is 30% slower with
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;group_keys=True&lt;/span&gt;&lt;/code&gt;, enough to be able to troubleshoot the problem. There was no obvious reason
why the changed value should bring a significant slowdown.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;%prun&lt;/span&gt;&lt;/code&gt; showed us that the time was almost exclusively spent in a post-processing step that
combines all groups via &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;concat&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="addressing-the-performance-degradation"&gt;
&lt;h2&gt;Addressing the performance degradation&lt;/h2&gt;
&lt;p&gt;Let’s look at how both cases differ. The new version passes the grouping levels to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;concat&lt;/span&gt;&lt;/code&gt;, which
are used to construct the resulting Index levels. This shouldn’t be that slow though. Investigations
showed that this runs through a code-path that is very inefficient!&lt;/p&gt;
&lt;p&gt;Looking closer at the results of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;%prun&lt;/span&gt;&lt;/code&gt; pointed us to one specific loop that took up most of
the runtime. This loop calculates the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;codes&lt;/span&gt;&lt;/code&gt; for the resulting index based on the provided
levels. It’s slow, really slow! Every single element provided as &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;keys&lt;/span&gt;&lt;/code&gt;, which
represent the number of groups, is checked against the whole level, which explains our previous
observation that the runtime got worse with an increasing number of groups. You can check out the
&lt;a class="reference external" href="https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html#more-concatenating-with-group-keys"&gt;pandas user guide&lt;/a&gt;
if you are interested in situations where this is useful. Fortunately, we have a convenient
advantage in case of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;groupby&lt;/span&gt;&lt;/code&gt;. We know beforehand that every key equals the specific level.
We added a fast-path that exploits this knowledge getting the runtime of this step more or less to
zero.&lt;/p&gt;
&lt;p&gt;This change resulted in a &lt;a class="reference external" href="https://github.com/pandas-dev/pandas/pull/53195"&gt;small PR&lt;/a&gt; that cut
the runtime of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;group_keys=True&lt;/span&gt;&lt;/code&gt; to approximately 11 seconds as well.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Now that we made our pandas reproducer run 30% faster, we have to check whether we accomplished our
initial objective. Re-running the local Dask reproducer should give us an idea about the
impact on Dask. We got the performance down to 22 seconds as well! Promising news that saved
our plans for the evening!&lt;/p&gt;
&lt;p&gt;Unfortunately, we had to wait until pandas 2.0.2 was released to run a proper benchmark.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/benchmark_after.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;This looks great! Our small pandas change translated to our Dask query and got performance back
to the previous level!&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-upstream-performance-tests.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jun 23, 2023</summary>
    <category term="coiled" label="coiled"/>
    <category term="dask" label="dask"/>
    <category term="pandas" label="pandas"/>
    <category term="performance" label="performance"/>
    <published>2023-06-23T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/coiled-notebooks.html</id>
    <title>Coiled notebooks</title>
    <updated>2023-06-14T00:00:00+00:00</updated>
    <author>
      <name>Nat Tabris</name>
    </author>
    <content type="html">&lt;section id="coiled-notebooks"&gt;

&lt;p&gt;We recently pushed out a new, experimental &lt;a class="reference external" href="https://docs.coiled.io/user_guide/labs/notebooks.html"&gt;notebooks feature&lt;/a&gt;
for easily launching Jupyter servers in the cloud from your local machine.
We’re excited about Coiled notebooks because they:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Allow users to easily scale up their computations on a single machine in the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make GPUs more accessible.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;section id="how-to-launch-a-notebook"&gt;
&lt;h2&gt;How to launch a notebook&lt;/h2&gt;
&lt;p&gt;You can launch a Coiled notebook with:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;coiled&amp;gt;=0.6.8&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;notebook&lt;span class="w"&gt; &lt;/span&gt;start
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This will provision an instance on your cloud provider, set up a JupyterLab session, and connect
you to that session in a web browser.&lt;/p&gt;
&lt;p&gt;In addition to the advantages you get from Coiled Dask clusters like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Automatic software environment syncing (&lt;a class="reference external" href="https://docs.coiled.io/user_guide/package_sync.html"&gt;docs&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The same cloud permissions you have locally&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run in any region on AWS/GCP&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Coiled notebooks also support automatic file syncing
between your local machine and the remote Jupyter server in the cloud.
This lets you bring your local files with you, edit them in the cloud, and have those
changes brought back locally.&lt;/p&gt;
&lt;p&gt;To run a notebook with automatic file syncing, &lt;a class="reference external" href="https://mutagen.io/documentation/introduction/installation"&gt;install Mutagen&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;mutagen-io/mutagen/mutagen
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;and use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--sync&lt;/span&gt;&lt;/code&gt; flag:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;notebook&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;--sync&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Automatically syncs your files&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="example-scaling-up"&gt;
&lt;h2&gt;Example: Scaling up&lt;/h2&gt;
&lt;p&gt;Sometimes users just want to spin up a big machine in the cloud and use a local Dask cluster
on that machine for their computations. This is a great fit when working with ~10-100 GB
datasets where a single, large machine can be effective.&lt;/p&gt;
&lt;iframe width="560"
    height="315"
    src="https://www.youtube.com/embed/mfLyF7nZlRw"
    data-hj-allow-iframe="true"
    style="margin: 0 auto 20px auto; display: block;"
    frameborder="0"
    allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
    allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;With Coiled notebooks, you can use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--vm-type&lt;/span&gt;&lt;/code&gt; option to specify what instance type you want
to use:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;notebook&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;--vm-type&lt;span class="w"&gt; &lt;/span&gt;m6i.32xlarge&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 128 Cores and 512 GB of RAM&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then on the Jupyter server, Dask’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;LocalCluster&lt;/span&gt;&lt;/code&gt; will utilize the full resources of the instance
(e.g. 128 core and ~500 GB of memory in this case)&lt;/p&gt;
&lt;div align="center"&gt;
&lt;p&gt;&lt;img alt="coiled-notebook-big-localcluster" src="https://docs.coiled.io/_images/coiled-notebook-big-localcluster.png" /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="example-easy-access-to-gpus"&gt;
&lt;h2&gt;Example: Easy access to GPUs&lt;/h2&gt;
&lt;p&gt;Several libraries in the PyData space like PyTorch, XGBoost, and the entire &lt;a class="reference external" href="https://rapids.ai"&gt;RAPIDS&lt;/a&gt;
ecosystem (e.g. cuDF, cuML, etc) support GPU acceleration. This can lead to large performance improvements
in a variety of applications. However, today many data scientists and developers don’t readily have access to
GPUs.&lt;/p&gt;
&lt;p&gt;Coiled notebooks make it easy to run on GPU-enabled instances, lowering the barrier to entry for GPU
accelerated computations. For example, you can use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--gpu&lt;/span&gt;&lt;/code&gt; flag and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--container&lt;/span&gt;&lt;/code&gt; option to launch a
Coiled notebook on a GPU instance inside a pre-built &lt;a class="reference external" href="https://hub.docker.com/r/rapidsai/rapidsai"&gt;RAPIDS docker image&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# GPU enabled notebook running inside a RAPIDS docker image&lt;/span&gt;
coiled&lt;span class="w"&gt; &lt;/span&gt;notebook&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;--gpu&lt;span class="w"&gt; &lt;/span&gt;--container&lt;span class="w"&gt; &lt;/span&gt;nvcr.io/nvidia/rapidsai/rapidsai:23.04-cuda11.8-runtime-ubuntu20.04-py3.10
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div align="center"&gt;
&lt;p&gt;&lt;img alt="coiled-notebook-gpu-availability" src="https://docs.coiled.io/_images/coiled-notebook-gpu-availability.png" /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Tip&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--gpu&lt;/span&gt;&lt;/code&gt; flag defaults to a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;g4dn.xlarge&lt;/span&gt;&lt;/code&gt; (NVIDIA T4) instance,
but you can use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;--vm-type&lt;/span&gt;&lt;/code&gt; option to specific any GPU instance you
want to run on.&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="try-out-and-report"&gt;
&lt;h2&gt;Try out and report&lt;/h2&gt;
&lt;p&gt;We’re excited about Coiled notebooks and think they can be useful to users.
They’re also a work in progress. We value feedback from users to inform future development.
Feel free to try Coiled notebooks and let us know about your experience in the
&lt;a class="reference external" href="https://github.com/coiled/feedback/issues"&gt;Coiled feedback issue tracker&lt;/a&gt;
or by emailing &lt;a class="reference external" href="mailto:support&amp;#37;&amp;#52;&amp;#48;coiled&amp;#46;io"&gt;support&lt;span&gt;&amp;#64;&lt;/span&gt;coiled&lt;span&gt;&amp;#46;&lt;/span&gt;io&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled-notebooks.html"/>
    <summary>We recently pushed out a new, experimental notebooks feature
for easily launching Jupyter servers in the cloud from your local machine.
We’re excited about Coiled notebooks because they:</summary>
    <category term="coiled" label="coiled"/>
    <category term="gpu" label="gpu"/>
    <category term="jupyter" label="jupyter"/>
    <category term="notebooks" label="notebooks"/>
    <published>2023-06-14T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/pyarrow-in-pandas-and-dask.html</id>
    <title>Utilizing PyArrow to improve pandas and Dask workflows</title>
    <updated>2023-06-05T00:00:00+00:00</updated>
    <author>
      <name>Patrick Hoefler</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jun 5, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;14m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="utilizing-pyarrow-to-improve-pandas-and-dask-workflows"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/phofl"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/61934744?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Patrick Hoefler&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Get the most out of PyArrow support in pandas and Dask right now&lt;/em&gt;&lt;/p&gt;
&lt;section id="introduction"&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This post investigates where we can use PyArrow to improve our pandas and Dask workflows right now.
General support for PyArrow dtypes was added with pandas 2.0 to &lt;a class="reference external" href="https://pandas.pydata.org"&gt;pandas&lt;/a&gt;
and &lt;a class="reference external" href="https://www.dask.org"&gt;Dask&lt;/a&gt;. This solves a
bunch of long-standing pains for users of both libraries. pandas users often complain to me that
pandas does not support missing values in arbitrary dtypes or that non-standard dtypes are not very
well supported. A particularly annoying problem for
Dask users is running out of memory with large datasets. PyArrow backed string columns
consume up to 70% less memory compared to NumPy object columns and thus have the potential to
mitigate this problem as well as providing a huge performance improvement.&lt;/p&gt;
&lt;p&gt;Support for PyArrow dtypes in pandas, and by extension Dask, is still relatively new. I would
recommend caution when opting into the PyArrow &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend&lt;/span&gt;&lt;/code&gt; until at least pandas 2.1 is
released. Not every part of both APIs is optimized yet. You should be able to get a big improvement
in certain workflows though. This post will go over a couple of examples where I’d recommend switching to
PyArrow right away, because it already provides huge benefits.&lt;/p&gt;
&lt;p&gt;Dask itself can benefit in various ways from PyArrow dtypes. We will investigate how PyArrow backed
strings can easily mitigate the pain point of running out of memory on Dask clusters and how we
can improve performance through utilizing PyArrow.&lt;/p&gt;
&lt;p&gt;I am part of the pandas core team and was heavily involved in implementing and improving PyArrow
support in pandas. I’ve recently joined
&lt;a class="reference external" href="https://www.coiled.io"&gt;Coiled&lt;/a&gt; where I
am working on Dask. One of my tasks is improving the PyArrow integration.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="general-overview-of-pyarrow-support"&gt;
&lt;h2&gt;General overview of PyArrow support&lt;/h2&gt;
&lt;p&gt;PyArrow dtypes were initially introduced in pandas 1.5. The implementation was experimental and I
wouldn’t recommend using it on pandas 1.5.x. Support for them is still relatively new.
pandas 2.0 provides a huge improvement, including making opting into PyArrow backed DataFrames easy.
We are still working on supporting them properly everywhere, and thus they should be used with caution
until at least pandas 2.1 is released. Both projects work continuously to improve support throughout Dask and
pandas.&lt;/p&gt;
&lt;p&gt;We encourage users to try them out! This will help us to get a better idea of what is still lacking
support or is not fast enough. Giving feedback helps us improve support and will drastically reduce the
time that is necessary to create a smooth user experience.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dataset"&gt;
&lt;h2&gt;Dataset&lt;/h2&gt;
&lt;p&gt;We will use the taxi dataset from New York City that contains all Uber and Lyft rides. It has
some interesting attributes like price, tips, driver pay and many more. The dataset can be found
&lt;a class="reference external" href="https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page"&gt;here&lt;/a&gt;
(see &lt;a class="reference external" href="https://www.nyc.gov/home/terms-of-use.page"&gt;terms of service&lt;/a&gt;) and is stored in parquet
files. When analyzing Dask queries, we will use a publicly available S3 bucket to simplify our
queries: &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-datasets/uber-lyft-tlc/&lt;/span&gt;&lt;/code&gt;. We will use the dataset from December 2022
for our pandas queries, since this is the maximum that fits comfortably into memory on my
machine (24GB of RAM). We have to avoid stressing our RAM usage, since this might introduce side
effects when analyzing performance.&lt;/p&gt;
&lt;p&gt;We will also investigate the performance of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;. We will use the &lt;em&gt;Crimes in Chicago&lt;/em&gt; dataset
that can be found &lt;a class="reference external" href="https://www.kaggle.com/datasets/utkarshx27/crimes-2001-to-present"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-cluster"&gt;
&lt;h2&gt;Dask cluster&lt;/h2&gt;
&lt;p&gt;There are various different options to set up a Dask cluster, see the
&lt;a class="reference external" href="https://docs.dask.org/en/stable/deploying.html"&gt;Dask documentation&lt;/a&gt; for
a non-exhaustive list of deployment options. I will use
&lt;a class="reference external" href="https://docs.coiled.io/user_guide/index.html"&gt;Coiled&lt;/a&gt; to create a cluster on AWS with
30 machines through:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dask-performance-comparisons&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# this is the region of our dataset&lt;/span&gt;
    &lt;span class="n"&gt;worker_vm_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;m6i.large&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled is connected to my AWS account. It creates the cluster within my account and manages all
resources for me. 30 machines are enough to operate on our dataset comfortably. We will investigate
how we can reduce the required number of workers to 15 through some small
modifications.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="pandas-stringdtype-backed-by-pyarrow"&gt;
&lt;h2&gt;pandas StringDtype backed by PyArrow&lt;/h2&gt;
&lt;p&gt;We begin with a feature that was originally introduced over 3 years ago in pandas 1.0. Setting the
dtype in pandas or Dask to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;string&lt;/span&gt;&lt;/code&gt; returns an object with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;StringDtype&lt;/span&gt;&lt;/code&gt;. This feature is relatively mature and should
provide a smooth user experience.&lt;/p&gt;
&lt;p&gt;Historically, pandas represented string data through NumPy arrays with dtype &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;object&lt;/span&gt;&lt;/code&gt;. NumPy object data is stored as an
array of pointers pointing to the actual data in memory. This makes iterating over an array containing
strings very slow. pandas 1.0 initially introduced said
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;StringDtype&lt;/span&gt;&lt;/code&gt; that allowed easier and consistent operations on strings. This dtype was still backed by Python
strings and thus, wasn’t very performant either. Rather, it provided a clear abstraction of string
data.&lt;/p&gt;
&lt;p&gt;pandas 1.3 finally introduced an enhancement to create an efficient string dtype. This datatype is backed by PyArrow arrays.
&lt;a class="reference external" href="https://arrow.apache.org/docs/python/index.html"&gt;PyArrow&lt;/a&gt; provides a data structure that enables
performant and memory efficient string operations.
Starting from that point on, users could use a string dtype that was contiguous in memory and thus
very fast. This dtype can be requested through &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;string[pyarrow]&lt;/span&gt;&lt;/code&gt;. Alternatively, we can request it
by specifying &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;string&lt;/span&gt;&lt;/code&gt; as the dtype and setting:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string_storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Since Dask builds on top of pandas, this string dtype is available here as well. On top of that,
Dask offers a convenient option that automatically converts all string-data to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;string[pyarrow]&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataframe.convert-string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is a convenient way of
avoiding NumPy object dtype for string columns. Additionally, it has the advantage that it
creates PyArrow arrays natively for I/O methods that operate with Arrow objects.
On top of providing huge performance improvements, PyArrow strings consume significantly less
memory. An average Dask DataFrame with PyArrow strings consumes around 33-50% of the original
memory compared to NumPy object. This solves the biggest pain point for Dask users that is running
out of memory when operating on large datasets. The option enables global testing in Dask’s test
suite. This ensures that PyArrow backed strings are mature enough to provide a smooth user
experience.&lt;/p&gt;
&lt;p&gt;Let’s look at a few operations that represent typical string operations. We will start with a couple
of pandas examples before switching over to operations on our Dask cluster.&lt;/p&gt;
&lt;p&gt;We will use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;df.convert_dtypes&lt;/span&gt;&lt;/code&gt; to convert our object columns to PyArrow string arrays. There
are more efficient ways of getting PyArrow dtypes in pandas that we will explore later. We will use
the Uber-Lyft dataset from December 2022, this file fits comfortably into memory on my machine.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string_storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;fhvhv_tripdata_2022-10.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;tips&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="s2"&gt;&amp;quot;driver_pay&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="s2"&gt;&amp;quot;base_passenger_fare&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="s2"&gt;&amp;quot;dispatching_base_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert_dtypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;convert_boolean&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;convert_floating&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;convert_integer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Our DataFrame has NumPy dtypes for all non-string columns in this example. Let’s start with
filtering for all rides that were operated by Uber.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;HV0003&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This operation creates a mask with True/False values that specify whether Uber operated a ride.
This doesn’t utilize any special string methods, but the equality comparison dispatches to
PyArrow. Next, we will use the String accessor that is implemented in pandas and gives you access
to all kinds of string operations on a per-element basis. We want to find all rides that were
dispatched from a base starting with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;quot;B028&amp;quot;&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dispatching_base_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;B028&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;startswith&lt;/span&gt;&lt;/code&gt; iterates over our array and checks whether every string starts with the specified
substring. The advantage of PyArrow is easy to see. The data are contiguous in memory, which means
that we can efficiently iterate over them. Additionally, these arrays have a second array with
pointers that point to the first memory address of every string, which makes computing the starting
sequence even faster.&lt;/p&gt;
&lt;p&gt;Finally, we look at a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;GroupBy&lt;/span&gt;&lt;/code&gt; operation that groups over PyArrow string columns. The calculation
of the groups can dispatch to PyArrow as well, which is more efficient than factorizing
over a NumPy object array.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dispatching_base_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numeric_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s look at how these operations stack up against DataFrames where string columns are represented
by NumPy object dtype.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/pandas_string_performance_comparison.svg" /&gt;&lt;/p&gt;
&lt;p&gt;The results are more or less as we expected. The string based comparisons are significantly faster
when performed on PyArrow strings. Most string accessors should provide a huge performance
improvement. Another interesting observation is memory usage, it is reduced by roughly 50% compared
to NumPy object dtype. We will take a closer look at this with Dask.&lt;/p&gt;
&lt;p&gt;Dask mirrors the pandas API and dispatches to pandas for most operations. Consequently, we can use
the same API to access PyArrow strings. A convenient option to request these globally is the option
mentioned above, which is what we will use here:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataframe.convert-string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;One of the biggest benefits of this option during development is that it enables easy testing of PyArrow
strings globally in Dask to make sure that everything works smoothly. We will utilize the Uber-Lyft
dataset for our explorations. The dataset takes up around 240GB of memory on our cluster. Our initial
cluster has 30 machines, which is enough to perform our computations comfortably.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;

&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataframe.convert-string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/uber-lyft-tlc/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Wait till the computation is finished&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We persist the data in memory so that I/O performance does not influence our performance measurements. Our data is now
available in memory, which makes access fast. We will perform computations that are similar to our
pandas computations. One of the main goals is to show that the benefits from pandas will
translate to computations in a distributed environment with Dask.&lt;/p&gt;
&lt;p&gt;One of the first observations is that the DataFrame with PyArrow backed string columns consumes only
130GB of memory, only half of what it consumed with NumPy object columns. We have only a few string
columns in our DataFrame, which means that the memory savings for string columns are actually higher than around 50%
when switching to PyArrow strings. Consequently, we will reduce the size of our cluster to 15 workers
when performing our operations on PyArrow string columns.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We measure the performance of the mask-operation and one of the String accessors together through
subsequent filtering of the DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;HV0003&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dispatching_base_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;B028&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We see that we can use the same methods as in our previous example. This makes transitioning from
pandas to Dask relatively easy.&lt;/p&gt;
&lt;p&gt;Additionally, we will again compute a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;GroupBy&lt;/span&gt;&lt;/code&gt; operation on our data. This is significantly harder
in a distributed environment, which makes the results more interesting. The previous operations
parallelize relatively easy onto a large cluster, while this is harder with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;GroupBy&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hvfhs_license_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dispatching_base_num&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numeric_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/Dask_string_performance_comparison.svg" /&gt;&lt;/p&gt;
&lt;p&gt;We get nice improvements by factors of 2 and 3. This is especially intriguing since we reduced
the size of our cluster from 30 machines to 15, reducing the cost by 50%. Subsequently, we also reduced our computational
resources by a factor of 2, which makes our performance improvement even more impressive. Thus,
the performance improved by a factor of 4 and 6 respectively. We can
perform the same computations on a smaller cluster, which saves money and is more efficient in general,
and still get a performance boost out of it.&lt;/p&gt;
&lt;p&gt;Summarizing, we saw that PyArrow string-columns are a huge improvement when comparing them to NumPy object columns in
DataFrames. Switching to PyArrow strings is a relatively small change that might improve the
performance and efficiency of an average workflow that depends on string data immensely. These improvements
are equally visible in pandas and Dask!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="engine-keyword-in-i-o-methods"&gt;
&lt;h2&gt;Engine keyword in I/O methods&lt;/h2&gt;
&lt;p&gt;We will now take a look at I/O functions in pandas and Dask. Some functions have custom implementations,
like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;, while others dispatch to another library, like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_excel&lt;/span&gt;&lt;/code&gt; to
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;openpyxl&lt;/span&gt;&lt;/code&gt;. Some of these functions gained a new &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;engine&lt;/span&gt;&lt;/code&gt; keyword that enables us to dispatch to
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;PyArrow&lt;/span&gt;&lt;/code&gt;. The PyArrow parsers are multithreaded by default and hence, can provide a significant
performance improvement.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Crimes_-_2001_to_Present.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This configuration will return the same results as the other engines. The only difference is that
PyArrow is used to read the data. The same option is available for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_json&lt;/span&gt;&lt;/code&gt;.
The PyArrow-engines were added to provide a faster way of reading data. The improved speed is only
one of the advantages. The PyArrow parsers return the data as a
&lt;a class="reference external" href="https://arrow.apache.org/docs/python/generated/pyarrow.Table.html"&gt;PyArrow Table&lt;/a&gt;. A PyArrow Table
provides built-in functionality to convert to a pandas &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DataFrame&lt;/span&gt;&lt;/code&gt;. Depending on the data, this
might require a copy while casting to NumPy (string, integers with missing values, …), which
brings an unnecessary slowdown. This is where the PyArrow &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend&lt;/span&gt;&lt;/code&gt; comes in.
It is implemented as an &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;ArrowExtensionArray&lt;/span&gt;&lt;/code&gt; class in pandas, which is backed by a
&lt;a class="reference external" href="https://arrow.apache.org/docs/python/generated/pyarrow.ChunkedArray.html"&gt;PyArrow ChunkedArray&lt;/a&gt;.
As a direct consequence, the conversion from a PyArrow Table to pandas is extremely cheap since it
does not require any copies.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Crimes_-_2001_to_Present.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype_backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This returns a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DataFrame&lt;/span&gt;&lt;/code&gt; that is backed by PyArrow arrays. pandas isn’t optimized everywhere
yet, so this can give you a slowdown in follow-up operations. It might be worth it if
the workload is particularly I/O heavy. Let’s look at a direct comparison:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/pandas_read_csv_performance.svg" /&gt;&lt;/p&gt;
&lt;p&gt;We can see that PyArrow-engine and PyArrow dtypes provide a 15x speedup compared
to the C-engine.&lt;/p&gt;
&lt;p&gt;The same advantages apply to Dask. Dask wraps the pandas csv reader and
hence, gets the same features for free.&lt;/p&gt;
&lt;p&gt;The comparison for Dask is a bit more complicated. Firstly, my example reads the data from my local machine while
our Dask examples will read the data from a S3 bucket. Network speed will
be a relevant component. Also, distributed computations have some
overhead that we have to account for.&lt;/p&gt;
&lt;p&gt;We are purely looking for speed here, so we will read some timeseries data
from a public S3 bucket.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/csv/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;parse_dates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;timestamp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We will execute this code-snippet for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;engine=&amp;quot;c&amp;quot;&lt;/span&gt;&lt;/code&gt;, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;engine=&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;/code&gt; and additionally
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;engine=&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;/code&gt; with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend=&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;/code&gt;. Let’s look at some performance comparisons.
Both examples were executed with 30 machines on the cluster.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/Dask_read_csv_performance.svg" /&gt;&lt;/p&gt;
&lt;p&gt;The PyArrow-engine runs around 2 times as fast as the C-engine. Both implementations used the same
number of machines. The memory usage was reduced by 50% with the PyArrow &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend&lt;/span&gt;&lt;/code&gt;. The same
reduction is available if only object columns are converted to PyArrow strings, which gives
a better experience in follow-up operations.&lt;/p&gt;
&lt;p&gt;We’ve seen that the Arrow-engines provide significant speedups over the custom C implementations.
They don’t support all features of the custom implementations yet, but if your use-case is
compatible with the supported options, you should get a significant speedup for free.&lt;/p&gt;
&lt;p&gt;The case with the PyArrow &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend&lt;/span&gt;&lt;/code&gt; is a bit more complicated. Not all areas of the API are
optimized yet. If you spend a lot of time processing your data outside I/O functions, then this might not
give you what you need. It will speed up your processing if your workflow spends a lot of
time reading the data.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dtype-backend-in-pyarrow-native-i-o-readers"&gt;
&lt;h2&gt;dtype_backend in PyArrow-native I/O readers&lt;/h2&gt;
&lt;p&gt;Some other I/O methods have an engine keyword as well. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt; is the most popular
example. The situation is a bit different here though. These I/O methods were already using the
PyArrow engine by default. So the parsing is as efficient as possible. One other potential
performance benefit is the usage of the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend&lt;/span&gt;&lt;/code&gt; keyword. Normally, PyArrow will return
a PyArrow table which is then converted to a pandas DataFrame. The PyArrow dtypes are converted to
their NumPy equivalent. Setting &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend=&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;/code&gt; avoids this conversion. This gives
a decent performance improvement and saves a lot of memory.&lt;/p&gt;
&lt;p&gt;Let’s look at one pandas performance comparison. We read the Uber-Lyft taxi data from December 2022.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fhvhv_tripdata_2022-10.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We read the data with and without &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend=&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/pandas_read_parquet_performance.svg" /&gt;&lt;/p&gt;
&lt;p&gt;We can easily see that the most time is taken up by the conversion after the reading of the
Parquet file was finished. The function runs 3 times as fast when avoiding
the conversion to NumPy dtypes.&lt;/p&gt;
&lt;p&gt;Dask has a specialized implementation for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt; that has some advantages tailored to
distributed workloads compared to the pandas implementation. The common denominator is that both
functions dispatch to PyArrow to read the parquet file. Both have in common that the data are
converted to NumPy dtypes after successfully reading the file. We are reading
the whole Uber-Lyft dataset, which consumes around 240GB of memory on our
cluster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/uber-lyft-tlc/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We read the dataset in 3 different configurations. First with the default NumPy dtypes, then with
the PyArrow string option turned on:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataframe.convert-string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And lastly with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend=&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;/code&gt;. Let’s look at what this means performance-wise:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/Dask_read_parquet_performance.svg" /&gt;&lt;/p&gt;
&lt;p&gt;Similar to our pandas example, we can see that converting to NumPy dtypes takes up a huge chunk of
our runtime. The PyArrow dtypes give us a nice performance improvement. Both PyArrow configurations
use half of the memory that the NumPy dtypes are using.&lt;/p&gt;
&lt;p&gt;PyArrow-strings are a lot more mature than the general PyArrow &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend&lt;/span&gt;&lt;/code&gt;. Based on the
performance chart we got, we get roughly the same performance improvement when using PyArrow
strings and NumPy dtypes for all other dtypes. If a workflow does not work well enough on PyArrow
dtypes yet, I’d recommend enabling PyArrow strings only.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We have seen how we can leverage PyArrow in pandas in Dask right now. PyArrow backed string columns have the
potential to impact most workflows in a positive way and provide a smooth user experience with
pandas 2.0. Dask has a convenient option to globally avoid NumPy object dtype when possible, which
makes opting into PyArrow backed strings even easier. PyArrow also provides huge speedups in other
areas where available. The PyArrow &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype_backend&lt;/span&gt;&lt;/code&gt; is still pretty new and has the
potential to cut I/O times significantly right now. It is certainly worth exploring whether it can solve
performance bottlenecks. There is a lot of work going on to improve support for general PyArrow
dtypes with the potential to speed up an average workflow in the near future.&lt;/p&gt;
&lt;p&gt;There is a current proposal in pandas to start inferring strings as PyArrow backed strings by
default starting from pandas 3.0. Additionally, it includes many more areas where leaning more
onto PyArrow makes a lot of sense (e.g. Decimals, structured data, …). You can read up on the
proposal &lt;a class="reference external" href="https://github.com/pandas-dev/pandas/pull/52711"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thank you for reading. Feel free to reach out (patrick&amp;#64;coiled.io) to share your thoughts and feedback
about PyArrow support in both libraries.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/pyarrow-in-pandas-and-dask.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jun 5, 2023</summary>
    <category term="dask" label="dask"/>
    <category term="pandas" label="pandas"/>
    <category term="pyarrow" label="pyarrow"/>
    <published>2023-06-05T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/distributed-printing.html</id>
    <title>Distributed printing</title>
    <updated>2023-05-18T00:00:00+00:00</updated>
    <author>
      <name>James Bourbeau</name>
    </author>
    <content type="html">&lt;section id="distributed-printing"&gt;

&lt;p&gt;&lt;em&gt;Dask makes it easy to print whether you’re running code locally on your laptop, or remotely on a cluster in the cloud.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of the most basic things programmers do is print text to their screen.&lt;/p&gt;
&lt;p&gt;Printing is often used for things like debugging:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Made it here...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;or to signal progress:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Done with iteration &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;However, when running code at scale on a Dask cluster even simple &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;print&lt;/span&gt;&lt;/code&gt; calls
can become non-intuitive.&lt;/p&gt;
&lt;p&gt;For example, this snippet runs two functions on a Dask/Coiled cluster in the cloud:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;

&lt;span class="c1"&gt;# Spin up cluster&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Calling inc on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="nd"&gt;@dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Calling add on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; + &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

&lt;span class="c1"&gt;# Run computations on the cluster&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Both the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;inc&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;add&lt;/span&gt;&lt;/code&gt; functions have &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;print&lt;/span&gt;&lt;/code&gt; calls and, naively, I’d expect to see&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Calling&lt;/span&gt; &lt;span class="n"&gt;inc&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;Calling&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;printed to my screen when I run this example. However, when you run this code snippet you don’t see
any output. That’s because those prints happen on the remote Dask workers (in this case on AWS)
where these tasks are run. We can see the result of these prints by looking our
&lt;a class="reference external" href="https://docs.coiled.io/user_guide/logging.html"&gt;cluster logs&lt;/a&gt;.&lt;/p&gt;
&lt;div align="center"&gt;
&lt;p&gt;&lt;img alt="print-in-worker-logs" src="https://docs.coiled.io/_images/print-in-worker-logs.png" /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;It’s good that these prints work, but it’s not a very pleasant experience and probably doesn’t
match most user’s expectations.&lt;/p&gt;
&lt;p&gt;To make printing on a cluster similar to on programmer’s local machine, Dask has a
&lt;a class="reference external" href="https://distributed.dask.org/en/stable/api.html#distributed.print"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.distributed.print&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;
function which mirrors &lt;a class="reference external" href="https://docs.python.org/3/library/functions.html#print"&gt;Python’s built-in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;print&lt;/span&gt;&lt;/code&gt; function&lt;/a&gt;
but, when called in a task running on a cluster, the output of the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;print&lt;/span&gt;&lt;/code&gt;
will be forwarded back to the user’s client Python session and printed there.&lt;/p&gt;
&lt;p&gt;Here’s the same example as above, but with one line added to use Dask’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;print&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;  &lt;span class="c1"&gt;# This line is the only difference&lt;/span&gt;

&lt;span class="c1"&gt;# Spin up cluster&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Calling inc on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="nd"&gt;@dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Calling add on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; + &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

&lt;span class="c1"&gt;# Run computations on the cluster&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Running this on my laptop prints&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Calling&lt;/span&gt; &lt;span class="n"&gt;inc&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;Calling&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;in my IPython session 🎉&lt;/p&gt;
&lt;p&gt;When using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;print&lt;/span&gt;&lt;/code&gt;s inside tasks running on a Dask cluster, know that &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.distributed.print&lt;/span&gt;&lt;/code&gt;
is available and probably what you want to use.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Tip&lt;/p&gt;
&lt;p&gt;Dask also has a &lt;a class="reference external" href="https://distributed.dask.org/en/stable/api.html#distributed.warn"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.distributed.warn&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;
utility, which mirrors &lt;a class="reference external" href="https://docs.python.org/3/library/warnings.html#warnings.warn"&gt;Python’s built-in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;warnings.warn&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;
that forwards warnings to user’s client Python session.&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/distributed-printing.html"/>
    <summary>Dask makes it easy to print whether you’re running code locally on your laptop, or remotely on a cluster in the cloud.print-in-worker-logs</summary>
    <category term="dask" label="dask"/>
    <category term="utilities" label="utilities"/>
    <published>2023-05-18T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/observability-with-dask.html</id>
    <title>Observability for Distributed Computing with Dask</title>
    <updated>2023-05-16T00:00:00+00:00</updated>
    <author>
      <name>Hendrik Makait</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;May 16, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;8m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="observability-for-distributed-computing-with-dask"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://makait.com/"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://makait.com/_static/profile.png" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Hendrik Makait&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Debugging is hard.
Distributed debugging is hell.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When dealing with unexpected issues in a distributed system, you need to understand what and &lt;em&gt;why&lt;/em&gt; it happened, how interactions between individual pieces contributed to the problems, and how to avoid them in the future.
In other words, you need &lt;strong&gt;observability&lt;/strong&gt;.
This article explains what observability is, how Dask implements it, what pain points remain, and how Coiled helps you overcome these.&lt;/p&gt;
&lt;figure class="align-default" id="coiled-dashboard"&gt;
&lt;img alt="The Coiled metrics dashboard provides observability into a Dask cluster and its workloads." src="https://docs.coiled.io/_images/coiled-dashboard1.png" style="width: 100%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;The Coiled metrics dashboard provides observability into a Dask cluster and its workloads.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;This is based on a &lt;a class="reference external" href="https://2023.pycon.de/program/SSTCTS/"&gt;talk&lt;/a&gt; given by &lt;a class="reference external" href="https://github.com/hendrikmakait"&gt;Hendrik Makait&lt;/a&gt; at PyCon DE &amp;amp; PyData Berlin 2023.
You can also take a look at the &lt;a class="reference external" href="https://github.com/hendrikmakait/pydata-berlin-2023"&gt;original slides&lt;/a&gt; or watch the &lt;a class="reference external" href="https://youtu.be/Qp90ka5ZMqg?si=DmrZSY4vBQeUUtJ7"&gt;video recording&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;section id="the-problem"&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Imagine receiving an alert:&lt;/strong&gt; One of your daily computations failed after running for several thousands of CPU hours.
By looking at the error message, you see that workers have died while attempting to run a particular task:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;KilledWorker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Attempted&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;complex-computation-f970d1bdb34ba1bb5263425c7f356cf3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;different&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="nb"&gt;all&lt;/span&gt; &lt;span class="n"&gt;those&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="n"&gt;died&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;10.0.46.23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;33455.&lt;/span&gt; &lt;span class="n"&gt;Inspecting&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;often&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;good&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;diagnose&lt;/span&gt; &lt;span class="n"&gt;what&lt;/span&gt; &lt;span class="n"&gt;went&lt;/span&gt; &lt;span class="n"&gt;wrong&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;For&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;information&lt;/span&gt; &lt;span class="n"&gt;see&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;distributed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;stable&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;killed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;“Weird,”&lt;/em&gt; you think, &lt;em&gt;“this has never happened since I added it to production two months ago.
So, why would it fail now?
What has changed?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Wouldn’t it be great if you had a way to answer these questions?
Wouldn’t it be great if you could understand what had been going on (and going wrong!) with your Dask cluster when it failed?
&lt;strong&gt;In other words, wouldn’t it be great if you had an &lt;em&gt;observable&lt;/em&gt; system?&lt;/strong&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-is-observability"&gt;
&lt;h2&gt;What is observability?&lt;/h2&gt;
&lt;p&gt;One particularly useful explanation of &lt;strong&gt;observability&lt;/strong&gt; is provided by the &lt;a class="reference external" href="https://opentelemetry.io/docs/concepts/observability-primer/#what-is-observability"&gt;OpenTelemetry&lt;/a&gt; project &lt;em&gt;(emphasis added)&lt;/em&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;Observability lets us &lt;strong&gt;understand a system from the outside&lt;/strong&gt;, by letting us ask questions about that system &lt;strong&gt;without knowing its inner workings&lt;/strong&gt;.
Furthermore, it allows us to easily troubleshoot and handle novel problems (i.e. “unknown unknowns”), and helps us answer the question, &lt;strong&gt;“Why is this happening?”&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;In essence, observability is the property of a system that allows users to understand &lt;strong&gt;why&lt;/strong&gt; things are happening.
By giving users a way to understand its behavior, an observable system enables users to take on different activities like monitoring the system’s health, root-causing issues, and optimizing performance.&lt;/p&gt;
&lt;p&gt;To make a system observable, we need to collect data to describe the system’s behavior and performance.
For Dask, two types of this &lt;strong&gt;telemetry data&lt;/strong&gt; are the most useful:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Logs:&lt;/strong&gt;
A log is an (un-)structured text record about events emitted by system, application, or user code.
Usually, logs are enriched with metadata, such as associated timestamps that provide the necessary context.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metrics:&lt;/strong&gt;
A metric is a quantitative measurement that captures information about the behavior or performance of a system and its components.
Metrics are collected at runtime and often presented as statistical aggregates and visualizations.&lt;/p&gt;
&lt;p&gt;Having good telemetry data is necessary for a system to become observable.
However, it is not enough:
Users need &lt;strong&gt;diagnostic tools&lt;/strong&gt; to leverage the telemetry data provided by the system and gain insights.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="observability-with-dask"&gt;
&lt;h2&gt;Observability with Dask&lt;/h2&gt;
&lt;section id="logging"&gt;
&lt;h3&gt;Logging&lt;/h3&gt;
&lt;p&gt;Dask and its components implement logging using Python’s &lt;a class="reference external" href="https://docs.python.org/3/library/logging.html"&gt;standard logging module&lt;/a&gt;, which makes &lt;a class="reference external" href="https://docs.dask.org/en/latest/how-to/debug.html#logs"&gt;logging in Dask configurable&lt;/a&gt;.
The difficulty with logging in Dask stems from its distributed nature:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;How do you access logs in a cluster of hundreds of instances?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How do you correlate them to identify interactions?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While Dask contains the built-in methods &lt;a class="reference external" href="https://distributed.dask.org/en/stable/api.html#distributed.Client.get_scheduler_logs"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Client.get_scheduler_logs()&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; and &lt;a class="reference external" href="https://distributed.dask.org/en/stable/api.html#distributed.Client.get_worker_logs"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Client.get_worker_logs()&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; that fetch logs to the client, this approach has a major downside: &lt;strong&gt;If it’s dead, it doesn’t return logs.&lt;/strong&gt;
If you, like most developers, primarily use logs for debugging, the relevant instances are likely dead or struggling to answer requests, not to mention shutdown clusters when running a retroactive analysis.&lt;/p&gt;
&lt;p&gt;The better solution is to rely on a &lt;strong&gt;centralized log management&lt;/strong&gt; system to handle logs.
This external system collects, stores, and analyzes logs from different sources. This approach has several benefits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Persistent logs even after cluster shutdown&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simple log correlation between OS- and application-level logs across instances&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Efficient and powerful querying&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The snippet below shows how the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/api.html#coiled-cluster-logs"&gt;Coiled CLI&lt;/a&gt; lets you filter for the cluster from our example and find all messages that mention the killed worker’s address:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;cluster&lt;span class="w"&gt; &lt;/span&gt;logs&lt;span class="w"&gt; &lt;/span&gt;--cluster&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;207560&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--filter&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;10.0.46.23&amp;quot;&lt;/span&gt;
...
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.46.23&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.668000&lt;span class="w"&gt; &lt;/span&gt;distributed.nanny.memory&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;WARNING&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Worker&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;exceeded&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;95&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;memory&lt;span class="w"&gt; &lt;/span&gt;budget.&lt;span class="w"&gt; &lt;/span&gt;Restarting...
&lt;span class="o"&gt;(&lt;/span&gt;scheduler&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.697000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Connection&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:43506&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;closed.
&lt;span class="o"&gt;(&lt;/span&gt;scheduler&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.697000&lt;span class="w"&gt; &lt;/span&gt;distributed.scheduler&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Remove&lt;span class="w"&gt; &lt;/span&gt;worker&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;WorkerState&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tls://10.0.46.23:33455&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;name:&lt;span class="w"&gt; &lt;/span&gt;hendrik-pycon-blog-worker-6660457730,&lt;span class="w"&gt; &lt;/span&gt;status:&lt;span class="w"&gt; &lt;/span&gt;paused,&lt;span class="w"&gt; &lt;/span&gt;memory:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;processing:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;
&lt;span class="o"&gt;(&lt;/span&gt;scheduler&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.697000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;comms&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.43.162&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.703000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;comms&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.35.110&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.704000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;comms&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.33.60&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.705000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;comms&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.36.173&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.706000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;comms&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.35.15&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.707000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;comms&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.46.123&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.714000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;comms&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.42.85&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.714000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;comms&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.39.241&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;-05-04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:01:25.721000&lt;span class="w"&gt; &lt;/span&gt;distributed.core&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;comms&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;tls://10.0.46.23:33455
...
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;While logs contain rich information, sifting through them requires much work.
Often, we want to gain an initial understanding of the state of our system at a glance. For this, metrics are more suitable.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="metrics"&gt;
&lt;h3&gt;Metrics&lt;/h3&gt;
&lt;p&gt;Metrics are what powers Dask.
Internally, components like the scheduler or active memory manager rely on metrics to make data-driven decisions.
To help users understand these decisions and observe what caused them, Dask provides two ways for users to access metrics:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Built-in dashboards&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integration with &lt;a class="reference external" href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The integration with &lt;a class="reference external" href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; adds an &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;/metrics&lt;/span&gt;&lt;/code&gt; endpoint on the scheduler and workers.
Prometheus can then periodically scrape these endpoints to collect the metrics.
As with central log management, this has the benefit that Prometheus centrally collects, stores, and analyzes the metrics. You can then leverage Prometheus and build powerful dashboards for monitoring or drill-down analysis on top, as we did for our benchmark suite on our &lt;a class="reference external" href="https://benchmarks-grafana.oss.coiledhq.com"&gt;public Grafana server&lt;/a&gt;.&lt;/p&gt;
&lt;figure class="align-default" id="grafana"&gt;
&lt;img alt="Grafana dashboard of Dask metrics." src="https://docs.coiled.io/_images/grafana.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;You can also configure rule-based alerts, e.g., if disk usage on a machine exceeds 95%.
At Coiled, we use metrics to flag clusters with potentially performance-impacting behavior, such as high memory pressure on individual machines.&lt;/p&gt;
&lt;figure class="align-default" id="observability"&gt;
&lt;img alt="A Coiled cluster is flagged with cluster memory pressure and disk usage." src="https://docs.coiled.io/_images/coiled-flags.png" style="width: 600px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;This Coiled cluster was flagged for cluster-wide memory pressure and disk usage.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The metrics exposed to Prometheus range from a global view of the system state&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;dask_scheduler_workers:&lt;/strong&gt;
Number of workers known by scheduler&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;dask_worker_threads:&lt;/strong&gt;
Number of worker threads&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;to a fine-grained performance focus&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;dask_scheduler_tasks_compute_seconds_total:&lt;/strong&gt;
Total time (per prefix) spent computing tasks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;dask_worker_gil_contention_total:&lt;/strong&gt;
Value representing cumulative total GIL contention on worker, in the form of summed percentages.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find the complete list of supported metrics in &lt;a class="reference external" href="https://distributed.dask.org/en/latest/prometheus.html"&gt;the Dask docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To give you a better understanding of how you can use metrics to understand your system and make data-driven decisions, let’s briefly discuss some examples:&lt;/p&gt;
&lt;p&gt;In &lt;a class="reference external" href="https://blog.coiled.io/blog/burstable-vs-nonburstable.html"&gt;Burstable vs. non-burstable AWS instance types for data engineering workloads&lt;/a&gt;, we look at runtime and other performance metrics to understand the practical difference between burstable and non-burstable instance types.&lt;/p&gt;
&lt;figure class="align-default" id="t3-array-observability"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-t3-array-25min.png" src="https://docs.coiled.io/_images/burstable-t3-array-25min.png" style="width: 100%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;t3.large&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="align-default" id="m6i-array-observability"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-m6i-array-25min.png" src="https://docs.coiled.io/_images/burstable-m6i-array-25min.png" style="width: 100%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;m6i.large&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;As you can see in the plots above, it matters quite a bit, and we were able to explain the differences by looking at even more metrics.&lt;/p&gt;
&lt;p&gt;In &lt;a class="reference external" href="https://blog.coiled.io/blog/performance-testing.html"&gt;Performance testing at Coiled&lt;/a&gt;, we describe how we leverage performance metrics on an extensive suite of real-world use cases. Our approach allows us both to proactively gauge the performance impact of new changes to Dask through A/B testing as well as to identify and to root-cause performance regressions.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="diagnostic-tools"&gt;
&lt;h3&gt;Diagnostic tools&lt;/h3&gt;
&lt;p&gt;Telemetry data like logs and metrics are only helpful if you can leverage them.
To do so, you need good diagnostic tools that refine the available telemetry data into a format that helps the user understand what is happening inside the system and why.&lt;/p&gt;
&lt;p&gt;We have already seen that centralized log management systems and metrics stores can be powerful diagnostic tools but require additional infrastructure work to set them up properly.
Let us now look at the tools Dask &lt;a class="reference external" href="https://distributed.dask.org/en/stable/diagnosing-performance.html"&gt;has already built in&lt;/a&gt;.&lt;/p&gt;
&lt;section id="dashboards"&gt;
&lt;h4&gt;Dashboards&lt;/h4&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://docs.dask.org/en/stable/dashboard.html"&gt;dashboards&lt;/a&gt; Dask offers provide a wealth of information. For example, the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;/status&lt;/span&gt;&lt;/code&gt; dashboard gives an overview of the cluster activity, including worker CPU and memory utilization as well as which tasks are being processed:&lt;/p&gt;
&lt;figure class="align-default" id="dask-dashboard"&gt;
&lt;img alt="The `/status` dashboard gives an overview of the cluster activity." src="https://docs.coiled.io/_images/dask-dashboard.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Other dashboards focus on details about individual aspects of the Dask cluster, e.g., providing more details on worker activity (&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;/workers&lt;/span&gt;&lt;/code&gt;) or &lt;a class="reference external" href="https://blog.coiled.io/blog/gil-monitoring.html"&gt;GIL contention&lt;/a&gt; (&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;/individual-contention&lt;/span&gt;&lt;/code&gt;).
See &lt;a class="reference external" href="https://youtu.be/N_GqzcuGLCY"&gt;this walkthrough video&lt;/a&gt; for a tour of the available dashboards and how to leverage them.&lt;/p&gt;
&lt;p&gt;When looking at how useful dashboards are for observability, the results are mixed:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;👍 &lt;strong&gt;Powerful:&lt;/strong&gt; Each dashboard is designed with a specific question in mind that it helps to answer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;👍 &lt;strong&gt;Pre-configured:&lt;/strong&gt; Dashboards are pre-configured and require no additional setup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;👎 &lt;strong&gt;Ephemeral:&lt;/strong&gt; Dashboards are only available as long as the scheduler/worker is alive, and they have no mechanism for playing back data from the past.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="performance-reports"&gt;
&lt;h4&gt;Performance reports&lt;/h4&gt;
&lt;p&gt;&lt;a class="reference external" href="https://distributed.dask.org/en/stable/diagnosing-performance.html#performance-reports"&gt;Performance reports&lt;/a&gt; are a built-in tool allowing users to store and share a subset of the information available in the dashboards.
To create a performance report, users have to wrap their workload with the &lt;a class="reference external" href="https://distributed.dask.org/en/stable/api.html#distributed.performance_report"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;distributed.performance_report&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; context manager:&lt;/p&gt;
&lt;div class="highlight-python3 notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;performance_report&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;performance_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pycon_de.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This will collect all the telemetry and compile the report into a &lt;a class="reference external" href="https://blog.coiled.io/_static/content/observability-performance-report.html"&gt;shareable HTML file&lt;/a&gt;. For more, watch this &lt;a class="reference external" href="https://youtu.be/nTMGbkS761Q"&gt;3-minute demo&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="profiling"&gt;
&lt;h4&gt;Profiling&lt;/h4&gt;
&lt;p&gt;When analyzing performance, you won’t get around profiling.
Dask has a built-in &lt;a class="reference external" href="https://distributed.dask.org/en/stable/diagnosing-performance.html#statistical-profiling"&gt;statistical CPU profiler&lt;/a&gt; that you can access at &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;/profile&lt;/span&gt;&lt;/code&gt;.
This profiler has low overhead and is always running.
It can also aggregate profiling information across the cluster or show individual profiles for each worker.
However, the profiler is Python-only, so workloads that heavily use compiled packages will benefit less.&lt;/p&gt;
&lt;figure class="align-default" id="dask-dashboard-profile"&gt;
&lt;img alt="The Dask profiler summarizes information as a flame graph." src="https://docs.coiled.io/_images/profile.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;If you are interested in more detailed profiling of your Dask workloads or are heavily using compiled packages, I’d suggest taking a look at &lt;a class="reference external" href="https://github.com/gjoseph92/dask-pyspy"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask-pyspy&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;.
At a Dask Demo Day, &lt;a class="reference external" href="https://github.com/gjoseph92"&gt;Gabe Joseph&lt;/a&gt; gave &lt;a class="reference external" href="https://youtu.be/_x7oaSEJDjA?t=2763"&gt;an excellent demo&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="zooming-back-out"&gt;
&lt;h2&gt;Zooming back out&lt;/h2&gt;
&lt;p&gt;To summarize, I’d like to highlight a few key points:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;An observable system lets us ask: &lt;em&gt;“Why is this happening?”&lt;/em&gt; and answer questions about &lt;em&gt;unknown unknowns&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Telemetry data such as logging, metrics, and tracing are necessary but not sufficient for an observable system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Good diagnostic tooling is also needed to give users insights into the system and its components.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dask offers powerful but ephemeral telemetry data and diagnostic tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To fully leverage the telemetry data available in Dask, you need to centrally and durably collect them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If having good observability into Dask excites you, I’d encourage you to spin up your next Dask cluster on &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;; we manage a centralized observability platform for you.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/observability-with-dask.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;May 16, 2023&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;8m read</summary>
    <category term="coiled" label="coiled"/>
    <category term="dashboards" label="dashboards"/>
    <category term="dask" label="dask"/>
    <category term="distributed" label="distributed"/>
    <category term="grafana" label="grafana"/>
    <category term="logging" label="logging"/>
    <category term="metrics" label="metrics"/>
    <category term="observability" label="observability"/>
    <category term="prometheus" label="prometheus"/>
    <published>2023-05-16T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/gil-monitoring.html</id>
    <title>GIL monitoring in Dask</title>
    <updated>2023-05-15T00:00:00+00:00</updated>
    <author>
      <name>Miles Granger</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;May 15, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;2m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="gil-monitoring-in-dask"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://github.com/milesgranger"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/13764397?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Miles Granger&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="versionadded"&gt;
&lt;p&gt;&lt;span class="versionmodified added"&gt;Added in version 2023.4.1: &lt;/span&gt;&lt;span&gt;Support GIL contention monitoring.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;With release &lt;a class="reference external" href="https://github.com/dask/distributed/releases/tag/2023.4.1"&gt;2023.4.1&lt;/a&gt;,
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.distributed&lt;/span&gt;&lt;/code&gt; adds support for monitoring Python’s
&lt;a class="reference external" href="https://wiki.python.org/moin/GlobalInterpreterLock"&gt;Global Interpreter Lock (GIL)&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="Dashboard of Event Loop and GIL contention" id="gil-monitoring-dashboard" src="https://docs.coiled.io/_images/gil-monitoring-dashboard.gif" style="width: 60%;" /&gt;
&lt;p&gt;&lt;em&gt;New GIL “Contention” monitoring dashboard at &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;lt;&amp;lt;dashboard&lt;/span&gt; &lt;span class="pre"&gt;host&amp;gt;&amp;gt;/individual-contention&lt;/span&gt;&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;section id="why-should-i-care"&gt;
&lt;h2&gt;Why should I care?&lt;/h2&gt;
&lt;p&gt;Well, if you are running a single-threaded Python application, odds
are you don’t need to care much. In the context of Dask though,
it can potentially make a significant difference in performance.&lt;/p&gt;
&lt;p&gt;Code running on Dask runs in a threaded environment with other threads
doing compression, disk I/O, network I/O, and other user code.
Hopefully this code uses the GIL in a judicious manner.&lt;/p&gt;
&lt;p&gt;GIL monitoring through the dashboard and Prometheus metrics enables users to identify
workflows that can be improved to be more GIL friendly.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="alright-fancy-pants-how-do-i-activate-this-feature"&gt;
&lt;h2&gt;Alright fancy pants, how do I activate this feature?&lt;/h2&gt;
&lt;p&gt;Just have &lt;a class="reference external" href="https://github.com/milesgranger/gilknocker"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;gilknocker&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; installed. 🚀&lt;/p&gt;
&lt;p&gt;By default the &lt;a class="reference external" href="https://docs.dask.org/en/stable/configuration.html"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.config&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;
has &lt;a class="reference external" href="https://docs.dask.org/en/stable/configuration.html#distributed.admin.system-monitor.gil.enabled"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;distributed.admin.system-monitor.gil.enabled=True&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Tip&lt;/p&gt;
&lt;p&gt;It’s likely this will cover your needs, however one can additionally adjust
&lt;a class="reference external" href="https://docs.dask.org/en/stable/configuration.html#distributed.admin.system-monitor.gil.interval"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;distributed.admin.system-monitor.gil.interval&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;. At the time of this writing, it
defaults to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;1&lt;/span&gt; &lt;span class="pre"&gt;ms&lt;/span&gt;&lt;/code&gt; but can be changed to be a higher or lower frequency. This will affect
the frequency in which the GIL is polled to determine how busy it is.&lt;/p&gt;
&lt;/div&gt;
&lt;hr class="docutils" /&gt;
&lt;div class="note admonition"&gt;
&lt;p class="admonition-title"&gt;Example&lt;/p&gt;
&lt;p&gt;Here is an example adjusting the frequency for GIL polling to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;10us&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;distributed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;system-monitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;gil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10us&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="is-there-a-performance-impact-by-enabling-this-feature"&gt;
&lt;h2&gt;Is there a performance impact by enabling this feature?&lt;/h2&gt;
&lt;p&gt;It depends. 😉&lt;/p&gt;
&lt;p&gt;With that said, it’s quite likely you’ll experience no noticeable impact
on performance using the default settings. Should you increase the polling
interval to a higher frequency, then this will contribute to more GIL contention
and potentially lead to decreased performance.&lt;/p&gt;
&lt;p&gt;Let’s look at a comparison we ran before adding this feature,
with GIL monitoring enabled (left) and disabled (right).&lt;/p&gt;
&lt;img alt="Performance impact of adding GIL monitoring w/ default settings." id="benchmark-performance-impact" src="https://docs.coiled.io/_images/gil-monitoring-perf-confidence.png" style="width: 100%;" /&gt;
&lt;div class="admonition-what-am-i-looking-at admonition"&gt;
&lt;p class="admonition-title"&gt;What am I looking at?&lt;/p&gt;
&lt;p&gt;In an effort to preserve stable performance, we use &lt;a class="reference external" href="https://blog.coiled.io/blog/performance-testing.html#coiled-benchmarks"&gt;Coiled’s A/B benchmarks&lt;/a&gt; to show the
negligible performance impact of GIL monitoring.&lt;/p&gt;
&lt;p&gt;Above we have a comparison of confidence intervals against the null-hypothesis, that is to say,
not monitoring the GIL.&lt;/p&gt;
&lt;p&gt;Each row represents a single benchmark test, the blue meaning
improvement and red a regression. There is always variance to some extent,
represented by the spread horizontally and shading. However, the more
similar the two charts look, the less likely there is a performance difference.&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;With GIL monitoring you’ll be able to view how your code behaves in this context
and enable you to make more informed decisions on performance improvements.&lt;/p&gt;
&lt;p&gt;To enable monitoring the GIL, you only need to install &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;gilknocker&lt;/span&gt;&lt;/code&gt; which is available
on &lt;a class="reference external" href="https://anaconda.org/conda-forge/gilknocker"&gt;conda-forge&lt;/a&gt; and
&lt;a class="reference external" href="https://pypi.org/project/gilknocker/"&gt;PyPI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While these metrics are available with
&lt;a class="reference external" href="https://distributed.dask.org/en/stable/prometheus.html"&gt;Prometheus&lt;/a&gt;,
you’ll also get an updated dashboard called “Contention” located at
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;lt;&amp;lt;dashboard&lt;/span&gt; &lt;span class="pre"&gt;host&amp;gt;&amp;gt;/individual-contention&lt;/span&gt;&lt;/code&gt; displaying the Event Loop and GIL
contention metrics in realtime.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/gil-monitoring.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;May 15, 2023</summary>
    <category term="dask" label="dask"/>
    <category term="distributed" label="distributed"/>
    <category term="gil" label="gil"/>
    <category term="monitoring" label="monitoring"/>
    <category term="prometheus" label="prometheus"/>
    <published>2023-05-15T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/performance-testing.html</id>
    <title>Performance testing at Coiled</title>
    <updated>2023-05-05T00:00:00+00:00</updated>
    <author>
      <name>Guido Imperiale</name>
    </author>
    <content type="html">&lt;section id="performance-testing-at-coiled"&gt;

&lt;p&gt;At &lt;a class="reference external" href="https://coiled.io"&gt;Coiled&lt;/a&gt; we develop &lt;a class="reference external" href="https://dask.org"&gt;Dask&lt;/a&gt; and automatically
deploy it to large clusters of cloud workers (sometimes 1000+ EC2 instances at once!).
In order to avoid surprises when we publish a new release, Dask needs to be covered by a
comprehensive battery of tests — both for functionality and performance.&lt;/p&gt;
&lt;section id="functional-regression-testing"&gt;
&lt;h2&gt;Functional regression testing&lt;/h2&gt;
&lt;p&gt;Dask is thoroughly covered by functional unit tests which verify that, when you feed
certain inputs into the system, you obtain the expected outputs.
Similarly to most software development projects nowadays, we use a &lt;strong&gt;CI-assisted,
automated&lt;/strong&gt; workflow for it:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;A developer opens a PR;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The PR code is automatically executed in CI against all functional regression tests;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If, and only if, all tests are green the PR can be merged into main.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key features of this system are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;It re-runs on every change;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nobody needs to explicitly remember to start the tests;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It runs in a stable, reproducible environment;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It re-tests &lt;em&gt;everything&lt;/em&gt;, not just what the developers think may be impacted.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of the above should surprise anyone these days. What we do, that is definitely more
out of the ordinary, is we apply the exact same paradigms commonly used for
functional regression testing also to &lt;em&gt;performance&lt;/em&gt; testing.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-we-did-in-the-past"&gt;
&lt;h2&gt;What we did in the past&lt;/h2&gt;
&lt;p&gt;Historically, whenever we had to decide whether to merge a Dask PR with a likely
performance impact, we ran ad hoc manual tests on one or two use cases that we
thought would be relevant, which we found was not sustainable:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;It was labor-intensive, as the developers would need to rebuild the use case and
manually meter it every time;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PRs frequently impacted unexpected use cases: a PR that would produce a speed-up
where expected sometimes caused a regression where nobody was looking;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A cluster with hundreds of workers very rarely exhibits deterministic performance; if
you run the same test twice there will be some variance in the measured metrics.
One-off manual tests tend to ignore variance and instead take the first measure as
golden.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Clearly something had to change.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-about-asv"&gt;
&lt;h2&gt;What about ASV?&lt;/h2&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="ASV" src="https://docs.coiled.io/_images/asv.png" style="width: 410.2px; height: 239.39999999999998px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asv.readthedocs.io/"&gt;Airspeed Velocity (ASV)&lt;/a&gt; is a great microbenchmarking
tool. Emphasis however should be put on the &lt;em&gt;micro&lt;/em&gt; bit in the name, as there are
important limitations:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Everything must run on a single box;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You must trust performance to scale linearly;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You are only interested in end-to-end runtime.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When testing Dask:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Performance on a LocalCluster is completely different from that on AWS. Network
latency and bandwidth play a major role, as do other bottlenecks such as I/O
throughput. You can’t expect the behaviour of a single box to be representative of a
cloud cluster, even with the same amount of CPUs and RAM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can’t expect a scaled down problem running on 4 CPUs to be representative of a
production size problem running on 400 CPUs. Again, bottlenecks such as the scheduler,
S3 throughput, and network bandwidth play a fundamental part in this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are many metrics other than end-to-end runtime such as memory and network
usage, as well as the &lt;em&gt;breakdown&lt;/em&gt; of the totals.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="coiled-benchmarks"&gt;
&lt;h2&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks"&gt;coiled/benchmarks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We created a &lt;a class="reference external" href="https://github.com/coiled/benchmarks"&gt;GitHub repository&lt;/a&gt; and wrote a
comprehensive suite of tests in it. Each test spawns a medium-sized Coiled cluster and
runs a realistic use case, such as:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/d7e5b0065741b73e55aec6c84687099ebcd2afa3/tests/benchmarks/test_array.py#L205-L216"&gt;Matrix dot product&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/d7e5b0065741b73e55aec6c84687099ebcd2afa3/tests/benchmarks/test_array.py#L219-L230"&gt;Overlapping computations&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/d7e5b0065741b73e55aec6c84687099ebcd2afa3/tests/benchmarks/test_h2o.py"&gt;Dataframe join and groupby&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/d7e5b0065741b73e55aec6c84687099ebcd2afa3/tests/benchmarks/test_parquet.py#L79-L94"&gt;Parquet I/O on S3&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/benchmarks/blob/d7e5b0065741b73e55aec6c84687099ebcd2afa3/tests/benchmarks/test_xgboost.py"&gt;ML training with XGBoost&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each test case is measured on selected performance metrics; at the moment, end-to-end
runtime, peak memory usage, and mean memory usage; we plan to add more in the future.&lt;/p&gt;
&lt;p&gt;The test suite runs in two modes: nightly regression tests and A/B tests.&lt;/p&gt;
&lt;section id="nightly-tests"&gt;
&lt;h3&gt;Nightly tests&lt;/h3&gt;
&lt;p&gt;Every night, CI will automatically:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Run all tests;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Detect regressions in test runtimes;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open a GitHub issue if performance degrades from previous days;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Publish a report showing several months worth of results.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Nightly tests report" src="https://docs.coiled.io/_images/overnight.png" /&gt;&lt;/p&gt;
&lt;div class="tip admonition"&gt;
&lt;p class="admonition-title"&gt;Case study&lt;/p&gt;
&lt;p&gt;The test shown in the above image exhibited a very consistent end-to-end runtime
throughout the months. Then, one fateful day in early March, somebody merged a PR into
main that caused this test to slow down by a factor of 4x!&lt;/p&gt;
&lt;p&gt;The next morning Dask maintainers woke up to &lt;a class="reference external" href="https://github.com/coiled/benchmarks/issues/702"&gt;a new GitHub issue&lt;/a&gt; highlighting the regression. The issue
was promptly investigated and remediated shortly thereafter.&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="a-b-tests"&gt;
&lt;h3&gt;A/B tests&lt;/h3&gt;
&lt;p&gt;Before we merge major PRs we push to the coiled/benchmarks CI two or more conda
environments:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A (a.k.a. baseline; what we know and love)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;depedencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;- dask ==2023.4.1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;- distributed ==2023.4.1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;B (a PR, the next release, whatever)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Everything same as baseline&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;git+https://github.com/dask/dask@b85bf5be72b02342222c8a0452596539fce19bce&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;git+https://github.com/dask/distributed@6dc4c24b7a01b93952bbfd95d3ecba043580c245&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The conda environment files can be accompanied by Dask config files and/or parameters to
the Coiled cluster; this allows, for example, to test how switching on/off work stealing
affects performance or how 10 workers with 4 CPUs each compare against 20 workers with
2 CPUs each.&lt;/p&gt;
&lt;p&gt;You can also increase cluster size on the fly; most test data scales dynamically with
cluster size so that end-to-end runtime remains roughly constant:&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# For all tests using the small_client fixture&lt;/span&gt;
&lt;span class="nt"&gt;small_cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;n_workers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;worker_vm_types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;m6i.large&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 2 CPU, 8 GiB&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;test_dot_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;small_client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;small_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Typically 76.66 GiB&lt;/span&gt;
    &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scaled_array_shape_quadratic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;4.5 GiB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Once the test settings are uploaded CI will run all tests, multiple times, in the
A and B configurations and will generate a statistical analysis of the differences.&lt;/p&gt;
&lt;section id="confidence-report"&gt;
&lt;h4&gt;Confidence report&lt;/h4&gt;
&lt;p&gt;This A/B report shows, for every test case, how likely it is that B is better/worse than
A in end-to-end runtime, memory usage, etc. This is an aggregation of 5+ runs for B vs.
5+ runs for A; the report shows p values as transparency:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A/B confidence report" src="https://docs.coiled.io/_images/AB-confidence.png" /&gt;&lt;/p&gt;
&lt;div class="tip admonition"&gt;
&lt;p class="admonition-title"&gt;Case study&lt;/p&gt;
&lt;p&gt;The above plot is the output of a preliminary performance test for a PR introducing
&lt;a class="reference external" href="https://github.com/dask/distributed/issues/4424#issuecomment-1494508315"&gt;Asynchronous disk
access&lt;/a&gt;. The PR
caused &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;test_set_index&lt;/span&gt;&lt;/code&gt; (4&lt;sup&gt;th&lt;/sup&gt; row from the top) to become at least 40% faster (p=95%) and
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;test_spilling&lt;/span&gt;&lt;/code&gt; (middle of the plot) to become at least 60% &lt;em&gt;slower&lt;/em&gt; (p=84%).
&lt;strong&gt;Both results were completely unexpected&lt;/strong&gt;; the PR did not get merged.&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="null-hypothesis"&gt;
&lt;h4&gt;Null hypothesis&lt;/h4&gt;
&lt;p&gt;Some tests are noisier than others.
CI automatically runs tests for the baseline environment twice and compares them in A/B.
If the null hypothesis shows changes, then information from that test should be treated
as noise in the actual A/B tests.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A/B confidence report" src="https://docs.coiled.io/_images/null-hypothesis.png" /&gt;&lt;/p&gt;
&lt;div class="tip admonition"&gt;
&lt;p class="admonition-title"&gt;Case study&lt;/p&gt;
&lt;p&gt;In the above plot, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;test_single_future&lt;/span&gt;&lt;/code&gt; seems to be sometimes a bit faster, sometimes a
bit slower in the B setup compared to A. Except that A and B are identical. This is
because the test is so noisy that, out of sheer luck, the runs in B were faster on
average than those in A. The test should be ignored when looking at actual A/B
situations. If you want meaningful results out of it you should either run it many more
times or redesign it to be less noisy.&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="per-test-details"&gt;
&lt;h4&gt;Per test details&lt;/h4&gt;
&lt;p&gt;For each test we have a more detailed plot showing median &lt;em&gt;(blue bar)&lt;/em&gt;, mean
&lt;em&gt;(crosshair)&lt;/em&gt;, and standard deviation &lt;em&gt;(black horizontal line)&lt;/em&gt; of the observed metrics:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Single test, low variance - detail)" src="https://docs.coiled.io/_images/pertest-good.png" /&gt;
&lt;img alt="Single test, low variance - A/B)" src="https://docs.coiled.io/_images/pertest-AB-good.png" /&gt;&lt;/p&gt;
&lt;div class="tip admonition"&gt;
&lt;p class="admonition-title"&gt;Case study&lt;/p&gt;
&lt;p&gt;This plot is from the same A/B test run that produced the confidence reports shown
before (the relevant bit is repeated right below for convenience). The runtime in the
two B setups (&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;async_spill&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;async_spill_nohack&lt;/span&gt;&lt;/code&gt;) is solidly less than that in the
baseline and null hypothesis.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Single test, high variance - detail" src="https://docs.coiled.io/_images/pertest-noisy.png" /&gt;
&lt;img alt="Single test, high variance - A/B" src="https://docs.coiled.io/_images/pertest-AB-noisy.png" /&gt;&lt;/p&gt;
&lt;div class="tip admonition"&gt;
&lt;p class="admonition-title"&gt;Case study&lt;/p&gt;
&lt;p&gt;In this case, instead, you can clearly see that the noise-to-signal ratio is so high
that there’s nothing useful to be gathered out of this test.&lt;/p&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="introspection-with-grafana"&gt;
&lt;h3&gt;Introspection with Grafana&lt;/h3&gt;
&lt;p&gt;Finally, from the aggregated statistical reports, using Prometheus and
&lt;a class="reference external" href="https://tinyurl.com/yvw2z3ye"&gt;Grafana&lt;/a&gt;
we can drill down into each individual
run that underlies them and observe what happened during it.
This gives us information such as the runtime of each task group, the breakdown
of memory usage, network throughput, etc.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://tinyurl.com/yvw2z3ye"&gt;&lt;img alt="Grafana" src="https://docs.coiled.io/_images/grafana1.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="faq"&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Isn’t this very expensive?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Around $5 for the nightly tests and around $50 for a typical A/B test. The cost
increases if you decide to increase the cluster size to hundreds of workers, but it’s
not something needed every day.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Could you simulate a slow network on localhost instead?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; In theory, yes. In practice, we’d periodically need to make sure that the
simulation behaves like the real thing. We’d also need to simulate actual task
execution, as a single host would not be able to cope with the CPU and memory usage of a
real cluster. We’d need to simulate how S3 behaves when multiple hosts request different
files from it. There are many other pitfalls to consider.&lt;/p&gt;
&lt;p&gt;Not saying that simulation is always a bad idea, just that it takes a &lt;em&gt;substantial&lt;/em&gt;
investment of time and effort to set up correctly and to ensure it keeps reflecting
reality. &lt;a class="reference external" href="https://news.sky.com/story/starship-launch-latest-elon-musks-spacex-gets-ready-for-first-test-launch-of-worlds-biggest-and-most-powerful-rocket-12859338"&gt;Does each test run cost you $10
billion?&lt;/a&gt;
Maybe you should figure out how to simulate it instead. Does it cost $50? Just not worth
it.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-s-next"&gt;
&lt;h2&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;The current toolkit is extremely powerful and light years ahead of the previous
condition of testing things by hand. However, there are still large margins for
improvement:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Make it more user-friendly;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make it more interactive. For example, if you see an increase in end-to-end
runtime, you probably want to know &lt;em&gt;where&lt;/em&gt; the extra time is spent. &lt;a class="reference external" href="https://github.com/dask/distributed/issues?q=fine+performance+metrics"&gt;Dask is already
producing this
information&lt;/a&gt;;
we’re working on properly storing, aggregating, and visualizing it;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Observe more metrics such as disk and network usage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, we’re exploring the possibility of opening this system to Coiled customers! For
example, imagine if you could tag some of your runs as “A”, some others as “B”, and then
ask the &lt;a class="reference external" href="https://cloud.coiled.io"&gt;Coiled dashboard&lt;/a&gt; to show you statistical insights on
the differences between the two sets. You would be able to test not only different
versions of Dask and other dependencies, but also different versions of your own
algorithm, different cluster configurations, etc.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/performance-testing.html"/>
    <summary>At Coiled we develop Dask and automatically
deploy it to large clusters of cloud workers (sometimes 1000+ EC2 instances at once!).
In order to avoid surprises when we publish a new release, Dask needs to be covered by a
comprehensive battery of tests — both for functionality and performance.Nightly tests report</summary>
    <category term="coiled" label="coiled"/>
    <category term="dask" label="dask"/>
    <category term="distributed" label="distributed"/>
    <category term="performance" label="performance"/>
    <category term="testing" label="testing"/>
    <published>2023-05-05T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-graviton.html</id>
    <title>How well does Dask run on Graviton?</title>
    <updated>2023-05-05T00:00:00+00:00</updated>
    <author>
      <name>Nat Tabris</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;May 5, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="how-well-does-dask-run-on-graviton"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Sarah Johnson, Nat Tabris&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;ARM-based processors are known for matching performance of x86-based instance types at a lower cost, since they consume far less energy for the same performance. It’s not surprising then that some companies, like &lt;a class="reference external" href="https://www.honeycomb.io/blog/present-future-arm-aws-graviton-honeycomb"&gt;Honeycomb&lt;/a&gt;, are switching their entire infrastructure to ARM.&lt;/p&gt;
&lt;p&gt;We ran a number of Dask workloads on both x86- and ARM-based instance types and found costs were typically 20-30% lower when using ARM. We also tested out the latest generation of Amazon’s ARM-based processors Graviton3 instance types and looked at performance for a compute-heavy workload using Dask and XGBoost.&lt;/p&gt;
&lt;section id="aws-graviton"&gt;
&lt;h2&gt;AWS Graviton&lt;/h2&gt;
&lt;p&gt;Intel designs all Intel chips (including Intel x86 processors, e.g.), but many different companies design ARM chips. This lets different companies tailor the design to specific use cases. Apple, for example, designs ARM processors that deliver a nice balance of performance and energy-efficiency for phones and laptops. Amazon has designed Graviton processors specifically for their cloud servers, and the latest generation Graviton3 is especially optimized for compute-intensive workloads. We launched Dask clusters on AWS using Intel, Graviton2, and Graviton3 processors to see how switching to ARM makes a difference.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-on-graviton2"&gt;
&lt;h2&gt;Dask on Graviton2&lt;/h2&gt;
&lt;p&gt;Previously, we used Dask and Coiled to process ~ 1TB of data to see &lt;a class="reference external" href="https://www.coiled.io/blog/how-popular-is-matplotlib"&gt;how popular Matplotlib has been in scientific articles over the past decade&lt;/a&gt;. The dataset is publicly available in S3, saved as over 5,000 tar files, each of which contain a directory of articles. We extract each directory and do some minimal processing, which takes ~ 7 minutes with a 100-worker Coiled cluster.&lt;/p&gt;
&lt;p&gt;We ran this workload 5 times on both x86-based t3 instances and ARM-based t4g instances and found, on average, a 30% cost reduction.&lt;/p&gt;
&lt;figure class="align-default" id="matplotlib"&gt;
&lt;img alt="bar chart of AWS cost vs. processor type" src="https://docs.coiled.io/_images/arm-matplotlib.png" style="width: 572.25px; height: 329.25px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;This workload doesn’t use much CPU or memory, and is instead primarily relying on network bandwidth. The general-purpose x86-based t3 and ARM-based t4g instance types are a good fit for this type of workload, since they are readily available and provide more than enough performance without additional charges.&lt;/p&gt;
&lt;p&gt;To see how robust these results were across a variety of workloads, we also ran &lt;a class="reference external" href="https://blog.coiled.io/blog/performance-testing.html#coiled-benchmarks"&gt;a number of benchmarks&lt;/a&gt; for common Dask Array and Dask DataFrame operations. Across 19 different benchmarks, the ARM-based t4g instances were reliably cheaper for all but one, where there was a 2.3% cost increase. Cost savings were around 30% averaging across all tests, ranging from 15-55%.&lt;/p&gt;
&lt;figure class="align-default" id="t4g-benchmark"&gt;
&lt;img alt="Percent difference in estimated AWS costs for workers." src="https://docs.coiled.io/_images/arm-t4g-snakebench.png" style="width: 529.0px; height: 481.0px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Percent difference in estimated AWS costs for t4g (ARM-based) vs. t3 workers (x86-based)&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;On the high end, &lt;a class="reference external" href="https://github.com/coiled/coiled-runtime/blob/39d35b5347554b2bda685052ec23de889bd2d0be/tests/benchmarks/test_array.py#L145-L188"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;test_vorticity&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;, for example, was 46% cheaper (on average across three trials). This test is a simplification of a Dask Array operation commonly used in geoscience workloads and is primarily compute-bound.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-on-graviton3"&gt;
&lt;h2&gt;Dask on Graviton3&lt;/h2&gt;
&lt;p&gt;In February, AWS launched additional general-purpose Graviton3-based instance types, the m7g, with reportedly up to 25% better performance than their predecessors. For more details on Graviton3 performance improvements, we recommend this &lt;a class="reference external" href="https://chipsandcheese.com/2022/05/29/graviton-3-first-impressions/"&gt;deep dive from Chips and Cheese&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We used XGBoost with Dask and Coiled to &lt;a class="reference external" href="https://github.com/coiled/dask-xgboost-nyctaxi/blob/9d1959f92b9fb811ad227268246ae7019bc8154e/Modeling%201%20-%20Train%20an%20XGBoost%20Model%20with%20Dask.ipynb"&gt;predict the trip duration for Uber and Lyft rides in NYC&lt;/a&gt; using the &lt;a class="reference external" href="https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page"&gt;publicly available NYC Taxi dataset&lt;/a&gt;. In this workload we load the dataset, do some basic feature engineering, train and then cross-validate our XGBoost model. This takes ~5 minutes on a 45-worker Coiled cluster with 3 folds for cross-validation.&lt;/p&gt;
&lt;p&gt;We ran this workload 5 times on both x86-based m6i instances and ARM-based m7g instances and found, on average, a 23% cost savings.&lt;/p&gt;
&lt;figure class="align-default" id="xgboost"&gt;
&lt;img alt="bar chart of AWS cost vs. processor type" src="https://docs.coiled.io/_images/arm-xgboost.png" style="width: 573.0px; height: 329.25px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;As opposed to the network-bound Matplotlib example, this workload is primarily computationally- and memory-bound, with total worker memory reaching just under 400 GB and workers reaching nearly 75% CPU utilization. For these types of workloads, instance type families that balance compute and memory are a good option.&lt;/p&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Chart of average worker percent CPU utilization and average worker memory." src="https://docs.coiled.io/_images/arm-xgboost-resources.png" style="width: 461.5px; height: 300.0px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Average worker percent CPU utilization (top) and average worker memory (bottom) for XGBoost workload&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Again, to get a sense for how robust these cost savings are, we ran a the &lt;a class="reference external" href="https://blog.coiled.io/blog/performance-testing.html#coiled-benchmarks"&gt;same benchmarks as above&lt;/a&gt; comparing m6i and m7g instance types. Across the 19 different benchmarks, the ARM-based m7g instances were reliably cheaper for all but three, where ARM was, at most 25% more expensive. Where ARM was cheaper, estimated cost savings ranged from 5-55%, with an average of ~ 20% cost savings across tests.&lt;/p&gt;
&lt;figure class="align-default" id="m7g-benchmark"&gt;
&lt;img alt="Percent difference in estimated AWS costs for workers." src="https://docs.coiled.io/_images/arm-m7g-snakebench.png" style="width: 529.0px; height: 480.0px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Percent difference in estimated AWS costs for m7g (ARM-based) vs. m6i workers (x86-based)&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Focusing again at the primarily compute-bound &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;test_vorticity&lt;/span&gt;&lt;/code&gt;, m7g workers were 48% cheaper than using a cluster with m6i workers.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="using-arm-with-coiled"&gt;
&lt;h2&gt;Using ARM with Coiled&lt;/h2&gt;
&lt;p&gt;Graviton processors are designed by Amazon specifically with cloud performance in mind &lt;em&gt;and&lt;/em&gt; they consume far less energy. So why not try them for your own workloads? One challenge is ensuring your compiled dependencies are ARM-compatible. In many cases, the dependencies you’ll need are prebuilt for both x86 and ARM. If you’re using custom dependencies or an older version of a library, however, you may run into issues.&lt;/p&gt;
&lt;p&gt;As of Coiled version 0.6.0, your local Python environment will be replicated on your cluster, including any necessary platform-specific dependencies, making it easy to create a cluster with ARM-based instances.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# ARM instance types for scheduler and workers&lt;/span&gt;
    &lt;span class="n"&gt;arm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can see for yourself whether switching to ARM-based clusters saves on AWS costs or improves performance (see &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/arm.html"&gt;our documentation&lt;/a&gt; for more details). Try it out and let us know how it goes!&lt;/p&gt;
&lt;p&gt;If you’re not already using Coiled, it’s free to try out and easy to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/getting_started.html"&gt;get started&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-graviton.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;May 5, 2023bar chart of AWS cost vs. processor type</summary>
    <category term="ARM" label="ARM"/>
    <category term="costs" label="costs"/>
    <category term="graviton" label="graviton"/>
    <published>2023-05-05T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-upstream-testing.html</id>
    <title>Upstream testing in Dask</title>
    <updated>2023-04-18T00:00:00+00:00</updated>
    <author>
      <name>James Bourbeau</name>
    </author>
    <content type="html">&lt;section id="upstream-testing-in-dask"&gt;

&lt;p&gt;&lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; has deep integrations with other libraries in the PyData ecosystem like NumPy, pandas, Zarr, PyArrow, and more.
Part of providing a good experience for Dask users is making sure that Dask continues to work well with this community
of libraries as they push out new releases. This post walks through how Dask maintainers proactively ensure Dask
continuously works with its surrounding ecosystem.&lt;/p&gt;
&lt;section id="nightly-testing"&gt;
&lt;h2&gt;Nightly testing&lt;/h2&gt;
&lt;p&gt;Dask has a &lt;a class="reference external" href="https://github.com/dask/dask/blob/834a19eaeb6a5d756ca4ea90b56ca9ac943cb051/.github/workflows/upstream.yml"&gt;dedicated CI build&lt;/a&gt;
that runs Dask’s normal test suite once a day with unreleased, nightly versions of
several libraries installed. This lets us check whether or not a recent change in a library like NumPy or pandas
breaks some aspect of Dask’s functionality.&lt;/p&gt;
&lt;p&gt;To increase visibility when such a breakage occurs, as part of the upstream CI build, an
issue is automatically opened that provides a summary of what tests failed and links to the build
logs for the corresponding failure (&lt;a class="reference external" href="https://github.com/dask/dask/issues/9736"&gt;here’s an example issue&lt;/a&gt;).&lt;/p&gt;
&lt;div align="center"&gt;
&lt;p&gt;&lt;img alt="example-upstream-CI-issue" src="https://docs.coiled.io/_images/example-upstream-CI-issue.png" /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This makes it less likely that a failing upstream build goes unnoticed.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-things-can-break-and-are-fixed"&gt;
&lt;h2&gt;How things can break and are fixed&lt;/h2&gt;
&lt;p&gt;There are usually two different ways in which things break. Either:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;A library made an intentional change in behavior and a corresponding compatibility change needs to be made in
Dask (the next section has an example of this case).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There was some unintentional consequence of a change made in a library that resulted in a breakage in Dask.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When the latter case occurs, Dask maintainers can then engage with other library maintainers to resolve the
unintended breakage. This all happens before any libraries push out a new release, so no user code breaks.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-pandas-2-0"&gt;
&lt;h2&gt;Example: pandas 2.0&lt;/h2&gt;
&lt;p&gt;One specific example of this process in action is the recent pandas 2.0 release.
This is a major version release and contains significant breaking changes like removing deprecated functionality.&lt;/p&gt;
&lt;p&gt;As these breaking changes were merged into pandas, we started seeing related failures in Dask’s upstream CI build.
Dask maintainers were then able to add
&lt;a class="reference external" href="https://github.com/dask/dask/pulls?q=is%3Apr+%22pandas+2.0%22+in%3Atitle"&gt;a variety of compatibility changes&lt;/a&gt; so
that Dask works well with pandas 2.0 immediately.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="acknowledgements"&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Special thanks to &lt;a class="reference external" href="https://github.com/keewis"&gt;Justus Magin&lt;/a&gt; for his work on the
&lt;a class="reference external" href="https://github.com/xarray-contrib/issue-from-pytest-log"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;xarray-contrib/issue-from-pytest-log&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; GitHub action.
We’ve found this to be really convenient for easily opening up GitHub issues when test failures occur.&lt;/p&gt;
&lt;p&gt;Also, thanks to &lt;a class="reference external" href="https://github.com/j-bennet"&gt;Irina Truong&lt;/a&gt; (&lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;), &lt;a class="reference external" href="https://github.com/phofl"&gt;Patrick Hoefler&lt;/a&gt; (&lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt;),
and &lt;a class="reference external" href="https://github.com/mroeschke"&gt;Matthew Roeschke&lt;/a&gt; (&lt;a class="reference external" href="https://rapids.ai/"&gt;NVIDIA&lt;/a&gt;) for their efforts ensuring pandas and Dask
continue to work together.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-upstream-testing.html"/>
    <summary>Dask has deep integrations with other libraries in the PyData ecosystem like NumPy, pandas, Zarr, PyArrow, and more.
Part of providing a good experience for Dask users is making sure that Dask continues to work well with this community
of libraries as they push out new releases. This post walks through how Dask maintainers proactively ensure Dask
continuously works with its surrounding ecosystem.</summary>
    <category term="dask" label="dask"/>
    <category term="ecosystem" label="ecosystem"/>
    <category term="pydata" label="pydata"/>
    <published>2023-04-18T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/burstable-vs-nonburstable.html</id>
    <title>Burstable vs non-burstable AWS instance types for data engineering workloads</title>
    <updated>2023-04-04T00:00:00+00:00</updated>
    <author>
      <name>Nat Tabris</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Apr 4, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;8m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="burstable-vs-non-burstable-aws-instance-types-for-data-engineering-workloads"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://tabris.us/"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/46289310?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Nat Tabris&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;There are many instance types to choose from on AWS. In this post, we’ll look at one choice you can make—burstable vs non-burstable instances—and show how the “cheaper” burstable option can end up being more expensive for data engineering workloads.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;AWS offers &lt;strong&gt;burstable&lt;/strong&gt; EC2 instances with a lower base price than the corresponding non-burstable instance types.&lt;/p&gt;
&lt;figure class="align-default" id="burstable-hourly-rates"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-hourly-rates.svg" src="https://docs.coiled.io/_images/burstable-hourly-rates.svg" style="width: 75%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Burstable instances are perfect for workloads like web servers which have low CPU utilization most of the time, but occasionally fully utilize the CPU—web traffic tends to be bursty.&lt;/p&gt;
&lt;p&gt;What about data science/engineering workloads? For these use-cases, burstable instances are worse than we expected. For a wide-range of data workloads, non-burstable instances are both cheaper per hour &lt;em&gt;and&lt;/em&gt; faster.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;Coiled offers managed Dask in the cloud. Based on what we’ve learned about burstable instances, we’re changing the default Intel-based instance types on AWS from t3 to m6i instances. This is one of the many ways that we help users run Python at scale efficiently (as well as cheaply and securely).&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;In the rest of the post, we’ll see the impact on overall costs across our benchmark suite, and then dig into a few workloads to see how non-burstable instances provide better performance for CPU, disk, and network.&lt;/p&gt;
&lt;section id="overall-benchmark-costs"&gt;
&lt;h2&gt;Overall Benchmark Costs&lt;/h2&gt;
&lt;p&gt;Coiled runs a suite of Python/Dask benchmarks on AWS. This collection of  ~100 benchmarks cover common data science, data engineering, and machine learning workloads. A typical run takes around 150 instance-hours (though as we’ll see, this depends on the choice of instance type).&lt;/p&gt;
&lt;p&gt;When we switched from burstable to non-burstable instance types, we saw a significant reduction in cost. For instance, comparing two individual runs, we saw costs go from $7.13 (burstable) to $3.89 (non-burstable).&lt;/p&gt;
&lt;figure class="align-default" id="burstable-surcharge"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-base-extra.svg" src="https://docs.coiled.io/_images/burstable-base-extra.svg" style="width: 75%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;This cost difference is due to two things:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;The burstable surcharge&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Poor performance of burstable instances&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’ll go into more detail on each of these in the sections below.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="id1"&gt;
&lt;h2&gt;Burstable surcharge&lt;/h2&gt;
&lt;p&gt;If you take a look at the prices that AWS lists for EC2 instances (here’s the &lt;a class="reference external" href="https://aws.amazon.com/ec2/pricing/on-demand/"&gt;on-demand pricing&lt;/a&gt;), you’ll see that t3.large instances are cheaper than m6i.large instances. So why is the total cost for m6i.large so much lower?&lt;/p&gt;
&lt;p&gt;With burstable instances, the base price covers CPU utilization up to some limit: 30% for t3.large, 40% for t3.xlarge and t3.2xlarge. This base price is about 15% cheaper than the price of the corresponding non-burstable instances.&lt;/p&gt;
&lt;p&gt;With burstable instances there are two types of costs you need to watch: the base price, and the extra CPU credit cost. What’s this extra cost about?&lt;/p&gt;
&lt;p&gt;Burstable t3.large instances have a “baseline” of 30% CPU utilization. When your utilization is lower, you accumulate CPU credits. When your utilization is higher, you use the credits you’ve accumulated, or if you don’t have enough credits, you pay extra.&lt;/p&gt;
&lt;p&gt;In the case of our benchmark suite, these extra CPU credits were 28% of the total non-burstable compute cost and made our burstable compute significantly more expensive than non-burstable compute.&lt;/p&gt;
&lt;p&gt;You can see these costs for yourself on instances in your account as explained in &lt;a class="reference external" href="https://blog.coiled.io/blog/aws-cost-explorer-tips.html#burstable-instances"&gt;our blog post about AWS Cost Explorer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;More generally, using m6i.large instances are cheaper than t3.large instances when you have sustained CPU utilization above 42.5%. See &lt;a class="reference external" href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-unlimited-mode-concepts.html#when-to-use-unlimited-mode"&gt;this AWS doc&lt;/a&gt; for details about how this is calculated.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;&lt;strong&gt;What if I only want to pay the base price, nothing extra?&lt;/strong&gt;
If you never want to pay for extra CPU credits, you can use “standard” rather than “unlimited” mode with burstable instances. In this case, instead of paying for CPU credits, your performance is throttled. While this may make sense for bursty use-cases, this is a poor fit for compute-heavy workloads: you’d be paying 85% of the cost for 40% (or less) of the performance of a non-burstable instance.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="poor-performance-of-burstable-instances"&gt;
&lt;h2&gt;Poor Performance of Burstable Instances&lt;/h2&gt;
&lt;p&gt;The benchmarks ran faster on m6i.large instances than on t3.large. At a high-level, we can see this by comparing the total number of instance-hours:&lt;/p&gt;
&lt;figure class="align-default" id="benchmark-instance-hours"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-benchmark-instance-hours.svg" src="https://docs.coiled.io/_images/burstable-benchmark-instance-hours.svg" style="width: 75%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Let’s dig into why the non-burstable instances are faster than the burstable instances.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="modern-chipset"&gt;
&lt;h2&gt;Modern Chipset&lt;/h2&gt;
&lt;p&gt;Part of the explanation is that m6i offers faster and newer Intel CPUs, as well as faster networking:&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-center"&gt;&lt;p&gt;&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-center"&gt;&lt;p&gt;&lt;strong&gt;Burstable (t3.large)&lt;/strong&gt;&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-center"&gt;&lt;p&gt;&lt;strong&gt;Non-burstable (m6i.large)&lt;/strong&gt;&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-center"&gt;&lt;p&gt;&lt;strong&gt;Processor&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;Up to 3.1 GHz Intel Xeon Scalable processor (Skylake 8175M or Cascade Lake 8259CL)&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;Up to 3.5 GHz 3rd Generation Intel Xeon Scalable processors (Ice Lake 8375C)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td class="text-center"&gt;&lt;p&gt;&lt;strong&gt;Network Performance&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;Up to 5 Gbps&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-center"&gt;&lt;p&gt;Up to 10 Gbps&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;But that’s not the full story. To get a deeper understanding, let’s take a closer look at a few different workloads.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="workload-1-network-and-disk-with-array-workload"&gt;
&lt;h2&gt;Workload 1: Network and disk with array workload&lt;/h2&gt;
&lt;p&gt;The following workload stresses network and disk. We construct a large random array (too large for memory) and then rechunk it twice across the network, forcing a full shuffle of all of the data:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rechunk&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rechunk&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled collects extensive metrics on workloads. We publicly host the metrics for the benchmark suite on a &lt;a class="reference external" href="https://benchmarks-grafana.oss.coiledhq.com"&gt;public Grafana server&lt;/a&gt; to aid community development. We can go and look at them there.&lt;/p&gt;
&lt;p&gt;Let’s take a look at the network and disk metrics for the same tests on m6i.large and t3.large instances, moving 1.5TB over 25 minutes or 15 minutes respectively:&lt;/p&gt;
&lt;figure class="align-default" id="t3-array"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-t3-array-25min.png" src="https://docs.coiled.io/_images/burstable-t3-array-25min.png" style="width: 100%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;t3.large&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="align-default" id="m6i-array"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-m6i-array-25min.png" src="https://docs.coiled.io/_images/burstable-m6i-array-25min.png" style="width: 100%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;m6i.large&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The m6i.large instances ran the same workload in only 15 minutes, compared to 25 minutes on t3.large instances. This is explained by two facts:&lt;/p&gt;
&lt;p&gt;First, peak speeds are higher on m6i.large instances:&lt;/p&gt;
&lt;figure class="align-default" id="peak-network-disk"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-network-disk.svg" src="https://docs.coiled.io/_images/burstable-network-disk.svg" style="width: 75%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Second, variance was higher across burstable instances. This is evident from the bands of color on the Grafana charts, which are narrower for the m6i.large run, but thicker for the t3.large run.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The m6i.large instances get faster network and disk speeds, and they get these speeds more consistently.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can see these charts (and lots of others!) for yourself on our Grafana server:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://benchmarks-grafana.oss.coiledhq.com/d/GvbFsqKVk/coiled-cluster-metrics-basic?var-datasource=Benchmarks&amp;var-account=dask-benchmarks&amp;var-cluster=test_array-86954b05&amp;var-cluster-id=175900&amp;from=1679662670000&amp;to=1679664170000&amp;orgId=1"&gt;t3 metrics&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://benchmarks-grafana.oss.coiledhq.com/d/GvbFsqKVk/coiled-cluster-metrics-basic?var-datasource=Benchmarks&amp;var-account=dask-benchmarks&amp;var-cluster=test_array-e48321a0&amp;var-cluster-id=175723&amp;from=1679618405000&amp;to=1679619905000&amp;orgId=1"&gt;m6i metrics&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="workload-2-xgboost-optuna-with-moderate-cpu-use"&gt;
&lt;h2&gt;Workload 2: XGBoost + Optuna with moderate CPU use&lt;/h2&gt;
&lt;p&gt;When we started to compare performance on burstable and non-burstable instances, one of the more surprising discoveries was that burstable instances showed much more “steal” CPU time. This came up in the XGBoost + Optuna benchmark:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;train_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;study_params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xgboost&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tree_method&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;study_params&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;d_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;num_boost_round&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;evals&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;d_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;train&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;predictions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xgboost&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mean_squared_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_dask_array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;predictions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_dask_array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;squared&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;n_estimators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggest_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;n_estimators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;learning_rate&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggest_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;learning_rate&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;colsample_bytree&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggest_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;colsample_bytree&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;colsample_bynode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggest_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;colsample_bynode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;colsample_bylevel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggest_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;colsample_bylevel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;reg_lambda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggest_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reg_lambda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;max_depth&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggest_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;max_depth&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;max_leaves&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggest_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;max_leaves&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;max_cat_to_onehot&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggest_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;max_cat_to_onehot&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;train_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;study&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optuna&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_study&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;study_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nyc-travel-time-model&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;study&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_trials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This benchmark uses little network or disk, and while it doesn’t saturate the CPU, it does have a consistent level of moderate CPU utilization. Here is the CPU utilization throughout the benchmark run:&lt;/p&gt;
&lt;figure class="align-default" id="t3-cpu"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-t3-cpu-6min.png" src="https://docs.coiled.io/_images/burstable-t3-cpu-6min.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;t3.large (&lt;a class="reference external" href="https://benchmarks-grafana.oss.coiledhq.com/d/GvbFsqKVk/coiled-cluster-metrics-basic?var-datasource=Benchmarks&amp;var-account=dask-benchmarks&amp;var-cluster=test_xgboost-02c2a3e7&amp;var-cluster-id=176024&amp;from=1679663867000&amp;to=1679664190000&amp;orgId=1"&gt;full Grafana dashboard&lt;/a&gt;)&lt;/span&gt;&lt;a class="headerlink" href="#t3-cpu" title="Permalink to this image"&gt;#&lt;/a&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;figure class="align-default" id="m6i-cpu"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-m6i-cpu-6min.png" src="https://docs.coiled.io/_images/burstable-m6i-cpu-6min.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;m6i.large (&lt;a class="reference external" href="https://benchmarks-grafana.oss.coiledhq.com/d/GvbFsqKVk/coiled-cluster-metrics-basic?var-datasource=Benchmarks&amp;var-account=dask-benchmarks&amp;var-cluster=test_xgboost-ac20806f&amp;var-cluster-id=175769&amp;from=1679619408000&amp;to=1679619603000&amp;orgId=1"&gt;full Grafana dashboard&lt;/a&gt;)&lt;/span&gt;&lt;a class="headerlink" href="#m6i-cpu" title="Permalink to this image"&gt;#&lt;/a&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;p&gt;The m6i run is faster. Interestingly, there is also more “steal” (shown in red) on the t3 instances, while there’s almost none on the m6i instances. The non-burstable m6i instances have more of CPU use in &lt;strong&gt;user&lt;/strong&gt; and &lt;strong&gt;system&lt;/strong&gt;, where the t3.large instances had on average 5–12% &lt;strong&gt;steal&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What is &lt;strong&gt;steal&lt;/strong&gt; CPU use you ask?  According to the &lt;a class="reference external" href="https://man7.org/linux/man-pages/man1/mpstat.1.html"&gt;Linux man page for mpstat&lt;/a&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;steal [is] the percentage of time spent in involuntary wait by the virtual CPU or CPUs while the hypervisor was servicing another virtual processor.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;With a few exceptions, EC2 instances are run on much larger hosts. The physical server runs a hypervisor that virtualizes the resources and ensures that every EC2 instance gets its fair share of the actual hardware. Steal is the overhead of this sharing—it’s the amount of time your code spends waiting to get access to an actual CPU.&lt;/p&gt;
&lt;p&gt;While you sometimes see steal on non-burstable instances, it’s very low. Burstable instances consistently show much higher steal. We haven’t found a detailed explanation from AWS but we suspect this has two possible causes:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Burstable instances are cheaper because AWS can “overbook” the hardware, much like an airline might overbook a flight. As long as enough people are underutilizing the CPU (or enough people are running late for the flight), everyone can get what they want. But sometimes more of the workloads want to utilize more of the CPU (or everyone wants the seat they paid for), and that’s when someone has to get bumped.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On burstable instances, the hypervisor itself also has more work to do, since it tracks CPU credits. While AWS doesn’t say this, it’s possible that tracking burst (and the associated costs) also adds overhead. Or maybe this isn’t the case, since the hypervisor on &lt;a class="reference external" href="https://aws.amazon.com/ec2/nitro/"&gt;Nitro&lt;/a&gt; instances is actually offloaded to separate, dedicated hardware.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Regardless of the underlying cause, we’ve seen that burstable instances very frequently show 5–10% “steal” (or more), while non-burstable instances almost never have more than 1 or 2% steal.&lt;/p&gt;
&lt;p&gt;The upshot is that for workloads with moderate CPU utilization—such as typical data engineering workloads—you’ll often end up paying more for burstable instances and getting worse performance.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="spot-availability-for-burstable-instances"&gt;
&lt;h2&gt;Spot availability for burstable instances&lt;/h2&gt;
&lt;p&gt;Spot instances are another way to save money in the cloud. Spot instances are cheaper than on-demand instances: often providing 60–70% savings.&lt;/p&gt;
&lt;p&gt;However, spot instances have more limited availability than on-demand instances. They’re offered at a discount because they’re unused capacity, and AWS can reclaim your spot instances if there’s more demand for full-priced instances.&lt;/p&gt;
&lt;p&gt;It’s possible that even with all the caveats about burstable instances, burstable instances might be best all things considered if you had to choose between&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;t3 spot instances, and&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;m6i on-demand instances.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key question here is whether it’s easier to get t3 spot instances than it is to get m6i spot instances.&lt;/p&gt;
&lt;p&gt;Spot capacity varies over time by instance type and by region, so we can’t promise that m6i spot instances aren’t harder to get, but we can share what we see when running our benchmarks at scale.&lt;/p&gt;
&lt;p&gt;With the “use best zone” feature (described in &lt;a class="reference external" href="https://www.coiled.io/blog/save-money-with-spot"&gt;our blog post about spot instances&lt;/a&gt;), we’ve gotten 100% of the requested m6i.large workers in the last 14 days as spot, thousands of per day:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.coiled.io/_images/burstable-oss-spot-m6i.png" src="https://docs.coiled.io/_images/burstable-oss-spot-m6i.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;If you care about cost, you should definitely watch your ability to get spot vs on-demand instances (you can see your cost breakdowns &lt;a class="reference external" href="https://blog.coiled.io/blog/aws-cost-explorer-tips.html#spot-vs-on-demand-instance-usage"&gt;as explained in this blog post&lt;/a&gt;). But in our experience, we’ve had no trouble getting non-burstable m6i.large spot instances.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="further-reading"&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;Based on this analysis we’ve set the defaults for Coiled users to non-burstable instance types. We recommend that you do the same. Hopefully the way that we did this analysis helps explain our thinking, and informs how you might make similar decisions in the future.&lt;/p&gt;
&lt;p&gt;If you’d like to read more about burstable instances on AWS, here are a few places to start:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances.html"&gt;Burstable Performance Instances (AWS Documentation)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://aws.amazon.com/ec2/instance-types/t3/"&gt;Baseline CPU for t3 instances (AWS documentation)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.datadoghq.com/blog/understanding-aws-stolen-cpu-and-how-it-affects-your-apps/"&gt;Understanding AWS stolen CPU and how it affects your apps (Datadog blog)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/burstable-vs-nonburstable.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Apr 4, 2023</summary>
    <category term="aws" label="aws"/>
    <category term="burstable" label="burstable"/>
    <category term="ec2" label="ec2"/>
    <published>2023-04-04T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/shuffling-large-data-at-constant-memory.html</id>
    <title>Shuffling large data at constant memory in Dask</title>
    <updated>2023-03-15T00:00:00+00:00</updated>
    <author>
      <name>Hendrik Makait</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Mar 15, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;5m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="shuffling-large-data-at-constant-memory-in-dask"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://makait.com/"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://makait.com/_static/profile.png" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Hendrik Makait&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With release &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;2023.2.1&lt;/span&gt;&lt;/code&gt;, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.dataframe&lt;/span&gt;&lt;/code&gt; introduces a new shuffling method called P2P, making sorts, merges, and joins faster and using constant memory.
Benchmarks show impressive improvements:&lt;/p&gt;
&lt;figure class="align-default" id="benchmark-summary"&gt;
&lt;img alt="P2P shuffling uses constant memory while task-based shuffling scales linearly." src="https://docs.coiled.io/_images/p2p_benchmark_memory_usage.png" style="width: 100%;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;P2P shuffling (blue) uses constant memory while task-based shuffling (orange) scales linearly.&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This article describes the problem, the new solution, and the impact on performance.&lt;/p&gt;
&lt;section id="what-is-shuffling"&gt;
&lt;h2&gt;What is Shuffling?&lt;/h2&gt;
&lt;p&gt;Shuffling is a key primitive in data processing systems.
It is used whenever we move a dataset around in an all-to-all fashion, such as occurs in sorting, dataframe joins, or array rechunking.
Shuffling is a challenging computation to run efficiently, with lots of small tasks sharding the data.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="task-based-shuffling-scales-poorly"&gt;
&lt;h2&gt;Task-based shuffling scales poorly&lt;/h2&gt;
&lt;p&gt;While systems like distributed databases and Apache Spark use a dedicated shuffle service to move data around the cluster, Dask builds on task-based scheduling.
Task-based systems are more flexible for general-purpose parallel computing but less suitable for all-to-all shuffling.
This results in three main issues:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scheduler strain:&lt;/strong&gt; The scheduler hangs due to the sheer amount of tasks required for shuffling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Full dataset materialization:&lt;/strong&gt; Workers materialize the entire dataset causing the cluster to run out of memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Many small operations:&lt;/strong&gt; Intermediate tasks are too small and bring down hardware performance.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Together, these issues make large-scale shuffles inefficient, causing users to over-provision clusters.
Fortunately, we can design a system to address these concerns.
Early work on this started back &lt;a class="reference external" href="https://www.coiled.io/blog/better-shuffling-in-dask-a-proof-of-concept"&gt;in 2021&lt;/a&gt; and has now matured into the P2P shuffling system:&lt;/p&gt;
&lt;/section&gt;
&lt;section id="p2p-shuffling"&gt;
&lt;h2&gt;P2P shuffling&lt;/h2&gt;
&lt;p&gt;With release &lt;a class="reference external" href="https://distributed.dask.org/en/stable/changelog.html#v2023-2-1"&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;2023.2.1&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;, Dask introduces a new shuffle method called P2P, making sorts, merges, and joins run faster and in constant memory.&lt;/p&gt;
&lt;p&gt;This system is designed with three aspects, mirroring the problems listed above:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Peer-to-Peer communication:&lt;/strong&gt; Reduce the involvement of the scheduler&lt;/p&gt;
&lt;p&gt;Shuffling becomes an O(n) operation from the scheduler’s perspective, removing a key bottleneck.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="align-default" id="comparing-tasks"&gt;
&lt;img alt="P2P shuffling uses fewer tasks than task-based shuffling." src="https://docs.coiled.io/_images/shuffling_tasks.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;ol class="arabic" start="2"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Disk by default:&lt;/strong&gt; Store data as it arrives on disk, efficiently.&lt;/p&gt;
&lt;p&gt;Dask can now shuffle datasets of arbitrary size in small memory, reducing the need to fiddle with right-sizing clusters.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Buffer network and disk with memory:&lt;/strong&gt; Avoid many small writes by buffering sensitive hardware with in-memory stores.&lt;/p&gt;
&lt;p&gt;Shuffling involves CPU, network, and disk, each of which brings its own bottlenecks when dealing with many small pieces of data.
We use memory judiciously to trade off between these bottlenecks, balancing network and disk I/O to maximize the overall throughput.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In addition to these three aspects, P2P shuffling implements many minor optimizations to improve performance, and it relies on &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pyarrow&amp;gt;=7.0&lt;/span&gt;&lt;/code&gt; for efficient data handling and (de)serialization.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="results"&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;To evaluate P2P shuffling, we benchmark against task-based shuffling on common workloads from our benchmark suite.
For more information on this benchmark suite, see the &lt;a class="reference external" href="https://github.com/coiled/coiled-runtime/tree/main/tests/benchmarks"&gt;GitHub repository&lt;/a&gt; or the &lt;a class="reference external" href="https://benchmarks.coiled.io/"&gt;latest test results&lt;/a&gt;.&lt;/p&gt;
&lt;section id="memory-stability"&gt;
&lt;h3&gt;Memory stability&lt;/h3&gt;
&lt;p&gt;The biggest benefit of P2P shuffling is constant memory usage.
Memory usage drops and stays constant across all workloads:&lt;/p&gt;
&lt;figure class="align-default" id="benchmark-results"&gt;
&lt;img alt="P2P shuffling uses significantly less memory than task-based shuffling on all workloads." src="https://docs.coiled.io/_images/p2p_benchmark_suite.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;For the tested workloads, we saw up to &lt;strong&gt;10x lower memory&lt;/strong&gt;.
For even larger workloads, this gap only increases.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="performance-and-speed"&gt;
&lt;h3&gt;Performance and Speed&lt;/h3&gt;
&lt;p&gt;In the above plot, we can two performance improvements:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Faster execution:&lt;/strong&gt; Workloads run up to 45% faster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quicker startup:&lt;/strong&gt; Smaller graphs mean P2P shuffling starts sooner.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Comparing scheduler dashboards for P2P and task-based shuffling side-by-side
helps to understand what causes these performance gains:&lt;/p&gt;
&lt;figure class="align-default" id="task-vs-p2p"&gt;
&lt;img alt="Task-based shuffling shows bigger gaps in the task stream and spilling compared to P2P shuffling." src="https://docs.coiled.io/_images/dashboard-comparison.png" style="width: 1000px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Task-based shuffling (left) shows bigger gaps in the task stream and spilling compared to P2P shuffling (right).&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Graph size:&lt;/strong&gt; 10x fewer tasks are faster to deploy and easier on the scheduler.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Controlled I/O:&lt;/strong&gt; P2P shuffling handles I/O explicitly, which avoids less performant spilling by Dask.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fewer interruptions:&lt;/strong&gt; I/O is well balanced, so we see fewer gaps in work in the task stream.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, P2P shuffling leverages our hardware far more effectively.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="changing-defaults"&gt;
&lt;h2&gt;Changing defaults&lt;/h2&gt;
&lt;p&gt;These results benefit most users, so P2P shuffling is now the default starting from release &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;2023.2.1&lt;/span&gt;&lt;/code&gt;,
as long as &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pyarrow&amp;gt;=7.0.0&lt;/span&gt;&lt;/code&gt; is installed.  Dataframe operations like the following will benefit:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;df.set_index(...)&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;df.merge(...)&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;df.groupby(...).apply(...)&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="share-your-experience"&gt;
&lt;h3&gt;Share your experience&lt;/h3&gt;
&lt;p&gt;To understand how P2P performs on the vast range of workloads out there in the wild, and to ensure that making it the default was the right choice, we would love to learn about your experiences running it.
We have opened a &lt;a class="reference external" href="https://github.com/dask/distributed/discussions/7509"&gt;discussion issue&lt;/a&gt; on GitHub for feedback on this change.
Please let us know how it helps (or doesn’t).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="keep-old-behavior"&gt;
&lt;h3&gt;Keep old behavior&lt;/h3&gt;
&lt;p&gt;If P2P shuffling does not work for you, you can deactivate it by setting the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dataframe.shuffle.method&lt;/span&gt;&lt;/code&gt; config value to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;&amp;quot;tasks&amp;quot;&lt;/span&gt;&lt;/code&gt; or explicitly setting a keyword argument, for example:&lt;/p&gt;
&lt;p&gt;with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;yaml&lt;/span&gt;&lt;/code&gt; config&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;dataframe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tasks&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;or when starting a Coiled cluster&lt;/p&gt;
&lt;div class="highlight-python3 notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="c1"&gt;# The dataframe.shuffle.method config is available since 2023.3.1&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataframe.shuffle.method&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;tasks&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}):&lt;/span&gt;
    &lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# coiled sends current dask config automatically&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For more information on deactivating P2P shuffle, see the &lt;a class="reference external" href="https://github.com/dask/distributed/discussions/7509"&gt;discussion #7509&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="what-about-arrays"&gt;
&lt;h2&gt;What about arrays?&lt;/h2&gt;
&lt;p&gt;While the original motivation was to optimize large-scale dataframe joins, the P2P system is useful for all problems requiring lots of communication between tasks.
For array workloads, this often occurs when rechunking data, such as when organizing a matrix by rows when it was stored by columns.
Similar to dataframe joins, array rechunking has been inefficient in the past, which has become such a problem that the array community built specialized tools like &lt;a class="reference external" href="https://github.com/pangeo-data/rechunker"&gt;rechunker&lt;/a&gt; to avoid it entirely.&lt;/p&gt;
&lt;p&gt;There is a &lt;a class="reference external" href="https://github.com/dask/distributed/pull/7534"&gt;naive implementation&lt;/a&gt; of array rechunking using the P2P system available for experimental use.
Benchmarking this implementation shows mixed results:&lt;/p&gt;
&lt;figure class="align-default" id="array-benchmarks"&gt;
&lt;img alt="P2P rechunking uses constant memory." src="https://docs.coiled.io/_images/p2p_benchmark_arrays.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;👍 &lt;strong&gt;Constant memory use:&lt;/strong&gt; As with dataframe operations, memory use is constant.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;❓ &lt;strong&gt;Variable runtime:&lt;/strong&gt; The runtime of workloads may increase with P2P.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;👎 &lt;strong&gt;Memory overhead:&lt;/strong&gt; There is a large memory overhead for many small partitions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The constant memory use is a very promising result.
There are several ways to tackle the current limitations.
We expect this to improve as we work with collaborators from the array computing community.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;Development on P2P shuffling is not done yet.
For the future we plan the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask.array&lt;/span&gt;&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;While the early prototype of array rechunking is promising, it’s not there yet.
We plan to do the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Intelligently select which algorithm to use (task-based rechunking is better sometimes)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Work with collaborators on rechunking to improve performance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seek out other use cases, like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map_overlap&lt;/span&gt;&lt;/code&gt;, where this might be helpful&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Failure recovery&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Make P2P shuffling resilient to worker loss; currently, it has to restart entirely.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance tuning&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Performance today is good, but not yet at peak hardware speeds.
We can improve this in a few ways:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Hiding disk I/O&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using more memory when appropriate for smaller shuffles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improved batching of small operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More efficient serialization&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To follow along with the development, subscribe to the &lt;a class="reference external" href="https://github.com/dask/distributed/issues/7507"&gt;tracking issue&lt;/a&gt; on GitHub.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Shuffling data is a common operation in dataframe workloads.
Since &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;2023.2.1&lt;/span&gt;&lt;/code&gt;, Dask defaults to P2P shuffling for distributed clusters and shuffles data faster and at constant memory.
This improvement unlocks previously un-runnable workload sizes and efficiently uses your clusters.
Finally, P2P shuffling demonstrates extending Dask to add new paradigms while leveraging the foundations of its distributed engine.&lt;/p&gt;
&lt;p&gt;Share results in &lt;a class="reference external" href="https://github.com/dask/distributed/issues/7507"&gt;this discussion thread&lt;/a&gt; or follow development at this &lt;a class="reference external" href="https://github.com/dask/distributed/issues/7507"&gt;tracking issue&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/shuffling-large-data-at-constant-memory.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Mar 15, 2023</summary>
    <category term="dask" label="dask"/>
    <category term="distributed" label="distributed"/>
    <category term="p2p" label="p2p"/>
    <category term="shuffling" label="shuffling"/>
    <published>2023-03-15T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/just-in-time-python-environments.html</id>
    <title>Just in time Python environments</title>
    <updated>2023-02-23T00:00:00+00:00</updated>
    <author>
      <name>Sarah Johnson</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Feb 23, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;4m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="just-in-time-python-environments"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;a class="reference external" href="https://github.com/shughes-uk"&gt;Samantha Hughes&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/scharlottej13"&gt;Sarah Johnson&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Docker is a great tool for creating portable software environments, but we found it’s too slow for interactive exploration. We find that clusters depending on docker images often take 5+ minutes to launch. Ouch.&lt;/p&gt;
&lt;p&gt;In the latest Coiled release, version 0.4.0, you can use a new system for creating software environments on the fly using only mamba instead. We’re seeing start times 3x faster, or about 1-2 minutes.&lt;/p&gt;
&lt;section id="why-we-dropped-docker-for-python-environments"&gt;
&lt;h2&gt;Why we dropped Docker for Python environments&lt;/h2&gt;
&lt;p&gt;Docker is a great tool for managing software environments, but we found that it’s just too slow, especially for exploratory data workflows where users change their python environments frequently.&lt;/p&gt;
&lt;p&gt;This article goes into the challenges we faced, the solution we chose, and the performance impacts of that choice. Here are the results:&lt;/p&gt;
&lt;figure class="align-default" id="build-push-pull"&gt;
&lt;img alt="https://docs.coiled.io/_images/senvs2_build_push_pull.svg" src="https://docs.coiled.io/_images/senvs2_build_push_pull.svg" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;If you’re a Coiled user these changes are now deployed if you update to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&amp;gt;=0.4&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;In a &lt;a class="reference external" href="https://www.coiled.io/blog/fixing-mismatched-versions-with-dask-and-coiled"&gt;previous blog post&lt;/a&gt;, we discussed the importance of matching your local Python packages and versions to those installed on remote machines running in the cloud. The easiest way to do this with a Coiled cluster is using &lt;a class="reference external" href="https://docs.coiled.io/user_guide/package_sync.html"&gt;package sync&lt;/a&gt;, which scans your local environment and replicates it on your cluster. This is faster, easier, and safer than building a Docker image each time.&lt;/p&gt;
&lt;p&gt;However, some folks still want statically defined software environments stored in the cloud, like what Docker offers, and so we maintain an API &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;create_software_environment&lt;/span&gt;&lt;/code&gt; , which used to make Docker images given conda/pip specifications, but now does something much faster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_software_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;conda&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dask&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pandas&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pytorch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;arrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sqlalchemy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dask-snowflake&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="conda-and-docker-are-slow"&gt;
&lt;h2&gt;Conda and Docker are slow&lt;/h2&gt;
&lt;p&gt;Previously, Coiled used conda to solve Python environments and Docker to build those environments. We then pushed those Docker images to users’ container registries and on cluster start, pulled these images onto the cloud instances that formed a Dask/Coiled cluster.&lt;/p&gt;
&lt;p&gt;Benchmarking these steps using a complex/real-world software environment from the wild, we notice the first bottleneck is the environment solve. The customer reported anecdotally this could sometimes take 30 minutes! We fixed this simply by switching to Mamba, which is much faster.&lt;/p&gt;
&lt;p&gt;That still leaves the build, push and pull steps. We noticed that this takes about 8 minutes; which is painfully slow for users.  We break down that time cost below:&lt;/p&gt;
&lt;details&gt;&lt;summary&gt;environment.yml&lt;/summary&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;base&lt;/span&gt;
&lt;span class="nt"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;conda-forge&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;defaults&lt;/span&gt;
&lt;span class="nt"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gcc&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;_libgcc_mutex=0.1=conda_forge&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;-_openmp_mutex=4.5=2_gnu&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;abseil-cpp=20210324.2=h9c3ff4c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aiobotocore=2.3.4=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aiohttp=3.8.1=py39hb9d737c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aioitertools=0.10.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aiosignal=1.2.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;alembic=1.8.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;alsa-lib=1.2.6.1=h7f98852_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;anyio=3.6.1=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;appdirs=1.4.4=pyh9f0ad1d_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;argon2-cffi=21.3.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;argon2-cffi-bindings=21.2.0=py39hb9d737c_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;arrow-cpp=8.0.0=py39h811ffd7_4_cpu&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;asn1crypto=1.5.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;asttokens=2.0.5=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;async-timeout=4.0.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;attr=2.5.1=h166bdaf_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;attrs=21.4.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-c-cal=0.5.11=h95a6274_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-c-common=0.6.2=h7f98852_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-c-event-stream=0.2.7=h3541f99_13&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-c-io=0.10.5=hfb6a706_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-checksums=0.1.11=ha31a3da_7&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-sdk-cpp=1.8.186=hb4091e7_3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;babel=2.10.3=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;backcall=0.2.0=pyh9f0ad1d_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;backports=1.0=py_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;backports.functools_lru_cache=1.6.4=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;beautifulsoup4=4.11.1=pyha770c72_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;bleach=5.0.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;blinker=1.4=py_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;blosc=1.21.1=h83bc5f7_3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;bokeh=2.4.3=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;botocore=1.24.21=pyhd8ed1ab_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;brotli=1.0.9=h166bdaf_7&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;brotli-bin=1.0.9=h166bdaf_7&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;brotlipy=0.7.0=py39hb9d737c_1004&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;bzip2=1.0.8=h7f98852_4&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;c-ares=1.18.1=h7f98852_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ca-certificates=2022.6.15=ha878542_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;certifi=2022.6.15=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cffi=1.15.1=py39he91dace_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;click=8.1.3=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cloudpickle=2.1.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;colorama=0.4.5=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;configparser=5.2.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cryptography=37.0.4=py39hd97740a_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cycler=0.11.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cytoolz=0.12.0=py39hb9d737c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dask=2022.7.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dask-core=2022.7.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;databricks-cli=0.17.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dbus=1.13.6=h5008d03_3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;debugpy=1.6.0=py39h5a03fae_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;decorator=5.1.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;defusedxml=0.7.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;distributed=2022.7.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;docker-py=5.0.3=py39hf3d152e_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;docker-pycreds=0.4.0=py_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;entrypoints=0.4=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;executing=0.9.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;expat=2.4.8=h27087fc_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fftw=3.3.10=nompi_h77c792f_102&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;flask=2.1.3=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;flit-core=3.7.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;font-ttf-dejavu-sans-mono=2.37=hab24e00_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;font-ttf-inconsolata=3.000=h77eed37_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;font-ttf-source-code-pro=2.038=h77eed37_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;font-ttf-ubuntu=0.83=hab24e00_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fontconfig=2.14.0=h8e229c2_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fonts-conda-ecosystem=1=0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fonts-conda-forge=1=0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fonttools=4.34.4=py39hb9d737c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;freetype=2.10.4=h0708190_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;frozenlist=1.3.0=py39hb9d737c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fsspec=2022.5.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gettext=0.19.8.1=h73d1719_1008&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gflags=2.2.2=he1b5a44_1004&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;giflib=5.2.1=h36c2ea0_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gitdb=4.0.9=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gitpython=3.1.27=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;glib=2.72.1=h6239696_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;glib-tools=2.72.1=h6239696_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;glog=0.6.0=h6f12383_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;greenlet=1.1.2=py39h5a03fae_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;grpc-cpp=1.45.2=h3b8df00_4&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gst-plugins-base=1.20.3=hf6a322e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gstreamer=1.20.3=hd4edc92_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gunicorn=20.1.0=py39hf3d152e_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;heapdict=1.0.1=py_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;htmlmin=0.1.12=py_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;icu=70.1=h27087fc_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;idna=3.3=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;imagehash=4.2.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;importlib-metadata=4.11.4=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;importlib_metadata=4.11.4=hd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;importlib_resources=5.9.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ipykernel=6.15.1=pyh210e3f2_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ipython=8.4.0=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ipython_genutils=0.2.0=py_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;itsdangerous=2.1.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jack=1.9.18=h8c3723f_1002&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jedi=0.18.1=py39hf3d152e_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jinja2=3.1.2=pyhd8ed1ab_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jmespath=1.0.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;joblib=1.1.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jpeg=9e=h166bdaf_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;json5=0.9.5=pyh9f0ad1d_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jsonschema=4.7.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyter_client=7.3.4=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyter_core=4.11.1=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyter_server=1.18.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyterlab=3.4.4=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyterlab_pygments=0.2.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyterlab_server=2.15.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;keyutils=1.6.1=h166bdaf_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kiwisolver=1.4.4=py39hf939315_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;krb5=1.19.3=h3790be6_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;lcms2=2.12=hddcbb42_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ld_impl_linux-64=2.36.1=hea4e1c9_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;lerc=3.0=h9c3ff4c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libblas=3.9.0=15_linux64_openblas&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libbrotlicommon=1.0.9=h166bdaf_7&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libbrotlidec=1.0.9=h166bdaf_7&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libbrotlienc=1.0.9=h166bdaf_7&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libcap=2.64=ha37c62d_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libcblas=3.9.0=15_linux64_openblas&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libclang=14.0.6=default_h2e3cab8_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libclang13=14.0.6=default_h3a83d3e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libcrc32c=1.1.2=h9c3ff4c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libcups=2.3.3=hf5a7f15_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libcurl=7.83.1=h7bff187_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libdb=6.2.32=h9c3ff4c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libdeflate=1.12=h166bdaf_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libedit=3.1.20191231=he28a2e2_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libev=4.33=h516909a_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libevent=2.1.10=h9b69904_4&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libffi=3.4.2=h7f98852_5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libflac=1.3.4=h27087fc_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libgcc-ng=12.1.0=h8d9b700_16&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libgfortran-ng=12.1.0=h69a702a_16&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libgfortran5=12.1.0=hdcd56e2_16&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libglib=2.72.1=h2d90d5f_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libgomp=12.1.0=h8d9b700_16&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libgoogle-cloud=1.40.2=habd0e3a_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libiconv=1.16=h516909a_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;liblapack=3.9.0=15_linux64_openblas&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libllvm11=11.1.0=hf817b99_3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libllvm14=14.0.6=he0ac6c6_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libnghttp2=1.47.0=h727a467_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libnsl=2.0.0=h7f98852_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libogg=1.3.4=h7f98852_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libopenblas=0.3.20=pthreads_h78a6416_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libopus=1.3.1=h7f98852_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libpng=1.6.37=h753d276_3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libpq=14.4=hd77ab85_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libprotobuf=3.20.1=h6239696_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libsndfile=1.0.31=h9c3ff4c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libsodium=1.0.18=h36c2ea0_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libssh2=1.10.0=ha56f1ee_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libstdcxx-ng=12.1.0=ha89aaad_16&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libthrift=0.16.0=h519c5ea_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libtiff=4.4.0=hc85c160_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libtool=2.4.6=h9c3ff4c_1008&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libudev1=249=h166bdaf_4&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libutf8proc=2.7.0=h7f98852_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libuuid=2.32.1=h7f98852_1000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libvorbis=1.3.7=h9c3ff4c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libwebp=1.2.3=h522a892_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libwebp-base=1.2.3=h166bdaf_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libxcb=1.13=h7f98852_1004&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libxkbcommon=1.0.3=he3ba5ed_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libxml2=2.9.14=h22db469_3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;libzlib=1.2.12=h166bdaf_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;llvmlite=0.38.1=py39h7d9a04d_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;locket=1.0.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;lz4=4.0.0=py39h029007f_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;lz4-c=1.9.3=h9c3ff4c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mako=1.2.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;markdown=3.4.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;markupsafe=2.1.1=py39hb9d737c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;matplotlib=3.5.2=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;matplotlib-base=3.5.2=py39h700656a_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;matplotlib-inline=0.1.3=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;missingno=0.4.2=py_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mistune=0.8.4=py39h3811e60_1005&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mlflow=1.27.0=py39ha39b057_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;msgpack-python=1.0.4=py39hf939315_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;multidict=6.0.2=py39hb9d737c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;multimethod=1.4=py_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;munkres=1.1.4=pyh9f0ad1d_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mysql-common=8.0.29=haf5c9bc_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mysql-libs=8.0.29=h28c427c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nbclassic=0.4.3=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nbclient=0.6.6=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nbconvert=6.5.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nbconvert-core=6.5.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nbconvert-pandoc=6.5.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nbformat=5.4.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ncurses=6.3=h27087fc_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nest-asyncio=1.5.5=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;networkx=2.8.5=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;notebook=6.4.12=pyha770c72_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;notebook-shim=0.1.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nspr=4.32=h9c3ff4c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nss=3.78=h2350873_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;numba=0.55.2=py39h66db6d7_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;numpy=1.22.4=py39hc58783e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;oauthlib=3.2.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;openjpeg=2.4.0=hb52868f_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;openssl=1.1.1q=h166bdaf_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;orc=1.7.5=h6c59b99_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;packaging=21.3=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pandas=1.4.3=py39h1832856_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pandas-profiling=3.2.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pandoc=2.18=ha770c72_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pandocfilters=1.5.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;parquet-cpp=1.5.1=2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;parso=0.8.3=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;partd=1.2.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;patsy=0.5.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pcre=8.45=h9c3ff4c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pexpect=4.8.0=pyh9f0ad1d_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;phik=0.12.2=py39ha791e8c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pickleshare=0.7.5=py_1003&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pillow=9.2.0=py39hae2aec6_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pip=22.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ply=3.11=py_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;portaudio=19.6.0=h57a0ea0_5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;prometheus_client=0.14.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;prometheus_flask_exporter=0.20.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;prompt-toolkit=3.0.30=pyha770c72_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;protobuf=3.20.1=py39h5a03fae_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;psutil=5.9.1=py39hb9d737c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pthread-stubs=0.4=h36c2ea0_1001&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ptyprocess=0.7.0=pyhd3deb0d_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pulseaudio=14.0=h7f54b18_8&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pure_eval=0.2.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pybind11-abi=4=hd8ed1ab_3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pycparser=2.21=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pydantic=1.9.1=py39hb9d737c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pygments=2.12.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyjwt=2.4.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyopenssl=22.0.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyparsing=3.0.9=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyqt=5.15.7=py39h18e9c17_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyqt5-sip=12.11.0=py39h5a03fae_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyrsistent=0.18.1=py39hb9d737c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pysocks=1.7.1=py39hf3d152e_5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python=3.9.13=h9a8a25e_0_cpython&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python-dateutil=2.8.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python-fastjsonschema=2.16.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python_abi=3.9=2_cp39&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python~=3.9&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pytz=2022.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pywavelets=1.3.0=py39hd257fcd_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyyaml=6.0=py39hb9d737c_4&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyzmq=23.2.0=py39headdf64_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;qt-main=5.15.4=ha5833f6_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;querystring_parser=1.2.4=py_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;re2=2022.06.01=h27087fc_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;readline=8.1.2=h0f457ee_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;s2n=1.0.10=h9b69904_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;s3fs=2022.5.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;scikit-learn=1.1.1=py39h4037b75_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;scipy=1.8.1=py39he49c0e8_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;seaborn=0.11.2=hd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;seaborn-base=0.11.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;send2trash=1.8.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;setuptools=63.2.0=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;shap=0.41.0=py39h1832856_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sip=6.6.2=py39h5a03fae_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;six=1.16.0=pyh6c4a22f_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;slicer=0.0.7=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;smmap=3.0.5=pyh44b312d_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;snappy=1.1.9=hbd366e4_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sniffio=1.2.0=py39hf3d152e_3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sortedcontainers=2.4.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;soupsieve=2.3.2.post1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sqlalchemy=1.4.39=py39hb9d737c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sqlite=3.39.2=h4ff8645_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sqlparse=0.4.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;stack_data=0.3.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;statsmodels=0.13.2=py39hd257fcd_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tabulate=0.8.10=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tangled-up-in-unicode=0.2.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tblib=1.7.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;terminado=0.15.0=py39hf3d152e_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;threadpoolctl=3.1.0=pyh8a188c0_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tinycss2=1.1.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tk=8.6.12=h27826a3_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;toml=0.10.2=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;toolz=0.12.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tornado=6.1=py39hb9d737c_3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tqdm=4.64.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;traitlets=5.3.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;typing-extensions=4.3.0=hd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;typing_extensions=4.3.0=pyha770c72_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;unicodedata2=14.0.0=py39hb9d737c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;urllib3=1.26.10=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;visions=0.7.4=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;wcwidth=0.2.5=pyh9f0ad1d_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;webencodings=0.5.1=py_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;websocket-client=1.3.3=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;werkzeug=2.1.2=pyhd8ed1ab_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;wheel=0.37.1=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;wrapt=1.14.1=py39hb9d737c_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xcb-util=0.4.0=h166bdaf_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xcb-util-image=0.4.0=h166bdaf_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xcb-util-keysyms=0.4.0=h166bdaf_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xcb-util-renderutil=0.3.9=h166bdaf_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xcb-util-wm=0.4.1=h166bdaf_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xorg-libxau=1.0.9=h7f98852_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xorg-libxdmcp=1.1.3=h7f98852_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xz=5.2.5=h516909a_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;yaml=0.2.5=h7f98852_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;yarl=1.7.2=py39hb9d737c_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;zeromq=4.3.4=h9c3ff4c_1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;zict=2.2.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;zipp=3.8.0=pyhd8ed1ab_0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;zlib=1.2.12=h166bdaf_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;zstd=1.5.2=h8a70e8d_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aenum==3.1.11&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;affinegap==1.12&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;altair==4.2.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;awswrangler==2.16.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;backoff==1.11.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;bcrypt==3.2.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;black==22.6.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;blis==0.7.8&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;boto3==1.21.21&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;btrees==4.10.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cachetools==5.2.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;catalogue==2.0.7&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;categorical-distance==1.9&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;charset-normalizer==2.0.12&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;coiled==0.2.15&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;colored==1.4.3&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;commonmark==0.9.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;coverage==6.4.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cymem==2.0.6&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dateparser==1.1.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;datetime-distance==0.1.3&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dedupe==2.0.13&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dedupe-hcluster==0.3.9&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dedupe-variable-datetime==0.1.5&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dedupe-variable-name==0.0.14&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;doublemetaphone==1.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;elasticsearch==7.13.4&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;et-xmlfile==1.1.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;faiss-cpu==1.7.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fastcluster==1.2.6&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;flake8==4.0.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;floret==0.10.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;future==0.18.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gender-guesser==0.4.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;google-auth==2.9.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;google-auth-oauthlib==0.5.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gremlinpython==3.6.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gspread==5.4.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gspread-pandas==3.2.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;haversine==2.6.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;highered==0.2.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;iniconfig==1.1.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ipywidgets==7.7.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;isodate==0.6.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jsondiff==2.0.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jsonpath-ng==1.5.3&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyterlab-widgets==1.1.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;langcodes==3.3.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;levenshtein-search==1.4.5&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;lxml==4.9.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mccabe==0.6.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;murmurhash==1.0.7&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mypy-extensions==0.4.3&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;openpyxl==3.0.10&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;opensearch-py==1.1.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;paramiko==2.11.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;parseratorvariable==0.0.18&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pathspec==0.9.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pathy==0.6.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pdfkit==1.0.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;persistent==4.9.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pg8000==1.29.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;platformdirs==2.5.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pluggy==1.0.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;preshed==3.0.6&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;probableparsing==0.0.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;probablepeople==0.5.4&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;progressbar2==4.0.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;psycopg2-binary==2.9.3&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;py==1.11.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyarrow==7.0.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyasn1==0.4.8&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyasn1-modules==0.2.8&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pycodestyle==2.8.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pydeck==0.7.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyflakes==2.4.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyhacrf-datamade==0.2.6&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pylbfgs==0.2.0.14&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pympler==1.0.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pymysql==1.0.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pynacl==1.5.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pytest==7.1.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pytest-cov==3.0.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python-crfsuite==0.9.8&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python-dotenv==0.20.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python-utils==3.3.3&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pytz-deprecation-shim==0.1.0.post0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ratelimit==2.2.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;redshift-connector==2.0.908&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;regex==2022.3.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;requests==2.28.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;requests-aws4auth==1.1.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;requests-oauthlib==1.3.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rich==12.5.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rlr==2.4.6&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rsa==4.9&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;s3transfer==0.5.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;scramp==1.4.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;semver==2.13.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;simplecosine==1.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;smart-open==5.2.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;spacy==3.4.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;spacy-legacy==3.0.9&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;spacy-loggers==1.0.3&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;srsly==2.4.4&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sshtunnel==0.4.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;streamlit==1.11.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;syrupy==2.3.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;textual==0.1.18&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;thinc==8.1.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tomli==2.0.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ttictoc==0.5.6&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;typer==0.4.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tzdata==2022.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tzlocal==4.2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;unidecode==1.3.4&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;validators==0.20.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;wasabi==0.9.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;watchdog==2.1.9&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;widgetsnbextension==3.6.1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;zope-index==5.2.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;zope-interface==5.4.0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Step&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Time in seconds (mean ± σ)&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;Build image&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;155 ± 1.7&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td class="text-left"&gt;&lt;p&gt;Push image to registry&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;126&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;Pull image to cloud instance  (t3.medium)&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;163 ± 21&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Why is this slow? Mostly compression.&lt;/p&gt;
&lt;p&gt;Docker images are compressed with bzip2 which is efficient at creating smaller files, but slow as it trades CPU time for smaller image size. Caching helps a little, but most of each image is Python packages, which vary wildly and are hard to cache effectively. Additionally, each image layer is decompressed sequentially, which also slows things down despite using a multithreaded Gzip implementation.&lt;/p&gt;
&lt;p&gt;So we dropped Docker.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;&lt;strong&gt;A brief word from our sponsors&lt;/strong&gt;: Coiled makes it very easy to scale up your Python code in the cloud. We deploy in your AWS or Google Cloud account, and we configure the infrastructure so that you get sensible security &lt;em&gt;and cost-savings&lt;/em&gt; by default. If you have Python code that you need to run at scale, &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;give us a try&lt;/a&gt;!&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="switching-to-mamba"&gt;
&lt;h2&gt;Switching to mamba&lt;/h2&gt;
&lt;p&gt;Mamba environments normally aren’t portable (and in fact has had to pull some tricks to allow package installation in the first place). We can cheat though because Mamba environments are portable if you keep them in the same location on the filesystem, and we control the filesystem on our clusters. Our new build system instead does the following:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Creates an environment on a large machine&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turns it into lz4-compressed tarballs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Loads it into Amazon S3&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downloads/extracts it from s3 directly onto the cluster&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We can tune each of these stages using more modern techniques than Docker provides.&lt;/p&gt;
&lt;section id="tuning-mamba-performance"&gt;
&lt;h3&gt;Tuning Mamba performance&lt;/h3&gt;
&lt;p&gt;First, let’s understand how Mamba works:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Download metadata.json for each channel in use (40MB+)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Solve package dependencies (single-core, RAM hungry)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downloading and extract the required packages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Symlink&lt;/span&gt;&lt;/code&gt; those packages into the new environment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run any &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;post_install&lt;/span&gt;&lt;/code&gt; steps for packages that have them&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pip&lt;/span&gt; &lt;span class="pre"&gt;install&lt;/span&gt;&lt;/code&gt; on any additional pip packages&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This process presents some challenges to overcome:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Solving the environment can be slow and require lots of memory (up to 10 GB+)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downloading packages can be limited by network and disk speeds&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To address these we perform a few basic optimizations:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Download packages concurrently&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a big machine and ask conda/pip to store packages to a RAM disk (we’re using S3 as persistent storage eventually, so local disk is slow and not that useful anyway)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compress with LZ4 rather than bzip2&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stream into S3 in 16 MB chunks, achieving multiple gigabit upload and download speeds&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="results-faster-builds"&gt;
&lt;h2&gt;Results: Faster builds&lt;/h2&gt;
&lt;p&gt;Our new system provides the same user experience, but is significantly faster:&lt;/p&gt;
&lt;figure class="align-default" id="build-push-pull-results"&gt;
&lt;img alt="https://docs.coiled.io/_images/senvs2_build_push_pull.svg" src="https://docs.coiled.io/_images/senvs2_build_push_pull.svg" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;That’s under 2 minutes total, over three times faster than using a Docker image.&lt;/p&gt;
&lt;p&gt;If you’re using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&amp;gt;=0.4&lt;/span&gt;&lt;/code&gt;, you’ll automatically be using the new environment build system, and you can keep using the old system by pinning an older version of Coiled. Though we’re no longer using Docker to build software environments, you can keep using pre-built Docker images in your clusters. See &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/docker.html"&gt;our docs&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;If you’re not using Coiled then what are you waiting for? The Coiled free tier is enough for most people’s workloads and is easy to &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;get started&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/just-in-time-python-environments.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Feb 23, 2023</summary>
    <category term="packagesync" label="package sync"/>
    <category term="packaging" label="packaging"/>
    <category term="pythonenvironments" label="python environments"/>
    <published>2023-02-23T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/package-sync-integration-test.html</id>
    <title>How many PEPs does it take to install a package?</title>
    <updated>2023-01-17T00:00:00+00:00</updated>
    <author>
      <name>Samantha Hughes</name>
    </author>
    <content type="html">&lt;section id="how-many-peps-does-it-take-to-install-a-package"&gt;

&lt;p&gt;A few months ago we released &lt;a class="reference external" href="https://docs.coiled.io/user_guide/package_sync.html"&gt;package sync&lt;/a&gt;, a feature that takes your Python environment and replicates it in the cloud with zero effort.&lt;/p&gt;
&lt;p&gt;The reaction has been very positive; people enable the feature and then mostly forget about their previous woes with docker or version mismatches.&lt;/p&gt;
&lt;p&gt;However to make this feature really stand out, it needs to work the first time, every time. The feature worked great when you used Python exactly like we did, but there are so many ways of using Python it was rare for this to be true.&lt;/p&gt;
&lt;p&gt;The first and most critical step for package sync, is to identify every installed package, the installed version, and where it came from. This is critical information if we’re going to reverse engineer your environment! Some existing tools try this, like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pip&lt;/span&gt; &lt;span class="pre"&gt;list&lt;/span&gt;&lt;/code&gt;, but none provided all the information we needed.&lt;/p&gt;
&lt;p&gt;To achieve this we needed to do a deep dive on not only current PEP standards for Python environments, but every previous iteration of them (including some from before the time of PEPs) and every operating system we intended to support.&lt;/p&gt;
&lt;p&gt;There’s a surprisingly large number of ways of getting a Python package in position so &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;import&lt;/span&gt; &lt;span class="pre"&gt;foo&lt;/span&gt;&lt;/code&gt; works.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html"&gt;Eggs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://peps.python.org/pep-0376/#one-dist-info-directory-per-installed-distribution"&gt;PEP 376 Dist Info&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#egg-links"&gt;Egg Links&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.python.org/3/library/site.html"&gt;.pth files&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://peps.python.org/pep-0660/"&gt;PEP 660 Editable Installs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Python files or modules in your current working directory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manually dropping Python files or module in your &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;site-packages&lt;/span&gt;&lt;/code&gt; folder (hands up if you kept site-packages in VCS back in the day!)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of these also have different variants. For example, PEP 376 has &lt;a class="reference external" href="https://peps.python.org/pep-0610/"&gt;an addendum&lt;/a&gt; that allows a JSON file describing where the package was installed from.&lt;/p&gt;
&lt;p&gt;While Python has a &lt;a class="reference external" href="https://docs.python.org/3/library/importlib.metadata.html#distribution-discovery"&gt;relatively powerful tool&lt;/a&gt; to discover the names of all the packages installed, we have to find out where you got those packages from and how we might be able to repeat what you did. Did you install &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask&lt;/span&gt;&lt;/code&gt; from conda, Git, PyPI or perhaps a local editable directory?&lt;/p&gt;
&lt;p&gt;Every package manager also implements its own version of these standards. For example, Poetry prior to version 1.2.0 implemented an incorrect standard of PEP 610. PDM uses PEP 582, which is still a draft, and installs everything to a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;__pypackages__&lt;/span&gt;&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Each operating system also has its own quirks, we recently had to deal with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;importlib.metadata&lt;/span&gt;&lt;/code&gt; reporting paths to files in egg packages with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;/&lt;/span&gt;&lt;/code&gt; separators instead of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;\&lt;/span&gt;&lt;/code&gt;, as they are technically in a zip file.&lt;/p&gt;
&lt;p&gt;So how do we make sure package sync works and keeps working in all of these cases?&lt;/p&gt;
&lt;p&gt;With all this knowledge we built out a large integration test suite, where we install a dummy version of every package variant we know of using a bunch of different package managers on a bunch of a different operating systems.&lt;/p&gt;
&lt;p&gt;It currently has 29 different combinations of operating systems, versions, and package managers.&lt;/p&gt;
&lt;p&gt;OS:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Ubuntu&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Windows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;macOS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Debian&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;amazonlinux&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Package managers:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;apt&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;yum&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;venv&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pyenv&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;mamba&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;venv&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python versions:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;3.11&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3.10&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3.9&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3.8&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3.7&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;During development of the test suite we squashed 7 different bugs, some minor and some show stoppers.&lt;/p&gt;
&lt;p&gt;Our test suite will grow as we spot more ways of managing package and as the Python community creates even more. I created package sync with a firm belief that our solution should “just work” for everyone.&lt;/p&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/package-sync-integration-test.html"/>
    <summary>A few months ago we released package sync, a feature that takes your Python environment and replicates it in the cloud with zero effort.</summary>
    <category term="packagesync" label="package sync"/>
    <category term="packaging" label="packaging"/>
    <published>2023-01-17T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/xgboost-at-scale.html</id>
    <title>Scaling Hyperparameter Optimization With XGBoost, Optuna, and Dask</title>
    <updated>2023-01-06T00:00:00+00:00</updated>
    <author>
      <name>Greg Hayes</name>
    </author>
    <content type="html">&lt;section id="scaling-hyperparameter-optimization-with-xgboost-optuna-and-dask"&gt;

&lt;section id="scaling-xgboost-and-optuna-with-dask"&gt;
&lt;h2&gt;Scaling XGBoost and Optuna with Dask&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://xgboost.readthedocs.io/en/stable/index.html"&gt;XGBoost&lt;/a&gt; is one of the most well-known libraries among data scientists, having become one of the &lt;a class="reference external" href="https://medium.com/analytics-vidhya/xgboost-lightgbm-and-other-kaggle-competition-favorites-6212e8b0e835"&gt;top choices among Kaggle competitors&lt;/a&gt;. It is performant in a wide of array of supervised machine learning problems, implements scalable training through the &lt;a class="reference external" href="https://github.com/dmlc/rabit"&gt;rabit&lt;/a&gt; library, and integrates with many big data processing tools, including &lt;a class="reference external" href="https://xgboost.readthedocs.io/en/stable/python/python_api.html#module-xgboost.dask"&gt;Dask&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While XGBoost enables distributed model training, hyperparameter optimization (HPO) adds a layer of complexity which only grows with the size of the dataset. Tools like &lt;a class="reference external" href="https://optuna.readthedocs.io/en/stable/"&gt;Optuna&lt;/a&gt; have emerged to reduce this friction by dynamically optimizing the HPO search space providing much-needed visibility into this process, and &lt;a class="reference external" href="https://github.com/optuna/optuna-examples/tree/main/kubernetes"&gt;enabling parallel HPO&lt;/a&gt;, but in this paradigm, the distributed HPO step remains disconnected from data engineering. &lt;a class="reference external" href="https://github.com/optuna/optuna-examples/tree/main/dask"&gt;Examples of recent integrations between Dask and Optuna&lt;/a&gt; show how bringing distributed feature engineering together with HPO simplifies the workflow for machine learning engineering workflows.&lt;/p&gt;
&lt;p&gt;Here, we demonstrate one way to connect these pieces of the data science workflow on a dataset with hundreds of millions of rows. This is accomplished by leveraging Dask for exploration and feature engineering. Distributed hyperparameter optimization and distributed model training are enabled through native dask integration with Optuna and XGBoost, respectively. We bring all of these tools together by spinning up dask clusters on-demand with &lt;a class="reference external" href="https://www.coiled.io"&gt;Coiled&lt;/a&gt;. Code for this work is located at &lt;a class="reference external" href="https://github.com/coiled/dask-xgboost-nyctaxi"&gt;this GitHub repository&lt;/a&gt;.
&lt;br&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-problem-to-solve"&gt;
&lt;h2&gt;The Problem To Solve&lt;/h2&gt;
&lt;p&gt;For this example, we extend on our work &lt;a class="reference external" href="https://blog.coiled.io/blog/coiled-from-prefect/"&gt;demonstrating the use of Coiled and Prefect to automate data engineering workloads&lt;/a&gt;, by leveraging the initially prepared New York City “For Hire Vehicle” taxi dataset as a source for exploratory analysis. &lt;a class="reference external" href="https://github.com/coiled/coiled-from-prefect"&gt;Cleaning the data&lt;/a&gt; consists of declaring datatypes and partitioning files to manageable partition sizes.&lt;/p&gt;
&lt;figure class="align-default" id="xgboost-optuna-dask-in-many-threads"&gt;
&lt;img alt="https://docs.coiled.io/_images/dask-optuna-xgboost.png" src="https://docs.coiled.io/_images/dask-optuna-xgboost.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;One motivating problem with the New York City Taxi dataset is to predict expected trip duration. This has been the topic of both &lt;a class="reference external" href="https://www.kaggle.com/c/nyc-taxi-trip-duration"&gt;Kaggle competitions&lt;/a&gt; and the subject of &lt;a class="reference external" href="https://amr4i.github.io/pdfs/nyc_taxi_times.pdf"&gt;research papers&lt;/a&gt;. We will use this as a motivating example, as it allows us to explore many steps in the data science workflow. Much of this previous work was based on the New York City Taxi data. In our example, we will focus on the New York City For Hire Vehicle dataset (e.g. Uber/Lyft).&lt;/p&gt;
&lt;p&gt;Given this, our goal is to train an XGBoost model to predict trip duration (in seconds), given a projected route, projected pickup time and known pickup/dropoff locations as well as current traffic conditions. Given those variables, we expect variables like &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;tolls&lt;/span&gt;&lt;/code&gt;, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;congestion_surcharge&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;airport_fee&lt;/span&gt;&lt;/code&gt; are available, whereas &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;sales_tax&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;tips&lt;/span&gt;&lt;/code&gt; can only be known after the trip has been completed. We will also take inspiration from the geospatial research paper above, by engineering categorical features based on the concept of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Superboroughs&lt;/span&gt;&lt;/code&gt;, which are derived from domain knowledge about trips that require bridge crossings.&lt;/p&gt;
  &lt;br&gt;
&lt;/section&gt;
&lt;section id="getting-started"&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Since it’s faster to perform exploratory analysis with data that fits in memory, we start by creating a distributed compute environment and select a representative subset of the data. Using Coiled, we spin up a Dask cluster, and after some exploration, sample 2.5% of the data (or 1.6MM rows), and write this to a single local Parquet file for ease of use.&lt;/p&gt;
&lt;p&gt;Exploratory analysis with Pandas shows our travel time is (unsurprisingly) not normally distributed, and almost certainly contains outliers. We remove outliers using the &lt;a class="reference external" href="https://online.stat.psu.edu/stat200/lesson/3/3.2"&gt;IQR Method&lt;/a&gt;. Given the knowledge no trip should have a duration with a negative value, we floor the lower bound at &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;0&lt;/span&gt; &lt;span class="pre"&gt;seconds&lt;/span&gt;&lt;/code&gt;. Boundaries are shown on the histogram plot, and filtering results in a removal of 4.6% of the data.&lt;/p&gt;
&lt;figure class="align-default" id="histogram-nyc-taxi"&gt;
&lt;img alt="https://docs.coiled.io/_images/trip_histogram.png" src="https://docs.coiled.io/_images/trip_histogram.png" style="width: 640px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Building on &lt;a class="reference external" href="https://amr4i.github.io/pdfs/nyc_taxi_times.pdf"&gt;our research paper’s&lt;/a&gt; definition of a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Superborough&lt;/span&gt;&lt;/code&gt; with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;taxi+_zone_lookup&lt;/span&gt;&lt;/code&gt; dataset, we map each Pickup and Dropoff LocationID to a Borough, and subsequently to a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Superborough&lt;/span&gt;&lt;/code&gt;, deriving a categorical feature &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;CrossSuperborough&lt;/span&gt;&lt;/code&gt; that defines whether a trip requires crossing between &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Superboroughs&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We drop columns containing data that is expected to be unavailable at the time of prediction and clean up features related to route planning, with the result being a mixture of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;float&lt;/span&gt;&lt;/code&gt;, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;int&lt;/span&gt;&lt;/code&gt;, and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;categorical&lt;/span&gt;&lt;/code&gt; variables.&lt;/p&gt;
&lt;p&gt;Finally, for the in-memory example, we prepare three models using the following estimators: &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DummyRegressor()&lt;/span&gt;&lt;/code&gt;, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;LinearRegression()&lt;/span&gt;&lt;/code&gt;, and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;XGBRegressor()&lt;/span&gt;&lt;/code&gt;, and compare their performance when evaluating against a test dataset using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;rmse&lt;/span&gt;&lt;/code&gt; metric. Results are summarized below, recalling that our travel durations are expressed in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;seconds&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div align="center"&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head"&gt;&lt;p&gt;Estimator&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;RMSE&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;DummyRegressor()&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;556.7&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;LinearRegression()&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;324.4&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;XGBRegressor()&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;236.2&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;These results, while sligthly better than those reported in the research article, mirror the trend reported by the authors.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="id1"&gt;
&lt;h2&gt;Scaling XGBoost and Optuna with Dask&lt;/h2&gt;
&lt;aside class="system-message"&gt;
&lt;p class="system-message-title"&gt;System Message: INFO/1 (&lt;span class="docutils literal"&gt;/home/runnerx/actions-runner/_work/platform/platform/python-api/docs/source/blog/xgboost-at-scale.md&lt;/span&gt;, line 65); &lt;em&gt;&lt;a href="#id1"&gt;backlink&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Duplicate implicit target name: “scaling xgboost and optuna with dask”.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;One of the enabling attributes about working in Dask is the tight integration between the pandas and Dask. Scaling our pandas feature engineering to the entire dataset with Dask is more natural than if we were to rewrite it to a different language, which accelerates the process of scaling our workload from a subset of the data to the entire dataset. Executing on this vision is accomplished by breaking our feature engineering and model training steps into two discrete pipelines, as shown in &lt;a class="reference external" href="https://github.com/coiled/dask-xgboost-nyctaxi/blob/a55ed42515d5b6a5f96c11897730d5f3b6105c5d/Feature%20Engineering.ipynb"&gt;feature engineering&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/coiled/dask-xgboost-nyctaxi/blob/a55ed42515d5b6a5f96c11897730d5f3b6105c5d/Modeling%201%20-%20Train%20an%20XGBoost%20Model%20with%20Dask.ipynb"&gt;model training&lt;/a&gt; notebooks.&lt;/p&gt;
&lt;p&gt;Our feature engineering occurs in two steps. The first loads our minimally cleaned dataset into a Dask cluster, creates a handful of columns that will ultimately become categorical variables, drops unneeded columns, and removes outliers. Our second function merges the data with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;taxi+_zone_lookup&lt;/span&gt;&lt;/code&gt; &lt;a class="reference external" href="https://data.cityofnewyork.us/Transportation/NYC-Taxi-Zones/d3c5-ddgc"&gt;data&lt;/a&gt; to create the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Superborough&lt;/span&gt;&lt;/code&gt; categorical feature.&lt;/p&gt;
&lt;p&gt;A few observations about scaling this work to the entire dataset:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;There’s active work underway to fully implement pandas Nullable datatypes, but at the time of this writing, using these datatypes across &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pandas&lt;/span&gt;&lt;/code&gt;, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Dask&lt;/span&gt;&lt;/code&gt;, &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Parquet&lt;/span&gt; &lt;span class="pre"&gt;Datasets&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;XGBoost&lt;/span&gt;&lt;/code&gt; is not recommended.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The default method for calculating &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;percentiles&lt;/span&gt;&lt;/code&gt; with dask is known to give potentially incorrect results. For more information, see &lt;a class="reference external" href="https://github.com/dask/dask/issues/6566"&gt;here&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/dask/dask/issues/731"&gt;here&lt;/a&gt;, and &lt;a class="reference external" href="https://github.com/dask/dask/issues/6566"&gt;here&lt;/a&gt;. This manifests in our outlier detection method, such that &amp;lt;0.25% of the data is discarded when performing the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;percentile&lt;/span&gt;&lt;/code&gt; calculation with dask on the entire dataset, as opposed to the 4.6% discarded our sample. The suggested solution is to use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;tdigest&lt;/span&gt;&lt;/code&gt; method, which requires &lt;a class="reference external" href="https://github.com/dask/crick"&gt;crick&lt;/a&gt;, which itself is currently being updated for compatibility with recent version of python and scipy. We attempt to mitigate this by using the 75th percentile calculated by pandas (under the assumption that our randomly sampled dataset is representative of the entire dataset), but this approach only discards 1.6% of the data, suggesting our assumption is incorrect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filtering a Dask DataFrame can lead to empty, or sparsely populated partitions. &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe-best-practices.html#repartition-to-reduce-overhead"&gt;See the following for best practices around repartitioning DataFrames for optimal performance&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;/section&gt;
&lt;section id="tuning-the-xgboost-model"&gt;
&lt;h2&gt;Tuning the XGBoost Model&lt;/h2&gt;
&lt;p&gt;Recent work by Dask and Optuna developers enabled native Dask integration with Optuna. This work, combined with the enabling of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;study.optimize&lt;/span&gt;&lt;/code&gt; in &lt;a class="reference external" href="https://github.com/optuna/optuna/issues/4049"&gt;multiple threads&lt;/a&gt; allows parallel hyperparameter optimizaton by creating a Python function that will:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Create a remote dask cluster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Load the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;feature_table&lt;/span&gt;&lt;/code&gt; from a Parquet Dataset&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Train an XGBoost model using cross-validation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Executing this function in a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;thread&lt;/span&gt;&lt;/code&gt; allows running many dask clients locally, with each client connecting to a separate remote dask cluster. Optuna enables hyperparameter optimization across these dask clusters by independently training xgboost models.&lt;/p&gt;
&lt;br&gt;
A few notes and observations about performance:
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Using categorical dtypes requires that the categories are &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;known&lt;/span&gt;&lt;/code&gt;. Writing a DataFrame with categorical datatypes to a Parquet Dataset causes &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;known&lt;/span&gt; &lt;span class="pre"&gt;categories&lt;/span&gt;&lt;/code&gt; to be lost.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scikit-learn implements cross-validation by generating indices for splitting a dataset into &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;train&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;test&lt;/span&gt;&lt;/code&gt; sets, but &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe-indexing.html#positional-indexing"&gt;positional indexing into a Dask DataFrame is inefficient&lt;/a&gt;. Here, we tackle this by making use of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DataFrame.random_split()&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating a &lt;a class="reference external" href="https://xgboost.readthedocs.io/en/stable/tutorials/dask.html"&gt;DMatrix forces lazy computation to evaluated&lt;/a&gt;, so we need to be mindful of when and how these are created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We use the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dask-ml&lt;/span&gt;&lt;/code&gt; implementation of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;mean_squared_error&lt;/span&gt;&lt;/code&gt; to keep this computation on the cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We periodically observe &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;concurrent.futures._base.CancelledError&lt;/span&gt;&lt;/code&gt; when performing our &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;cv_split&lt;/span&gt;&lt;/code&gt;. This seems to be connected to &lt;a class="reference external" href="https://github.com/dask/distributed/issues/4612"&gt;dask/distributed #4612&lt;/a&gt;. We can avoid this by adding an explicit &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;wait&lt;/span&gt;&lt;/code&gt; when calculating the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;rmse&lt;/span&gt; &lt;span class="pre"&gt;score&lt;/span&gt;&lt;/code&gt; from each training iteration.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="why-does-the-dask-cluster-seem-idle-when-training-a-xgboost-model"&gt;
&lt;h2&gt;Why Does The Dask Cluster Seem Idle When Training A XGBoost Model?&lt;/h2&gt;
&lt;p&gt;When training XGBoost models on a dask cluster, parallelization of the training step is handed off to &lt;a class="reference external" href="https://github.com/dmlc/rabit"&gt;rabit&lt;/a&gt;, which uses all available threads on each dask worker. From the user’s perspective, the dask dashboard may seem idle during this phase of the workflow, but watching the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;CPU&lt;/span&gt; &lt;span class="pre"&gt;Utilization&lt;/span&gt;&lt;/code&gt; tab will give visibility to the training step, even though the dask task stream is empty. By default, xgboost uses all available threads on each worker for training. Here, we train on a dask cluster with 10 workers, and compare the performance of multiple EC2 Instance Types, with the following execution time / behavior. Later in the post, we will explore this further.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;
&lt;div align="center"&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head"&gt;&lt;p&gt;EC2 Instance Type&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;# Cores&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Memory (GiB)&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Training time / iteration&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;m6i.xlarge&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;4&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;16&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Training doesn’t start after 5 minutes, killed&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-odd"&gt;&lt;td&gt;&lt;p&gt;m6i.2xlarge&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;8&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;32&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Training succeeds, 90–180-seconds – some trials fail with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;../rabit/include/rabit/internal/utils.h:86:&lt;/span&gt; &lt;span class="pre"&gt;Allreduce&lt;/span&gt; &lt;span class="pre"&gt;failed&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="row-even"&gt;&lt;td&gt;&lt;p&gt;m6i.4xlarge&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;16&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;64&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Training succeeds, 90–180-seconds&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;/section&gt;
&lt;section id="model-training-outcomes"&gt;
&lt;h2&gt;Model Training Outcomes&lt;/h2&gt;
&lt;p&gt;Below, we can see the outcome of our hyperparameter optimization experiment. This shows the progression of the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;rmse&lt;/span&gt;&lt;/code&gt; during the optimization process. We trained 129 separate models using 5-Fold Cross-Validation, with the best model having a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;rmse=286.4&lt;/span&gt; &lt;span class="pre"&gt;seconds&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="align-default" id="dask-optuna-optimization-history"&gt;
&lt;img alt="https://docs.coiled.io/_images/optimization_history.png" src="https://docs.coiled.io/_images/optimization_history.png" style="width: 640px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;We can also inspect the impact of different hyperparameters on the overall model performance, as shown in the Hyperparameter Importances plot below.&lt;/p&gt;
&lt;figure class="align-default" id="dask-optuna-feature-importances"&gt;
&lt;img alt="https://docs.coiled.io/_images/hyperparameter_importances.png" src="https://docs.coiled.io/_images/hyperparameter_importances.png" style="width: 678px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;At this point, we have the option of training a model using the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;best_params&lt;/span&gt;&lt;/code&gt; from our Optuna &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Study&lt;/span&gt;&lt;/code&gt; to save for deplopyment, or optionally, doing additional experimentation based on the most current set of results.&lt;/p&gt;
&lt;br&gt;
&lt;/section&gt;
&lt;section id="diving-deeper-into-training-xgboost-on-dask"&gt;
&lt;h2&gt;Diving Deeper Into Training XGBoost on Dask&lt;/h2&gt;
&lt;p&gt;In the above look at the impact of Dask workers based on different EC2 Instance types on model training, we observed dramatically different performance during the XGBoost model training phase of our workflow. We can leverage recent work with Grafana by the team at Coiled to delve into these differences.&lt;/p&gt;
&lt;figure class="align-default" id="coiled-dask-worker-m6i-xlarge-memory-xgboost"&gt;
&lt;img alt="https://docs.coiled.io/_images/m6i.xlarge_workers_xgboost.png" src="https://docs.coiled.io/_images/m6i.xlarge_workers_xgboost.png" style="width: 720px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;The above Grafana plot show cluster-wide memory-usage during the dispatch train step for ourour workflow.  The white line is total memory usage, the green line is total dask memory usage, and the yellow line is &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;total_occupied_memory&lt;/span&gt;&lt;/code&gt;.  Normally, we expect dask’s view of memory and the host’s view of memory to be very close.  Recall however, that training the XGBoost model is dispatched to the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;rabit&lt;/span&gt;&lt;/code&gt; library.&lt;/p&gt;
&lt;p&gt;At about the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;09:30:10&lt;/span&gt; &lt;span class="pre"&gt;mark&lt;/span&gt;&lt;/code&gt;, the Grafana dashboard shows memory pressure falling, which is consistent with what is reported on the dask dashboard. But host memory pressure spikes at &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;09:30:15&lt;/span&gt;&lt;/code&gt;. This happens when model training is dispatched to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;rabit&lt;/span&gt;&lt;/code&gt;. Dask has no visibility to this process, so everything looks fine on the dask dashboard, but Grafana sees this on the host. In fact, the memory pressure was so high that at least one EC2 instance was unable to report metrics for about 20 seconds, as evidenced by the apparent dip in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;total_cluster_memory&lt;/span&gt;&lt;/code&gt;. We also see that, during this &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;17-second&lt;/span&gt; &lt;span class="pre"&gt;time&lt;/span&gt; &lt;span class="pre"&gt;window&lt;/span&gt;&lt;/code&gt;, no tasks are completed.&lt;/p&gt;
&lt;p&gt;By comparison, the following plots show the same views during the dispatch step, when run on a cluster provisioned with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;m6i.4xlarge&lt;/span&gt;&lt;/code&gt; workers.&lt;/p&gt;
&lt;figure class="align-default" id="coiled-dask-worker-m6i-4xlarge-memory-xgboost"&gt;
&lt;img alt="https://docs.coiled.io/_images/m6i.4xlarge_memory.png" src="https://docs.coiled.io/_images/m6i.4xlarge_memory.png" style="width: 701px;" /&gt;
&lt;/figure&gt;
&lt;figure class="align-default" id="coiled-dask-worker-m6i-4xlarge-dispatch-xgboost"&gt;
&lt;img alt="https://docs.coiled.io/_images/m6i.4xlarge_dispatch.png" src="https://docs.coiled.io/_images/m6i.4xlarge_dispatch.png" style="width: 701px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Notice the much smaller gap between the host’s view of memory usage and dask’s view of memory usage on the cluster.&lt;/p&gt;
&lt;p&gt;To extend on this further, the following plot shows total memory usage during an &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;AllReduce&lt;/span&gt; &lt;span class="pre"&gt;error&lt;/span&gt;&lt;/code&gt; event. Observe the gap between the host’s view of memory and dask’s view.&lt;/p&gt;
&lt;figure class="align-default" id="coiled-dask-worker-m6i-2xlarge"&gt;
&lt;img alt="https://docs.coiled.io/_images/m6i.2xlarge_memory.png" src="https://docs.coiled.io/_images/m6i.2xlarge_memory.png" style="width: 707px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;While we have no idea how this will (or will not) manifest when deploying Dask to different deployment environments, it’s a potential pitfall when training XGBoost models with Dask. It also presents a potential opportunity for enhancements in future work.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/xgboost-at-scale.html"/>
    <summary>XGBoost is one of the most well-known libraries among data scientists, having become one of the top choices among Kaggle competitors. It is performant in a wide of array of supervised machine learning problems, implements scalable training through the rabit library, and integrates with many big data processing tools, including Dask.</summary>
    <category term="largescalemodeltraining" label="large scale model training"/>
    <category term="optuna" label="optuna"/>
    <category term="xgboost" label="xgboost"/>
    <published>2023-01-06T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/aws-iam-changes.html</id>
    <title>Handling Unexpected AWS IAM Changes</title>
    <updated>2023-01-06T00:00:00+00:00</updated>
    <author>
      <name>David Chudzicki</name>
    </author>
    <content type="html">&lt;section id="handling-unexpected-aws-iam-changes"&gt;

&lt;p&gt;&lt;strong&gt;The cloud is tricky! You might think the rules that determine which IAM permissions are required for which actions will continue to apply in the same way. You might think they’d apply the same way to different AWS accounts. Or that if these things aren’t true, at least AWS will let you know. (I did.) You’d be wrong!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Coiled operates in our users’ AWS (or Google Cloud) accounts, with IAM permissions they assign to us. We have &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/aws/cli.html"&gt;a CLI&lt;/a&gt; that lets users get set up by assigning us a minimal set of IAM permissions to do stuff in their account (like run EC2 instances), and we spin up Dask clusters when they ask us to. Or they can manually set things up by creating roles with &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/aws/reference.html#iam-policies"&gt;the permissions we document&lt;/a&gt; that we need.&lt;/p&gt;
&lt;p&gt;We also “tag” the resources we create, to help our users track down what Coiled is up to (for cost tracking or other auditing).&lt;/p&gt;
&lt;p&gt;Someone seems to be on a rampage to fix some bugs in how IAM permissions apply to tags, and it’s been a bit rough on some of our users.&lt;/p&gt;
&lt;section id="act-1-a-logs-tagloggroup-problem-in-november"&gt;
&lt;h2&gt;Act 1: a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;logs:TagLogGroup&lt;/span&gt;&lt;/code&gt; problem in November&lt;/h2&gt;
&lt;p&gt;When a user runs &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;coiled&lt;/span&gt; &lt;span class="pre"&gt;setup&lt;/span&gt; &lt;span class="pre"&gt;aws&lt;/span&gt;&lt;/code&gt;, we create a CloudWatch log group in their account, where we’ll send the logs from their Dask cluster.&lt;/p&gt;
&lt;p&gt;Back in November, a new user in a small AWS region reported a problem setting up Coiled:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;redacted&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;authorized&lt;/span&gt;
&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;perform&lt;/span&gt; &lt;span class="n"&gt;CreateLogGroup&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’d never seen this before and couldn’t reproduce it in any of our larger regions, but could reproduce in the user’s region.&lt;/p&gt;
&lt;p&gt;This was a bit of a mystery, but not &lt;em&gt;that&lt;/em&gt; much of one. I guess someone realized that they hadn’t been requiring the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;TagLogGroup&lt;/span&gt;&lt;/code&gt; permission to create a log group with tags (only to tag an existing log group?), and fixed it. Maybe the “fix” appeared first in this small alphabetically-early region, and broke us there.&lt;/p&gt;
&lt;p&gt;We added &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;logs:TagLogGroup&lt;/span&gt;&lt;/code&gt; to the list of permissions we ask users to give us, and all was well.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="act-2-a-warning-for-january"&gt;
&lt;h2&gt;Act 2: a warning for January!&lt;/h2&gt;
&lt;p&gt;On December 12, I received an email (well, many emails across several languages) that seemed related:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;As of October 30, 2022, tagging is supported for the “Destination” resource. Previously, CloudWatch Logs supported tagging only for the “Log Group” resource. We recommend that, for your IAM policies that are used to access the CreateLogGroup API, you add logs:TagResource permission to your IAM policies by January 31, 2023. The new logs:TagResource permission will not be required for existing accounts that previously used CreateLogGroup API with tags.&lt;/p&gt;
&lt;p&gt;In order to tag new log groups using the CreateLogGroup API, we recommend you add logs:TagResource permission to your IAM policies …&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;At first I read this as a belated warning about the behavior from November. But no, it’s not actually about a change from needing &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;logs:TagLogGroup&lt;/span&gt;&lt;/code&gt; to needing &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;logs:TagResource&lt;/span&gt;&lt;/code&gt;. Thanks for the warning! That would have been nice back in November. (I still haven’t seen any acknowledgement about the behavior change requiring &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;logs:TagLogGroup&lt;/span&gt;&lt;/code&gt;.)&lt;/p&gt;
&lt;/section&gt;
&lt;section id="act-3-an-ecr-tagresource-problem-in-december"&gt;
&lt;h2&gt;Act 3: an &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;ecr:TagResource&lt;/span&gt;&lt;/code&gt; problem in December&lt;/h2&gt;
&lt;p&gt;This morning a new user reported another IAM problem. They were trying to create a Coiled “software environment” (just a Docker image we build and push to ECR for them), and saw:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;botocore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;An&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;occurred&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AccessDeniedException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;calling&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;CreateRepository&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;redacted&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;coiled&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;authorized&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ecr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;TagResource&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;redacted&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;because&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;based&lt;/span&gt;
 &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="n"&gt;allows&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;ecr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;TagResource&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I quickly reproduced the problem and helped the user past it by giving us the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;ecr:TagResource&lt;/span&gt;&lt;/code&gt; permission. We’ve seen this before! Seems like they should have been requiring that all along, and broke us by requiring it now.&lt;/p&gt;
&lt;p&gt;My next question was: Why isn’t this affecting more users?&lt;/p&gt;
&lt;p&gt;Assisted by the hint in AWS’s December 12th email, I realized that maybe accounts which had previously created an ECR repo (tagged) would be unaffected. After confirming this hypothesis (my first repro was lucky to use a disposable account, which had never created an ECR repo, or at least not in that region), I exited “emergency” mode since I now knew existing users’ workflows aren’t at risk.&lt;/p&gt;
&lt;p&gt;I haven’t found any public discussion of this change.&lt;/p&gt;
&lt;p&gt;We deployed a fix and set up notifications to alert us if this ever happens again so we can individually help affected users (which it might, since a few users “set up” Coiled with the old permissions, but have not yet tried to create a software environment).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="act-4-test-in-new-accounts"&gt;
&lt;h2&gt;Act 4: Test in new accounts…?&lt;/h2&gt;
&lt;p&gt;It seems like AWS’s strategy is to make these changes in a way that will only affect accounts which haven’t used those permissions before.&lt;/p&gt;
&lt;p&gt;That keeps these changes from breaking existing users, and helps folks choosing a collection of IAM permissions for the first time. But we’re asking for a fixed collection of permissions from AWS accounts that may have never taken any of these actions before.&lt;/p&gt;
&lt;p&gt;We already do testing in accounts that are wiped clean nightly, but that’s not enough to catch these issues.&lt;/p&gt;
&lt;p&gt;I don’t know how much more of this to expect (or how much will be announced to us ahead of time). If this is going to keep happening, it would probably pay off to set up a process to programmatically create completely brand new accounts in which to test Coiled.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="obligatory-pitch-use-coiled"&gt;
&lt;h2&gt;Obligatory pitch: Use Coiled!&lt;/h2&gt;
&lt;p&gt;At Coiled we love these absurd details, and we love dealing with them so you don’t have to. We pay close attention to strange cloud behaviors and have fixes in place before most (or often any) users are aware.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/aws-iam-changes.html"/>
    <summary>The cloud is tricky! You might think the rules that determine which IAM permissions are required for which actions will continue to apply in the same way. You might think they’d apply the same way to different AWS accounts. Or that if these things aren’t true, at least AWS will let you know. (I did.) You’d be wrong!</summary>
    <category term="aws" label="aws"/>
    <published>2023-01-06T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/aws-cost-explorer-tips.html</id>
    <title>AWS Cost Explorer Tips and Tricks</title>
    <updated>2023-01-06T00:00:00+00:00</updated>
    <author>
      <name>Nat Tabris</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jan 6, 2023&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;7m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="aws-cost-explorer-tips-and-tricks"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;a class="reference external image-reference" href="https://tabris.us/"&gt;&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://avatars.githubusercontent.com/u/46289310?v=4" /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Nat Tabris&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Spending time in AWS Cost Explorer is one of the best ways to understand what’s going on in your AWS account. It’s one of the few places in the AWS Console where you can get a global view of your account or even of your entire organization.&lt;/p&gt;
&lt;p&gt;In this post, I’m going to share some views that I’ve found especially useful when digging in to our costs for running data-science workloads on EC2 instances. Along the way, you might also learn some ways to reduce those costs.&lt;/p&gt;
&lt;p&gt;Before we dig in, let’s talk about some basics. You can access Cost Explorer by logging in to the AWS Console and following &lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer"&gt;this link&lt;/a&gt; (or going to the “AWS Cost Management” service and clicking “Cost Explorer” in the side menu).&lt;/p&gt;
&lt;p&gt;Cost Explorer provides ways to filter and group your cost data. You can select time ranges, and you can see hourly, daily, and monthly costs, although hourly data (and other fine-grained views) are only available for more recent costs.&lt;/p&gt;
&lt;p&gt;For a high-level view, I often start by disabling all filters and grouping by “Service”:&lt;/p&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093326-12d78d07-d66c-42c7-bd02-95b045f278d5.png" style="width: 1069px;" /&gt;
&lt;p&gt;and then by “Region”:&lt;/p&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093393-d45f5c2a-f902-4d5e-9b6a-e3001e3d0a52.png" style="width: 1067px;" /&gt;
&lt;p&gt;Depending on what you see, you might want to explore in more depth by (e.g.) filtering to a specific region and looking at the breakdown by service for just that region. Or filter to a service and then grouping by region to see where the service is being used.&lt;/p&gt;
&lt;p&gt;AWS Cost Explorer has powerful filtering and grouping functions. Powerful—but also not entirely intuitive.&lt;/p&gt;
&lt;p&gt;At Coiled we make lots of clusters of EC2 instances for running big-data computations at scale. I personally spend a lot of time in AWS Cost Explorer trying to understand and optimize costs related to these clusters. In this post, I’ll explain the most helpful ways I’ve found for finding and understanding these costs. I’ll also share some tips I’ve learned about how to reduce these costs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;&lt;strong&gt;A brief word from our sponsors&lt;/strong&gt;: Coiled makes it very easy to scale up your Python code in the cloud. We deploy in your AWS or Google Cloud account, and we configure the infrastructure so that you get sensible security &lt;em&gt;and cost-savings&lt;/em&gt; by default. If you have Python code that you need to run at scale, &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;give us a try&lt;/a&gt;!&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;section id="spot-vs-on-demand-instance-usage"&gt;
&lt;h2&gt;Spot vs On Demand Instance Usage&lt;/h2&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Group by&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Filters&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;Purchase option&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;: EC2-Instances&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;If you want to see cost and hours, add “Usage type” filter to everything that includes “Box” or “Spot”. (Open the Usage type filter dropdown, type “box” and click select all. Then type “spot” and click select all again. Now apply and you should get usage in hours for both on demand “box” and spot instances.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;&lt;strong&gt;Cost savings tip&lt;/strong&gt;: As you can see, spot is a great way to save money! In December, our usage hours for on demand is very low, yet this accounts for over half of our cost. See &lt;a class="reference external" href="https://www.coiled.io/blog/save-money-with-spot"&gt;this blog post&lt;/a&gt; for our approach to using spot instances well.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093447-859ec98b-ab43-44da-8878-a06f22a69a3a.png" style="width: 1067px;" /&gt;
&lt;p&gt;Once you’ve logged in to the AWS Console, here’s a link to bring up this view for your own AWS account: &lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22Amazon%20Elastic%20Compute%20Cloud%20-%20Compute%22,%22displayValue%22:%22EC2-Instances%20(Elastic%20Compute%20Cloud%20-%20Compute)%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageType%22,%22displayValue%22:%22Usage%20type%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22BoxUsage:c5.2xlarge%22,%22displayValue%22:%22BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.2xlarge%22,%22displayValue%22:%22BoxUsage:m5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.4xlarge%22,%22displayValue%22:%22BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.8xlarge%22,%22displayValue%22:%22BoxUsage:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.large%22,%22displayValue%22:%22BoxUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5a.8xlarge%22,%22displayValue%22:%22BoxUsage:m5a.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m6i.4xlarge%22,%22displayValue%22:%22BoxUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.2xlarge%22,%22displayValue%22:%22BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.large%22,%22displayValue%22:%22BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.medium%22,%22displayValue%22:%22BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.xlarge%22,%22displayValue%22:%22BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.2xlarge%22,%22displayValue%22:%22BoxUsage:t3a.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.large%22,%22displayValue%22:%22BoxUsage:t3a.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.medium%22,%22displayValue%22:%22BoxUsage:t3a.medium%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.xlarge%22,%22displayValue%22:%22BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:c5.large%22,%22displayValue%22:%22EU-BoxUsage:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5a.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5a.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5n.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5n.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.medium%22,%22displayValue%22:%22EU-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3a.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3a.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3a.xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.large%22,%22displayValue%22:%22EUC1-BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.medium%22,%22displayValue%22:%22EUC1-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.xlarge%22,%22displayValue%22:%22EUC1-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.12xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.12xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.large%22,%22displayValue%22:%22USE2-BoxUsage:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5n.18xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5n.18xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c7g.large%22,%22displayValue%22:%22USE2-BoxUsage:c7g.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:g4dn.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:g4dn.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:i3.large%22,%22displayValue%22:%22USE2-BoxUsage:i3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:i3.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:i3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.16xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.16xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.large%22,%22displayValue%22:%22USE2-BoxUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5a.large%22,%22displayValue%22:%22USE2-BoxUsage:m5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5a.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5ad.large%22,%22displayValue%22:%22USE2-BoxUsage:m5ad.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5ad.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5ad.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5d.large%22,%22displayValue%22:%22USE2-BoxUsage:m5d.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5n.large%22,%22displayValue%22:%22USE2-BoxUsage:m5n.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5zn.large%22,%22displayValue%22:%22USE2-BoxUsage:m5zn.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6a.large%22,%22displayValue%22:%22USE2-BoxUsage:m6a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6g.large%22,%22displayValue%22:%22USE2-BoxUsage:m6g.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6g.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6g.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.large%22,%22displayValue%22:%22USE2-BoxUsage:m6i.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6id.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.large%22,%22displayValue%22:%22USE2-BoxUsage:m6id.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6id.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6in.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6in.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5a.large%22,%22displayValue%22:%22USE2-BoxUsage:r5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5b.large%22,%22displayValue%22:%22USE2-BoxUsage:r5b.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.32xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.32xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t2.medium%22,%22displayValue%22:%22USE2-BoxUsage:t2.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.large%22,%22displayValue%22:%22USE2-BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.medium%22,%22displayValue%22:%22USE2-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.large%22,%22displayValue%22:%22USE2-BoxUsage:t3a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.medium%22,%22displayValue%22:%22USE2-BoxUsage:t3a.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t4g.large%22,%22displayValue%22:%22USE2-BoxUsage:t4g.large%20(Hrs)%22%7D,%7B%22value%22:%22USW1-BoxUsage:t3.medium%22,%22displayValue%22:%22USW1-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22USW2-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:m5.xlarge%22,%22displayValue%22:%22USW2-BoxUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:t3.medium%22,%22displayValue%22:%22USW2-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:t3.xlarge%22,%22displayValue%22:%22USW2-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22SpotUsage:t3.medium%22,%22displayValue%22:%22SpotUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22SpotUsage:t3.xlarge%22,%22displayValue%22:%22SpotUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:c5a.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:c5a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5.large%22,%22displayValue%22:%22USE2-SpotUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5a.large%22,%22displayValue%22:%22USE2-SpotUsage:m5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5ad.large%22,%22displayValue%22:%22USE2-SpotUsage:m5ad.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5dn.large%22,%22displayValue%22:%22USE2-SpotUsage:m5dn.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5n.large%22,%22displayValue%22:%22USE2-SpotUsage:m5n.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5zn.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m5zn.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6a.large%22,%22displayValue%22:%22USE2-SpotUsage:m6a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6a.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.2xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.4xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.large%22,%22displayValue%22:%22USE2-SpotUsage:m6i.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.4xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6id.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.large%22,%22displayValue%22:%22USE2-SpotUsage:m6id.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6id.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.2xlarge%22,%22displayValue%22:%22USE2-SpotUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.large%22,%22displayValue%22:%22USE2-SpotUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.medium%22,%22displayValue%22:%22USE2-SpotUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3a.large%22,%22displayValue%22:%22USE2-SpotUsage:t3a.large%20(Hrs)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22PurchaseType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;Spot vs On Demand (by month, July–December 2022)&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="instance-types"&gt;
&lt;h2&gt;Instance Types&lt;/h2&gt;
&lt;p&gt;Same as the Spot vs On Demand, but group by Instance type.&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Group by&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Filters&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;Instance type&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;: EC2-Instances&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;If you want to see cost and hours, add “Usage type” filter to everything that includes “Box” or “Spot”.&lt;/p&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093482-f20d0583-5cd1-439a-8ba7-42b00eff4d41.png" style="width: 1064px;" /&gt;
&lt;p&gt;&lt;strong&gt;See this in your own AWS account&lt;/strong&gt;: &lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22Amazon%20Elastic%20Compute%20Cloud%20-%20Compute%22,%22displayValue%22:%22EC2-Instances%20(Elastic%20Compute%20Cloud%20-%20Compute)%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageType%22,%22displayValue%22:%22Usage%20type%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22BoxUsage:c5.2xlarge%22,%22displayValue%22:%22BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.2xlarge%22,%22displayValue%22:%22BoxUsage:m5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.4xlarge%22,%22displayValue%22:%22BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.8xlarge%22,%22displayValue%22:%22BoxUsage:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.large%22,%22displayValue%22:%22BoxUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5a.8xlarge%22,%22displayValue%22:%22BoxUsage:m5a.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m6i.4xlarge%22,%22displayValue%22:%22BoxUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.2xlarge%22,%22displayValue%22:%22BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.large%22,%22displayValue%22:%22BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.medium%22,%22displayValue%22:%22BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.xlarge%22,%22displayValue%22:%22BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.2xlarge%22,%22displayValue%22:%22BoxUsage:t3a.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.large%22,%22displayValue%22:%22BoxUsage:t3a.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.medium%22,%22displayValue%22:%22BoxUsage:t3a.medium%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.xlarge%22,%22displayValue%22:%22BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:c5.large%22,%22displayValue%22:%22EU-BoxUsage:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5a.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5a.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5n.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5n.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.medium%22,%22displayValue%22:%22EU-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3a.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3a.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3a.xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.large%22,%22displayValue%22:%22EUC1-BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.medium%22,%22displayValue%22:%22EUC1-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.xlarge%22,%22displayValue%22:%22EUC1-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.12xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.12xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.large%22,%22displayValue%22:%22USE2-BoxUsage:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5n.18xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5n.18xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c7g.large%22,%22displayValue%22:%22USE2-BoxUsage:c7g.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:g4dn.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:g4dn.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:i3.large%22,%22displayValue%22:%22USE2-BoxUsage:i3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:i3.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:i3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.16xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.16xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.large%22,%22displayValue%22:%22USE2-BoxUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5a.large%22,%22displayValue%22:%22USE2-BoxUsage:m5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5a.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5ad.large%22,%22displayValue%22:%22USE2-BoxUsage:m5ad.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5ad.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5ad.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5d.large%22,%22displayValue%22:%22USE2-BoxUsage:m5d.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5n.large%22,%22displayValue%22:%22USE2-BoxUsage:m5n.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5zn.large%22,%22displayValue%22:%22USE2-BoxUsage:m5zn.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6a.large%22,%22displayValue%22:%22USE2-BoxUsage:m6a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6g.large%22,%22displayValue%22:%22USE2-BoxUsage:m6g.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6g.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6g.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.large%22,%22displayValue%22:%22USE2-BoxUsage:m6i.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6id.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.large%22,%22displayValue%22:%22USE2-BoxUsage:m6id.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6id.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6in.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6in.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5a.large%22,%22displayValue%22:%22USE2-BoxUsage:r5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5b.large%22,%22displayValue%22:%22USE2-BoxUsage:r5b.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.32xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.32xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t2.medium%22,%22displayValue%22:%22USE2-BoxUsage:t2.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.large%22,%22displayValue%22:%22USE2-BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.medium%22,%22displayValue%22:%22USE2-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.large%22,%22displayValue%22:%22USE2-BoxUsage:t3a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.medium%22,%22displayValue%22:%22USE2-BoxUsage:t3a.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t4g.large%22,%22displayValue%22:%22USE2-BoxUsage:t4g.large%20(Hrs)%22%7D,%7B%22value%22:%22USW1-BoxUsage:t3.medium%22,%22displayValue%22:%22USW1-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22USW2-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:m5.xlarge%22,%22displayValue%22:%22USW2-BoxUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:t3.medium%22,%22displayValue%22:%22USW2-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:t3.xlarge%22,%22displayValue%22:%22USW2-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22SpotUsage:t3.medium%22,%22displayValue%22:%22SpotUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22SpotUsage:t3.xlarge%22,%22displayValue%22:%22SpotUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:c5a.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:c5a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5.large%22,%22displayValue%22:%22USE2-SpotUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5a.large%22,%22displayValue%22:%22USE2-SpotUsage:m5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5ad.large%22,%22displayValue%22:%22USE2-SpotUsage:m5ad.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5dn.large%22,%22displayValue%22:%22USE2-SpotUsage:m5dn.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5n.large%22,%22displayValue%22:%22USE2-SpotUsage:m5n.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5zn.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m5zn.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6a.large%22,%22displayValue%22:%22USE2-SpotUsage:m6a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6a.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.2xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.4xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.large%22,%22displayValue%22:%22USE2-SpotUsage:m6i.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.4xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6id.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.large%22,%22displayValue%22:%22USE2-SpotUsage:m6id.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6id.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.2xlarge%22,%22displayValue%22:%22USE2-SpotUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.large%22,%22displayValue%22:%22USE2-SpotUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.medium%22,%22displayValue%22:%22USE2-SpotUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3a.large%22,%22displayValue%22:%22USE2-SpotUsage:t3a.large%20(Hrs)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22InstanceType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;Instance types (by month, July–December 2022)&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="burstable-instances"&gt;
&lt;h2&gt;Burstable Instances&lt;/h2&gt;
&lt;p&gt;In addition to the base cost for burstable instances (t2/t3/t4g), you might pay extra if you use more than the “baseline” of CPU util.&lt;/p&gt;
&lt;p&gt;When you have burstable instances and are using less than the baseline, you’ll accumulate credits that you use when you go above the baseline. But if you burst more than your accumulated credits, you’ll pay extra, and that’s what we’re looking at here.&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Group by&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Filters&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;Usage type&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;: EC2 - Other&lt;br /&gt;&lt;strong&gt;Usage type&lt;/strong&gt;: include everything with “CPUCredits”&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093542-2d73960b-a7ab-4ce2-abae-eb6b707a862d.png" style="width: 1068px;" /&gt;
&lt;p&gt;&lt;strong&gt;See this in your own AWS account&lt;/strong&gt;: &lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageType%22,%22displayValue%22:%22Usage%20type%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22CPUCredits:t3%22,%22displayValue%22:%22CPUCredits:t3%20(vCPU-Hours)%22%7D,%7B%22value%22:%22CPUCredits:t3a%22,%22displayValue%22:%22CPUCredits:t3a%20(vCPU-Hours)%22%7D,%7B%22value%22:%22EU-CPUCredits:t3%22,%22displayValue%22:%22EU-CPUCredits:t3%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USE2-CPUCredits:t3%22,%22displayValue%22:%22USE2-CPUCredits:t3%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USE2-CPUCredits:t3a%22,%22displayValue%22:%22USE2-CPUCredits:t3a%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USE2-CPUCredits:t4g%22,%22displayValue%22:%22USE2-CPUCredits:t4g%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USW1-CPUCredits:t3%22,%22displayValue%22:%22USW1-CPUCredits:t3%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USW2-CPUCredits:t3%22,%22displayValue%22:%22USW2-CPUCredits:t3%20(vCPU-Hours)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;Extra burst credits (by month, July–December 2022)&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;&lt;strong&gt;Cost savings tip&lt;/strong&gt;: As you can see, extra burst credits can be expensive for CPU-intensive workloads! We’ve found that by switching from t3 to m6i instance types we’ve significantly reduced our AWS bill. See &lt;a class="reference external" href="https://blog.coiled.io/blog/burstable-vs-nonburstable.html"&gt;this blog post&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="ebs-disks"&gt;
&lt;h2&gt;EBS Disks&lt;/h2&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Group by&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Filters&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;Usage type&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;Usage type&lt;/strong&gt;: include everything with “EBS”&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;This will show both cost and usage in GB-Months. (See &lt;a class="reference external" href="https://aws.amazon.com/ebs/pricing/"&gt;AWS docs&lt;/a&gt; for details about EBS pricing.)&lt;/p&gt;
&lt;p&gt;Note that if you have Amazon Machine Images (AMIs), these often are backed by EBS snapshots so will be shown here under “SnapshotUsage”.&lt;/p&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093594-191736de-c418-4599-8b9b-cdcb0a2aca42.png" style="width: 1066px;" /&gt;
&lt;p&gt;&lt;strong&gt;See this in your own AWS account&lt;/strong&gt;: &lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageType%22,%22displayValue%22:%22Usage%20type%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EBS:VolumeUsage.gp2%22,%22displayValue%22:%22EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22EBS:VolumeUsage.gp3%22,%22displayValue%22:%22EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22EBSOptimized:m5a.8xlarge%22,%22displayValue%22:%22EBSOptimized:m5a.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-EBS:SnapshotUsage%22,%22displayValue%22:%22EU-EBS:SnapshotUsage%20(GB-Month)%22%7D,%7B%22value%22:%22EU-EBS:VolumeUsage.gp2%22,%22displayValue%22:%22EU-EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22EU-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22EU-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22EUC1-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22EUC1-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22USE2-EBS:SnapshotUsage%22,%22displayValue%22:%22USE2-EBS:SnapshotUsage%20(GB-Month)%22%7D,%7B%22value%22:%22USE2-EBS:VolumeUsage.gp2%22,%22displayValue%22:%22USE2-EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22USE2-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22USE2-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:c5.12xlarge%22,%22displayValue%22:%22USE2-EBSOptimized:c5.12xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:c5.large%22,%22displayValue%22:%22USE2-EBSOptimized:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:c5.xlarge%22,%22displayValue%22:%22USE2-EBSOptimized:c5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:c5n.18xlarge%22,%22displayValue%22:%22USE2-EBSOptimized:c5n.18xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:m5.8xlarge%22,%22displayValue%22:%22USE2-EBSOptimized:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW1-EBS:VolumeUsage.gp2%22,%22displayValue%22:%22USW1-EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22USW1-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22USW1-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22USW2-EBS:VolumeUsage.gp2%22,%22displayValue%22:%22USW2-EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22USW2-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22USW2-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;EBS (by month, July–December 2022)&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;&lt;strong&gt;Cost savings tip&lt;/strong&gt;: EBS is relatively cheap compared to EC2 but can still add up. In September our cost for compute was about $2200 and our cost for EBS disks was about $250. We’ve reduced these costs by using smaller disks as the default (with the option to increase disk size for specific workloads).&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="network-traffic-for-ec2-instances"&gt;
&lt;h2&gt;Network traffic for EC2 instances&lt;/h2&gt;
&lt;p&gt;You pay for moving data from AWS to the internet, you pay for moving data between regions, and you pay for moving data between zones inside a region.&lt;/p&gt;
&lt;p&gt;For reasons unknown to me, most types of traffic are listed under “EC2-Instances” service but interzone traffic shows under the “EC2 - Other” service.&lt;/p&gt;
&lt;p&gt;To see all of this traffic, broken down by type:&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Group by&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Filters&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;Usage type&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;: EC2-Instances and EC2 - Other&lt;br /&gt;&lt;strong&gt;Usage type group&lt;/strong&gt;: include everything with “EC2: Data”&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093618-fd9b2470-adcd-4cce-bb61-3ee614405b7a.png" style="width: 1064px;" /&gt;
&lt;p&gt;&lt;strong&gt;See this in your own AWS account&lt;/strong&gt;: &lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22Amazon%20Elastic%20Compute%20Cloud%20-%20Compute%22,%22displayValue%22:%22EC2-Instances%20(Elastic%20Compute%20Cloud%20-%20Compute)%22%7D,%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageTypeGroup%22,%22displayValue%22:%22Usage%20type%20group%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20CloudFront%20(Out)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20CloudFront%20(Out)%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Inter%20AZ%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Inter%20AZ%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Internet%20(In)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Internet%20(In)%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Internet%20(Out)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Internet%20(Out)%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Region%20to%20Region%20(In)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Region%20to%20Region%20(In)%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Region%20to%20Region%20(Out)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Region%20to%20Region%20(Out)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;EC2 traffic breakdown (by month, July–December 2022)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As you can see, there’s lots of “Other” traffic that didn’t cost anything. Presumably this is traffic to your EC2 instances, which is free:&lt;/p&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093708-134eb7c2-58bb-4b6b-b9ad-1a666eb01053.png" style="width: 533px;" /&gt;
&lt;p&gt;If you want to see just one type of traffic, you can be more specific in the Usage type group filter. For example, here’s cost and usage for traffic between zones:&lt;/p&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093770-3bc00712-d0f4-45ac-8c7e-35d3a9fb83bd.png" style="width: 1055px;" /&gt;
&lt;p&gt;&lt;strong&gt;See this in your own AWS account&lt;/strong&gt;: &lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22UsageTypeGroup%22,%22displayValue%22:%22Usage%20type%20group%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Inter%20AZ%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Inter%20AZ%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;Interzone traffic (by month, July–December 2022)&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;&lt;strong&gt;Cost savings tip&lt;/strong&gt;: We’re running distributed Python computations at scale and for some workloads there’s lots of data moved between compute nodes. For a while we experimented with clusters that were in multiple availability zones. As you can see, this was expensive. We now make sure that all the instances for a cluster are in the same zone.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="nat-gateways"&gt;
&lt;h2&gt;NAT Gateways&lt;/h2&gt;
&lt;p&gt;We don’t use NAT Gateways for our compute clusters, but we do use them for our control-plane infrastructure.&lt;/p&gt;
&lt;p&gt;NAT Gateways have a fixed hourly cost and a per GB traffic cost.&lt;/p&gt;
&lt;p&gt;You can see both together like this:&lt;/p&gt;
&lt;div class="pst-scrollable-table-container"&gt;&lt;table class="table"&gt;
&lt;thead&gt;
&lt;tr class="row-odd"&gt;&lt;th class="head text-left"&gt;&lt;p&gt;Group by&lt;/p&gt;&lt;/th&gt;
&lt;th class="head text-left"&gt;&lt;p&gt;Filters&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="row-even"&gt;&lt;td class="text-left"&gt;&lt;p&gt;Usage type&lt;/p&gt;&lt;/td&gt;
&lt;td class="text-left"&gt;&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;: EC2 - Other&lt;br /&gt;&lt;strong&gt;Usage type group&lt;/strong&gt;: “EC2: NAT Gateway - Data Processed” and “EC2: NAT Gateway - Running Hours”&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093803-d4a4f483-0182-4b70-902b-22fb076129d0.png" style="width: 1060px;" /&gt;
&lt;p&gt;&lt;strong&gt;See this in your own AWS account&lt;/strong&gt;: &lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageTypeGroup%22,%22displayValue%22:%22Usage%20type%20group%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2:%20NAT%20Gateway%20-%20Data%20Processed%22,%22displayValue%22:%22EC2:%20NAT%20Gateway%20-%20Data%20Processed%22%7D,%7B%22value%22:%22EC2:%20NAT%20Gateway%20-%20Running%20Hours%22,%22displayValue%22:%22EC2:%20NAT%20Gateway%20-%20Running%20Hours%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;NAT Gateway total cost&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you restrict the usage type group filter to just “EC2: NAT Gateway - Data Processed”, you’ll get both cost and GB usage:&lt;/p&gt;
&lt;img alt="image" src="https://user-images.githubusercontent.com/46289310/211093826-5853840b-0b20-47d3-828c-1b0ffbad154b.png" style="width: 1061px;" /&gt;
&lt;p&gt;&lt;strong&gt;See this in your own AWS account&lt;/strong&gt;: &lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageTypeGroup%22,%22displayValue%22:%22Usage%20type%20group%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2:%20NAT%20Gateway%20-%20Data%20Processed%22,%22displayValue%22:%22EC2:%20NAT%20Gateway%20-%20Data%20Processed%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;NAT Gateway data transfer&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;&lt;strong&gt;Cost savings tip&lt;/strong&gt;: If you can, avoid using NAT Gateways. For our compute clusters, we use public IP addresses (for outbound connections) and then use security groups to block inbound connections to the workers (we only want inbound connections to the scheduler node).&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="other-costs"&gt;
&lt;h2&gt;Other costs&lt;/h2&gt;
&lt;p&gt;There’s a good chance you’ll have costs for other services associated with your workloads. In our case, we have&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;S3 storage costs (transfer costs are minimal because we keep our EC2 instances in the same region as the relevant S3 buckets)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ECR storage and transfer (we build container images with our code and store these images in ECR)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CloudWatch logs (usually very low cost)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The best way to discover additional costs in your own account is by disabling all filters and grouping by “Service”. When there’s a service with a non-trivial (or higher than expected cost), I’ll usually dig in by filtering to that service and then grouping by various dimensions: region, usage type, maybe even by resource or by any tags that we use to distinguish costs for different projects.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="cost-explorer-links"&gt;
&lt;h2&gt;Cost Explorer Links&lt;/h2&gt;
&lt;p&gt;Use these views to start your own cost exploration.&lt;/p&gt;
&lt;p&gt;Log in to the AWS Console, and if you use AWS roles, switch to the role associated with the account you want to explore. The links below should then take you directly to Cost Explorer with the filters and grouping already selected for you.&lt;/p&gt;
&lt;p&gt;Once the view is loaded, you can change the date range, add filters (to, e.g., look at a specific region), see hourly or daily costs, or try other ways of grouping the data.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22Amazon%20Elastic%20Compute%20Cloud%20-%20Compute%22,%22displayValue%22:%22EC2-Instances%20(Elastic%20Compute%20Cloud%20-%20Compute)%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageType%22,%22displayValue%22:%22Usage%20type%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22BoxUsage:c5.2xlarge%22,%22displayValue%22:%22BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.2xlarge%22,%22displayValue%22:%22BoxUsage:m5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.4xlarge%22,%22displayValue%22:%22BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.8xlarge%22,%22displayValue%22:%22BoxUsage:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.large%22,%22displayValue%22:%22BoxUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5a.8xlarge%22,%22displayValue%22:%22BoxUsage:m5a.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m6i.4xlarge%22,%22displayValue%22:%22BoxUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.2xlarge%22,%22displayValue%22:%22BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.large%22,%22displayValue%22:%22BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.medium%22,%22displayValue%22:%22BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.xlarge%22,%22displayValue%22:%22BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.2xlarge%22,%22displayValue%22:%22BoxUsage:t3a.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.large%22,%22displayValue%22:%22BoxUsage:t3a.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.medium%22,%22displayValue%22:%22BoxUsage:t3a.medium%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.xlarge%22,%22displayValue%22:%22BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:c5.large%22,%22displayValue%22:%22EU-BoxUsage:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5a.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5a.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5n.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5n.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.medium%22,%22displayValue%22:%22EU-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3a.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3a.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3a.xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.large%22,%22displayValue%22:%22EUC1-BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.medium%22,%22displayValue%22:%22EUC1-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.xlarge%22,%22displayValue%22:%22EUC1-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.12xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.12xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.large%22,%22displayValue%22:%22USE2-BoxUsage:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5n.18xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5n.18xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c7g.large%22,%22displayValue%22:%22USE2-BoxUsage:c7g.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:g4dn.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:g4dn.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:i3.large%22,%22displayValue%22:%22USE2-BoxUsage:i3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:i3.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:i3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.16xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.16xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.large%22,%22displayValue%22:%22USE2-BoxUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5a.large%22,%22displayValue%22:%22USE2-BoxUsage:m5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5a.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5ad.large%22,%22displayValue%22:%22USE2-BoxUsage:m5ad.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5ad.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5ad.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5d.large%22,%22displayValue%22:%22USE2-BoxUsage:m5d.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5n.large%22,%22displayValue%22:%22USE2-BoxUsage:m5n.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5zn.large%22,%22displayValue%22:%22USE2-BoxUsage:m5zn.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6a.large%22,%22displayValue%22:%22USE2-BoxUsage:m6a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6g.large%22,%22displayValue%22:%22USE2-BoxUsage:m6g.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6g.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6g.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.large%22,%22displayValue%22:%22USE2-BoxUsage:m6i.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6id.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.large%22,%22displayValue%22:%22USE2-BoxUsage:m6id.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6id.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6in.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6in.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5a.large%22,%22displayValue%22:%22USE2-BoxUsage:r5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5b.large%22,%22displayValue%22:%22USE2-BoxUsage:r5b.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.32xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.32xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t2.medium%22,%22displayValue%22:%22USE2-BoxUsage:t2.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.large%22,%22displayValue%22:%22USE2-BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.medium%22,%22displayValue%22:%22USE2-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.large%22,%22displayValue%22:%22USE2-BoxUsage:t3a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.medium%22,%22displayValue%22:%22USE2-BoxUsage:t3a.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t4g.large%22,%22displayValue%22:%22USE2-BoxUsage:t4g.large%20(Hrs)%22%7D,%7B%22value%22:%22USW1-BoxUsage:t3.medium%22,%22displayValue%22:%22USW1-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22USW2-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:m5.xlarge%22,%22displayValue%22:%22USW2-BoxUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:t3.medium%22,%22displayValue%22:%22USW2-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:t3.xlarge%22,%22displayValue%22:%22USW2-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22SpotUsage:t3.medium%22,%22displayValue%22:%22SpotUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22SpotUsage:t3.xlarge%22,%22displayValue%22:%22SpotUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:c5a.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:c5a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5.large%22,%22displayValue%22:%22USE2-SpotUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5a.large%22,%22displayValue%22:%22USE2-SpotUsage:m5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5ad.large%22,%22displayValue%22:%22USE2-SpotUsage:m5ad.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5dn.large%22,%22displayValue%22:%22USE2-SpotUsage:m5dn.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5n.large%22,%22displayValue%22:%22USE2-SpotUsage:m5n.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5zn.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m5zn.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6a.large%22,%22displayValue%22:%22USE2-SpotUsage:m6a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6a.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.2xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.4xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.large%22,%22displayValue%22:%22USE2-SpotUsage:m6i.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.4xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6id.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.large%22,%22displayValue%22:%22USE2-SpotUsage:m6id.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6id.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.2xlarge%22,%22displayValue%22:%22USE2-SpotUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.large%22,%22displayValue%22:%22USE2-SpotUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.medium%22,%22displayValue%22:%22USE2-SpotUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3a.large%22,%22displayValue%22:%22USE2-SpotUsage:t3a.large%20(Hrs)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22PurchaseType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;Spot vs On Demand&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22Amazon%20Elastic%20Compute%20Cloud%20-%20Compute%22,%22displayValue%22:%22EC2-Instances%20(Elastic%20Compute%20Cloud%20-%20Compute)%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageType%22,%22displayValue%22:%22Usage%20type%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22BoxUsage:c5.2xlarge%22,%22displayValue%22:%22BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.2xlarge%22,%22displayValue%22:%22BoxUsage:m5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.4xlarge%22,%22displayValue%22:%22BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.8xlarge%22,%22displayValue%22:%22BoxUsage:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5.large%22,%22displayValue%22:%22BoxUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m5a.8xlarge%22,%22displayValue%22:%22BoxUsage:m5a.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:m6i.4xlarge%22,%22displayValue%22:%22BoxUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.2xlarge%22,%22displayValue%22:%22BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.large%22,%22displayValue%22:%22BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.medium%22,%22displayValue%22:%22BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3.xlarge%22,%22displayValue%22:%22BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.2xlarge%22,%22displayValue%22:%22BoxUsage:t3a.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.large%22,%22displayValue%22:%22BoxUsage:t3a.large%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.medium%22,%22displayValue%22:%22BoxUsage:t3a.medium%20(Hrs)%22%7D,%7B%22value%22:%22BoxUsage:t3a.xlarge%22,%22displayValue%22:%22BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:c5.large%22,%22displayValue%22:%22EU-BoxUsage:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5a.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5a.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:m5n.4xlarge%22,%22displayValue%22:%22EU-BoxUsage:m5n.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.medium%22,%22displayValue%22:%22EU-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3.xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3a.2xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3a.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-BoxUsage:t3a.xlarge%22,%22displayValue%22:%22EU-BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.large%22,%22displayValue%22:%22EUC1-BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.medium%22,%22displayValue%22:%22EUC1-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22EUC1-BoxUsage:t3.xlarge%22,%22displayValue%22:%22EUC1-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.12xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.12xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.large%22,%22displayValue%22:%22USE2-BoxUsage:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c5n.18xlarge%22,%22displayValue%22:%22USE2-BoxUsage:c5n.18xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:c7g.large%22,%22displayValue%22:%22USE2-BoxUsage:c7g.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:g4dn.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:g4dn.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:i3.large%22,%22displayValue%22:%22USE2-BoxUsage:i3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:i3.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:i3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.16xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.16xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.large%22,%22displayValue%22:%22USE2-BoxUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5a.large%22,%22displayValue%22:%22USE2-BoxUsage:m5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5a.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5ad.large%22,%22displayValue%22:%22USE2-BoxUsage:m5ad.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5ad.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m5ad.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5d.large%22,%22displayValue%22:%22USE2-BoxUsage:m5d.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5n.large%22,%22displayValue%22:%22USE2-BoxUsage:m5n.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m5zn.large%22,%22displayValue%22:%22USE2-BoxUsage:m5zn.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6a.large%22,%22displayValue%22:%22USE2-BoxUsage:m6a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6g.large%22,%22displayValue%22:%22USE2-BoxUsage:m6g.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6g.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6g.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.large%22,%22displayValue%22:%22USE2-BoxUsage:m6i.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6i.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6i.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.4xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6id.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.large%22,%22displayValue%22:%22USE2-BoxUsage:m6id.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6id.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6id.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:m6in.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:m6in.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5a.large%22,%22displayValue%22:%22USE2-BoxUsage:r5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r5b.large%22,%22displayValue%22:%22USE2-BoxUsage:r5b.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.32xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.32xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:r6i.8xlarge%22,%22displayValue%22:%22USE2-BoxUsage:r6i.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t2.medium%22,%22displayValue%22:%22USE2-BoxUsage:t2.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.2xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.large%22,%22displayValue%22:%22USE2-BoxUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.medium%22,%22displayValue%22:%22USE2-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.large%22,%22displayValue%22:%22USE2-BoxUsage:t3a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.medium%22,%22displayValue%22:%22USE2-BoxUsage:t3a.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t3a.xlarge%22,%22displayValue%22:%22USE2-BoxUsage:t3a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-BoxUsage:t4g.large%22,%22displayValue%22:%22USE2-BoxUsage:t4g.large%20(Hrs)%22%7D,%7B%22value%22:%22USW1-BoxUsage:t3.medium%22,%22displayValue%22:%22USW1-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:c5.2xlarge%22,%22displayValue%22:%22USW2-BoxUsage:c5.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:m5.xlarge%22,%22displayValue%22:%22USW2-BoxUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:t3.medium%22,%22displayValue%22:%22USW2-BoxUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USW2-BoxUsage:t3.xlarge%22,%22displayValue%22:%22USW2-BoxUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22SpotUsage:t3.medium%22,%22displayValue%22:%22SpotUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22SpotUsage:t3.xlarge%22,%22displayValue%22:%22SpotUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:c5a.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:c5a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5.large%22,%22displayValue%22:%22USE2-SpotUsage:m5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5a.large%22,%22displayValue%22:%22USE2-SpotUsage:m5a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5ad.large%22,%22displayValue%22:%22USE2-SpotUsage:m5ad.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5dn.large%22,%22displayValue%22:%22USE2-SpotUsage:m5dn.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5n.large%22,%22displayValue%22:%22USE2-SpotUsage:m5n.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m5zn.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m5zn.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6a.large%22,%22displayValue%22:%22USE2-SpotUsage:m6a.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6a.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6a.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.2xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.4xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.large%22,%22displayValue%22:%22USE2-SpotUsage:m6i.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6i.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6i.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.4xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6id.4xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.large%22,%22displayValue%22:%22USE2-SpotUsage:m6id.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:m6id.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:m6id.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.2xlarge%22,%22displayValue%22:%22USE2-SpotUsage:t3.2xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.large%22,%22displayValue%22:%22USE2-SpotUsage:t3.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.medium%22,%22displayValue%22:%22USE2-SpotUsage:t3.medium%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3.xlarge%22,%22displayValue%22:%22USE2-SpotUsage:t3.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-SpotUsage:t3a.large%22,%22displayValue%22:%22USE2-SpotUsage:t3a.large%20(Hrs)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22InstanceType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;Instance types&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageType%22,%22displayValue%22:%22Usage%20type%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22CPUCredits:t3%22,%22displayValue%22:%22CPUCredits:t3%20(vCPU-Hours)%22%7D,%7B%22value%22:%22CPUCredits:t3a%22,%22displayValue%22:%22CPUCredits:t3a%20(vCPU-Hours)%22%7D,%7B%22value%22:%22EU-CPUCredits:t3%22,%22displayValue%22:%22EU-CPUCredits:t3%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USE2-CPUCredits:t3%22,%22displayValue%22:%22USE2-CPUCredits:t3%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USE2-CPUCredits:t3a%22,%22displayValue%22:%22USE2-CPUCredits:t3a%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USE2-CPUCredits:t4g%22,%22displayValue%22:%22USE2-CPUCredits:t4g%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USW1-CPUCredits:t3%22,%22displayValue%22:%22USW1-CPUCredits:t3%20(vCPU-Hours)%22%7D,%7B%22value%22:%22USW2-CPUCredits:t3%22,%22displayValue%22:%22USW2-CPUCredits:t3%20(vCPU-Hours)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;Extra burst credits&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageType%22,%22displayValue%22:%22Usage%20type%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EBS:VolumeUsage.gp2%22,%22displayValue%22:%22EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22EBS:VolumeUsage.gp3%22,%22displayValue%22:%22EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22EBSOptimized:m5a.8xlarge%22,%22displayValue%22:%22EBSOptimized:m5a.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22EU-EBS:SnapshotUsage%22,%22displayValue%22:%22EU-EBS:SnapshotUsage%20(GB-Month)%22%7D,%7B%22value%22:%22EU-EBS:VolumeUsage.gp2%22,%22displayValue%22:%22EU-EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22EU-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22EU-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22EUC1-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22EUC1-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22USE2-EBS:SnapshotUsage%22,%22displayValue%22:%22USE2-EBS:SnapshotUsage%20(GB-Month)%22%7D,%7B%22value%22:%22USE2-EBS:VolumeUsage.gp2%22,%22displayValue%22:%22USE2-EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22USE2-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22USE2-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:c5.12xlarge%22,%22displayValue%22:%22USE2-EBSOptimized:c5.12xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:c5.large%22,%22displayValue%22:%22USE2-EBSOptimized:c5.large%20(Hrs)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:c5.xlarge%22,%22displayValue%22:%22USE2-EBSOptimized:c5.xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:c5n.18xlarge%22,%22displayValue%22:%22USE2-EBSOptimized:c5n.18xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USE2-EBSOptimized:m5.8xlarge%22,%22displayValue%22:%22USE2-EBSOptimized:m5.8xlarge%20(Hrs)%22%7D,%7B%22value%22:%22USW1-EBS:VolumeUsage.gp2%22,%22displayValue%22:%22USW1-EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22USW1-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22USW1-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D,%7B%22value%22:%22USW2-EBS:VolumeUsage.gp2%22,%22displayValue%22:%22USW2-EBS:VolumeUsage.gp2%20(GB-Month)%22%7D,%7B%22value%22:%22USW2-EBS:VolumeUsage.gp3%22,%22displayValue%22:%22USW2-EBS:VolumeUsage.gp3%20(GB-Month)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;EBS disks/snapshots&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22Amazon%20Elastic%20Compute%20Cloud%20-%20Compute%22,%22displayValue%22:%22EC2-Instances%20(Elastic%20Compute%20Cloud%20-%20Compute)%22%7D,%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageTypeGroup%22,%22displayValue%22:%22Usage%20type%20group%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20CloudFront%20(Out)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20CloudFront%20(Out)%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Inter%20AZ%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Inter%20AZ%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Internet%20(In)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Internet%20(In)%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Internet%20(Out)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Internet%20(Out)%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Region%20to%20Region%20(In)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Region%20to%20Region%20(In)%22%7D,%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Region%20to%20Region%20(Out)%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Region%20to%20Region%20(Out)%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;EC2 traffic breakdown&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22UsageTypeGroup%22,%22displayValue%22:%22Usage%20type%20group%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2:%20Data%20Transfer%20-%20Inter%20AZ%22,%22displayValue%22:%22EC2:%20Data%20Transfer%20-%20Inter%20AZ%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;Interzone traffic&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageTypeGroup%22,%22displayValue%22:%22Usage%20type%20group%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2:%20NAT%20Gateway%20-%20Data%20Processed%22,%22displayValue%22:%22EC2:%20NAT%20Gateway%20-%20Data%20Processed%22%7D,%7B%22value%22:%22EC2:%20NAT%20Gateway%20-%20Running%20Hours%22,%22displayValue%22:%22EC2:%20NAT%20Gateway%20-%20Running%20Hours%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;NAT Gateway total cost&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://us-east-1.console.aws.amazon.com/cost-management/home?region=us-east-1#/cost-explorer?chartStyle=STACK&amp;amp;amp;costAggregate=unBlendedCost&amp;amp;amp;endDate=2022-12-31&amp;amp;amp;excludeForecasting=false&amp;amp;amp;filter=%5B%7B%22dimension%22:%7B%22id%22:%22Service%22,%22displayValue%22:%22Service%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2%20-%20Other%22,%22displayValue%22:%22EC2%20-%20Other%22%7D%5D%7D,%7B%22dimension%22:%7B%22id%22:%22UsageTypeGroup%22,%22displayValue%22:%22Usage%20type%20group%22%7D,%22operator%22:%22INCLUDES%22,%22values%22:%5B%7B%22value%22:%22EC2:%20NAT%20Gateway%20-%20Data%20Processed%22,%22displayValue%22:%22EC2:%20NAT%20Gateway%20-%20Data%20Processed%22%7D%5D%7D%5D&amp;amp;amp;futureRelativeRange=CUSTOM&amp;amp;amp;granularity=Monthly&amp;amp;amp;groupBy=%5B%22UsageType%22%5D&amp;amp;amp;historicalRelativeRange=LAST_6_MONTHS&amp;amp;amp;isDefault=true&amp;amp;amp;reportName=&amp;amp;amp;showOnlyUncategorized=false&amp;amp;amp;showOnlyUntagged=false&amp;amp;amp;startDate=2022-07-01&amp;amp;amp;usageAggregate=usageQuantity&amp;amp;amp;useNormalizedUnits=false"&gt;NAT Gateway data transfer&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/aws-cost-explorer-tips.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jan 6, 2023</summary>
    <category term="aws" label="aws"/>
    <category term="costs" label="costs"/>
    <category term="ec2" label="ec2"/>
    <published>2023-01-06T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/coiled-from-prefect.html</id>
    <title>Automated Data Pipelines On Dask With Coiled &amp; Prefect</title>
    <updated>2022-12-19T00:00:00+00:00</updated>
    <author>
      <name>Greg Hayes</name>
    </author>
    <content type="html">&lt;section id="automated-data-pipelines-on-dask-with-coiled-prefect"&gt;

&lt;section id="operationalizing-pure-python-data-engineering-workloads"&gt;
&lt;h2&gt;Operationalizing Pure Python Data Engineering Workloads&lt;/h2&gt;
&lt;p&gt;Dask is widely used among data scientists and engineers proficient in Python for interacting with big data, doing statistical analysis, and developing machine learning models. Operationalizing this work has traditionally required lengthy code rewrites, which makes moving from development and production hard. This gap slows business progress and increases risk for data science and data engineering projects in an enterprise setting. The need to remove this bottleneck has prompted the emergence of production deployment solutions that allow code written by data scientists and engineers to be directly deployed to production, unlocking the power of continuous deployment for pure Python data science and engineers.&lt;/p&gt;
&lt;p&gt;Here, we demonstrate the use of Coiled and Prefect to automate a common component of a data engineering pipeline. The motivating example is a file ingestion pipeline that a) runs on a scheduled basis, b) leverages distributed computing capability of Dask to process highly variable and potentially large volumes of data, and c) allows for reprocessing the data when a change in business logic or incoming data mandates a change.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-problem-to-solve"&gt;
&lt;h2&gt;The Problem To Solve&lt;/h2&gt;
&lt;figure class="align-default" id="prefect-problem"&gt;
&lt;img alt="https://docs.coiled.io/_images/coiled-prefect-problem-to-solve.png" src="https://docs.coiled.io/_images/coiled-prefect-problem-to-solve.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page"&gt;New York City Taxi Dataset&lt;/a&gt; is well-known in the big data community. This dataset is now being further enriched by including For-Hire Vehicle records (Uber, Lyft, etc). In May 2022, data stewards moved from a bi-annual to monthly update cadence, and switched to Parquet format. These changes open the door to using the data to experiment with modern data engineering and data science workloads.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/mrocklin/nyc-taxi/blob/main/notebook.ipynb"&gt;Experimentation with the data&lt;/a&gt; demonstrates that, while we can take advantage of the Parquet file format, row groups are nonuniform and at times very large, which makes working with the data cumbersome. Additionally, there are no guarantees about data quality or uniformity of datatypes. This creates unnecessary barriers and risk to using the data in a real-world business setting, where data quality is of paramount importance.&lt;/p&gt;
&lt;p&gt;Here, we take the first step in a common data engineering pipeline from a raw data source by automating the ingestion and initial cleaning of the data for downstream consumers. This preprocessing step assures the data is properly formatted for downstream users. We do this in a way that minimizes cost by waiting until we know there are files to process before creating a Dask cluster on Coiled, and automatically tearing down the cluster when the workload is complete.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="getting-started"&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;The following is an architecture diagram of the solution to build.&lt;/p&gt;
&lt;figure class="align-default" id="prefect-deployment-architecture"&gt;
&lt;img alt="https://docs.coiled.io/_images/coiled-prefect-deployment.png" src="https://docs.coiled.io/_images/coiled-prefect-deployment.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;We assume users have both &lt;a class="reference external" href="https://www.coiled.io/"&gt;Coiled&lt;/a&gt; and &lt;a class="reference external" href="https://www.prefect.io/"&gt;Prefect&lt;/a&gt; accounts, are using Prefect &amp;gt;=2.0, and have a private S3 bucket with credentials for landing the output our our data transformation pipeline. Clone the &lt;a class="reference external" href="https://github.com/coiled/coiled-from-prefect"&gt;coiled-from-prefect&lt;/a&gt; repository, create the Python environment, and collect the following information:&lt;/p&gt;
&lt;p&gt;&lt;u&gt;Coiled&lt;/u&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html#create-an-api-token"&gt;Coiled User and Team Account information, and a Coiled API token&lt;/a&gt; This allows creating Coiled clusters from Prefect.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;u&gt;S3&lt;/u&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;The NYC Taxi Dataset is in a public S3 bucket (labeled &lt;code&gt;Source&lt;/code&gt;) above)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An AWS Access Key Id and AWS Secret Access Token to write to your private S3 bucket (labeled &lt;code&gt;Sink&lt;/code&gt; above). The bucket must be configured for write access&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;u&gt;Prefect&lt;/u&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Create the following &lt;a class="reference external" href="https://docs.prefect.io/ui/blocks/"&gt;Prefect Blocks&lt;/a&gt; in your Prefect Workspace for storing secrets:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;AWS Credentials Block named &lt;code&gt;prefectexample0&lt;/code&gt;. Add the &lt;code&gt;Aws Access Key Id&lt;/code&gt; and &lt;code&gt;Aws Secret Access Key&lt;/code&gt; for your S3 sink here. Dask workers need these credentials to write the output of your data transformation pipeline&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;S3 Block named &lt;code&gt;prefect-s3-storage&lt;/code&gt;. This is where user code is stored for Prefect to run in automation. In our example, this is the same S3 bucket as our target, but we specify a Bucket Path of &lt;code&gt;bucket/prefect-s3-storage&lt;/code&gt;. This &lt;code&gt;Block&lt;/code&gt; also expects an &lt;code&gt;AWS Access Key ID&lt;/code&gt; and &lt;code&gt;AWS Secret Access Key&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Three separate Secret Blocks, named: &lt;code&gt;coiled-account&lt;/code&gt;, &lt;code&gt;coiled-user&lt;/code&gt;, and &lt;code&gt;coiled-token&lt;/code&gt;. These enable authentication to your Coiled account.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a &lt;code&gt;terminal&lt;/code&gt;, and from your active Python environment running &lt;code&gt;python flows/coiled_flow.py&lt;/code&gt; should trigger the execution of your Prefect flow with the following output:&lt;/p&gt;
&lt;figure class="align-default" id="local-test"&gt;
&lt;img alt="https://docs.coiled.io/_images/prefect-data-engineering-local-test.png" src="https://docs.coiled.io/_images/prefect-data-engineering-local-test.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Prefect creates a flow from the &lt;code&gt;check_for_files&lt;/code&gt; Python function, and generates a random name &lt;code&gt;tuscan-cormorant&lt;/code&gt;. The Flow is parameterized for testing purposes to only choose the first file from the list, and so returns the file &lt;code&gt;s3://nyc-tlc/trip data/fhvhf_tripdata_2019-02.parquet&lt;/code&gt;. Since we have a file that needs to be processed, this triggers execution of second Prefect Flow that dynamically uses Coiled to create a Dask and submits the file processing job for execution.&lt;/p&gt;
&lt;p&gt;Insepcting the logs shows a few items that are noteworthy:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;The cluster comes up quickly; the Dask dashboard is available about 90-seconds after being requested.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/package_sync.html"&gt;Package Sync&lt;/a&gt; makes it easy to replicate our local software environment on the remote cluster&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="create-a-prefect-deployment-work-queue-and-agent"&gt;
&lt;h2&gt;Create a Prefect Deployment, Work Queue, and Agent&lt;/h2&gt;
&lt;p&gt;In the terminal, run:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;prefect&lt;/span&gt; &lt;span class="n"&gt;deployment&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="n"&gt;flows&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;coiled_flow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;check_for_files&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;check_for_files&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;prefect&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This will create &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;check_for_files-deployment.yaml&lt;/span&gt;&lt;/code&gt; (example below), provision a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Prefect&lt;/span&gt; &lt;span class="pre"&gt;Work&lt;/span&gt; &lt;span class="pre"&gt;Queue&lt;/span&gt;&lt;/code&gt; and push all the code needed for remote execution to the S3 bucket defined in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Prefect&lt;/span&gt; &lt;span class="pre"&gt;S3&lt;/span&gt; &lt;span class="pre"&gt;Block&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;###
### A complete description of a Prefect Deployment for flow &amp;#39;Check for files&amp;#39;
###
name: check_for_files
description: |-
  We&amp;#39;re going to run this on a weekly schedule.  Our intention is, as a default behavior,
  to find files in the S3 bucket that match `fhvhv_tripdata_*.parquet` and evaluate
  their modification timestamp.  We only want to process files that were
  modified in the last seven days.

  We also have the option to either: a) `reprocess` the entire dataset, which will delete
  the existing data and rewrite it, or b) `test_subset`, which will process only the first file
  in the list.  This is for testing only.

  Parameters
  ----------

  intent: str: one of `test_subset`, `reprocess`, or &amp;quot;&amp;quot;
version: ae6b4b09dd5bdfeb4e0de153ef60b7c6
# The work queue that will handle this deployment&amp;#39;s runs
work_queue_name: test
tags: []
parameters:
  intent: &amp;quot;&amp;quot;  &amp;lt;--- Verify this line is populated
schedule: null
infra_overrides: {}

###
### DO NOT EDIT BELOW THIS LINE
###
flow_name: Check for files
manifest_path: null
infrastructure:
  type: process
  env: {}
  labels: {}
  name: null
  command: null
  stream_output: true
  working_dir: null
  _block_document_id: &amp;lt;XXX&amp;gt;
  _block_document_name: &amp;lt;YYY&amp;gt;
  _is_anonymous: true
  block_type_slug: process
  _block_type_slug: process
storage:
  bucket_path: &amp;lt;S3 Bucket&amp;gt;/prefect-s3-storage
  aws_access_key_id: &amp;#39;**********&amp;#39;
  aws_secret_access_key: &amp;#39;**********&amp;#39;
  _block_document_id: &amp;lt;ZZZ&amp;gt;
  _block_document_name: prefect-s3-storage
  _is_anonymous: false
  block_type_slug: s3
  _block_type_slug: s3
path: &amp;#39;&amp;#39;
entrypoint: flows/coiled_flow.py:check_for_files
parameter_openapi_schema:
  title: Parameters
  type: object
  properties:
    intent:
      title: intent
      type: string
  required:
  - intent
  definitions: null

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s verify that &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;parameters:&lt;/span&gt; &lt;span class="pre"&gt;{&amp;quot;intent&amp;quot;:&lt;/span&gt; &lt;span class="pre"&gt;&amp;quot;&amp;quot;}&lt;/span&gt;&lt;/code&gt;, which sets the default behavior for our Prefect Flow to incrementally process data. If necessary, update this setting, then push the changes to our Deployment with:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;prefect&lt;/span&gt; &lt;span class="n"&gt;deployment&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;check_for_files&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;deployment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And then start a Prefect Agent on our local machine, which watches the Prefect Work Queue.&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;prefect&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;test&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now we can see the creation of both Deployment, named &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;Check&lt;/span&gt; &lt;span class="pre"&gt;for&lt;/span&gt; &lt;span class="pre"&gt;files/my_run&lt;/span&gt;&lt;/code&gt; and the Work Queue and in our Prefect workspace.&lt;/p&gt;
&lt;figure class="align-default" id="prefect-deployment"&gt;
&lt;img alt="https://docs.coiled.io/_images/prefect-deployment.png" src="https://docs.coiled.io/_images/prefect-deployment.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;figure class="align-default" id="prefect-work-queue"&gt;
&lt;img alt="https://docs.coiled.io/_images/prefect-work-queue.png" src="https://docs.coiled.io/_images/prefect-work-queue.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Our first test of the pipeline is to create a custom run named &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;test_subset&lt;/span&gt;&lt;/code&gt;. This validates our pipeline writes data to the target S3 bucket.&lt;/p&gt;
&lt;figure class="align-default" id="prefect-custom-run"&gt;
&lt;img alt="https://docs.coiled.io/_images/prefect-custom-run.png" src="https://docs.coiled.io/_images/prefect-custom-run.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;figure class="align-default" id="prefect-test-run"&gt;
&lt;img alt="https://docs.coiled.io/_images/prefect-test-run.png" src="https://docs.coiled.io/_images/prefect-test-run.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Now, we run the pipeline against the full dataset, and confirm new data lands as expected.&lt;/p&gt;
&lt;figure class="align-default" id="processed-files"&gt;
&lt;img alt="https://docs.coiled.io/_images/processed-files.png" src="https://docs.coiled.io/_images/processed-files.png" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;p&gt;There we have it. An automated, pure Python, big data engineering pipeline running Dask on Coiled and Prefect. From here, users should follow Prefect guidelines for operationalizing their &lt;a class="reference external" href="https://docs.prefect.io/concepts/work-queues/?h=agent"&gt;Agent&lt;/a&gt; and &lt;a class="reference external" href="https://docs.prefect.io/concepts/schedules/?h=schedul"&gt;scheduling execution of their deployment&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled-from-prefect.html"/>
    <summary>Dask is widely used among data scientists and engineers proficient in Python for interacting with big data, doing statistical analysis, and developing machine learning models. Operationalizing this work has traditionally required lengthy code rewrites, which makes moving from development and production hard. This gap slows business progress and increases risk for data science and data engineering projects in an enterprise setting. The need to remove this bottleneck has prompted the emergence of production deployment solutions that allow code written by data scientists and engineers to be directly deployed to production, unlocking the power of continuous deployment for pure Python data science and engineers.</summary>
    <category term="prefect" label="prefect"/>
    <category term="workflowautomation" label="workflow automation"/>
    <published>2022-12-19T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-read-csv-to-dataframe.html</id>
    <title>Reading CSV files into Dask DataFrames with read_csv</title>
    <updated>2022-02-09T00:00:00+00:00</updated>
    <author>
      <name>Matthew Powers</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Feb 9, 2022&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;6m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="reading-csv-files-into-dask-dataframes-with-read-csv"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Matthew Powers&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here’s how this post is organized:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#dask-read-csv-single-small-file"&gt;Reading a single small CSV file&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#dask-read-csv-single-large-file"&gt;Reading a large CSV file&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#dask-read-csv-multiple-files"&gt;Reading multiple CSV files&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#dask-read-csv-read-remote-files"&gt;Reading files from remote data stores like S3&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#limitations-of-csv-file-format"&gt;Limitations of CSV files&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lots of datasets are stored with the CSV file format, so it’s important to understand the Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt; API in detail.&lt;/p&gt;
&lt;section id="dask-read-csv-single-small-file"&gt;
&lt;span id="id1"&gt;&lt;/span&gt;&lt;h2&gt;Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;: single small file&lt;/h2&gt;
&lt;p&gt;Dask makes it easy to read a small file into a Dask DataFrame. Suppose you have a dogs.csv file with the following contents:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;
&lt;span class="n"&gt;fido&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;lucky&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;gus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here’s how to read the CSV file into a Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dogs.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can inspect the content of the Dask DataFrame with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;compute()&lt;/span&gt;&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;  first_name  age&lt;/span&gt;
&lt;span class="go"&gt;0       fido    2&lt;/span&gt;
&lt;span class="go"&gt;1      lucky    8&lt;/span&gt;
&lt;span class="go"&gt;2     copper    3&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is quite similar to the syntax for reading CSV files into pandas DataFrames.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dogs.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The Dask DataFrame API was intentionally designed to look and feel just like the pandas API.&lt;/p&gt;
&lt;p&gt;For a single small file, Dask may be overkill and you can probably just use pandas. Dask starts to gain a competitive advantage when dealing with large CSV files. Rule-of-thumb for working with pandas is to have at least 5x the size of your dataset as available RAM. Use Dask whenever you exceed this limit. For example, when working on a 16 GB RAM machine, consider switching over to Dask when your dataset exceeds 3 GB in size.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-csv-single-large-file"&gt;
&lt;span id="id2"&gt;&lt;/span&gt;&lt;h2&gt;Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;: single large file&lt;/h2&gt;
&lt;p&gt;Dask DataFrames are composed of multiple partitions, each of which is a pandas DataFrame. Dask intentionally splits up the data into multiple pandas DataFrames so operations can be performed on multiple slices of the data in parallel.&lt;/p&gt;
&lt;p&gt;Let’s read in a 5.19 GB file (5,190 MB) into a Dask DataFrame. This file is hosted in a public S3 bucket at &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-datasets/h2o/G1_1e8_1e2_0_0/csv/G1_1e8_1e2_0_0.csv&lt;/span&gt;&lt;/code&gt; if you’d like to download it yourself.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/G1_1e8_1e2_0_0.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We can run &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;ddf.npartitions&lt;/span&gt;&lt;/code&gt; to see how many partitions the data is divided into.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;
&lt;span class="go"&gt;81&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can customize the number of partitions that the DataFrame will contain by setting the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; parameter when invoking &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;section id="dask-read-csv-blocksize"&gt;
&lt;h3&gt;Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;: blocksize&lt;/h3&gt;
&lt;p&gt;The number of partitions depends on the value of the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; argument. If you don’t supply a value to the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; keyword, it is set to “default” and the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; is computed based on the available memory and number of cores on the machine, up to a maximum of 64 MB. In the example above, Dask automatically splits up the 5,190 MB data file into ~64 MB chunks when run on a MacBook Air with 8 GB of RAM and 4 cores.&lt;/p&gt;
&lt;p&gt;We can also manually set the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; parameter when reading CSV files to make the partitions larger or smaller.Let’s read this data file with a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; of 16 MB.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/G1_1e8_1e2_0_0.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blocksize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;16MB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;
&lt;span class="go"&gt;324&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The Dask DataFrame consists of 325 partitions when the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; is 16 MB. The number of partitions goes up when the blocksize decreases. Let’s read in this data file with a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; of 128 MB.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/G1_1e8_1e2_0_0.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blocksize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;128MB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;
&lt;span class="go"&gt;40&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The Dask DataFrame has 40 partitions when the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; is set to 128 MB. There are fewer partitions when the blocksize increases.&lt;/p&gt;
&lt;p&gt;Let’s take a look at how Dask infers the data types of each column when a CSV file is read. The rule of thumb when working with Dask DataFrames is to keep your partitions under 100 MB in size.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-csv-inferring-dtypes"&gt;
&lt;h3&gt;Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;: inferring dtypes&lt;/h3&gt;
&lt;p&gt;CSV is a text-based file format and does not contain metadata information about the data types or columns. When reading a CSV file, Dask needs to infer the column data types if they’re not explicitly set by the user.&lt;/p&gt;
&lt;p&gt;Let’s look at the data types that Dask has inferred for our DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt;
&lt;span class="go"&gt;id1    string[pyarrow]&lt;/span&gt;
&lt;span class="go"&gt;id2    string[pyarrow]&lt;/span&gt;
&lt;span class="go"&gt;id3    string[pyarrow]&lt;/span&gt;
&lt;span class="go"&gt;id4              int64&lt;/span&gt;
&lt;span class="go"&gt;id5              int64&lt;/span&gt;
&lt;span class="go"&gt;id6              int64&lt;/span&gt;
&lt;span class="go"&gt;v1               int64&lt;/span&gt;
&lt;span class="go"&gt;v2               int64&lt;/span&gt;
&lt;span class="go"&gt;v3             float64&lt;/span&gt;
&lt;span class="go"&gt;dtype: object&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask automatically uses PyArrow strings, which consume up to 70% less memory versus NumPy objects and can therefore provide a huge performance improvement (more details in &lt;a class="reference internal" href="blog/pyarrow-in-pandas-and-dask.html"&gt;&lt;span class="doc"&gt;Utilizing PyArrow to improve pandas and Dask workflows&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Dask infers the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype&lt;/span&gt;&lt;/code&gt; based on a sample of the data. It doesn’t look at every row in the dataset to infer the data types because that would be prohibitively slow for large datasets.&lt;/p&gt;
&lt;p&gt;You can increase the number of rows that are sampled by setting the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;sample&lt;/span&gt;&lt;/code&gt; parameter, though for this dataset, this does not change the inferred data types.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/G1_1e8_1e2_0_0.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="dask-read-csv-manually-specifying-dtype"&gt;
&lt;h3&gt;Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;: manually specifying &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype&lt;/span&gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Inferring data types based on a sample of the rows can be error-prone if the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype&lt;/span&gt;&lt;/code&gt; is not the same for all column values. A common example of this is when a column contains &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;NaN&lt;/span&gt;&lt;/code&gt; values. You can avoid &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype&lt;/span&gt;&lt;/code&gt; inference by explicitly specifying the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype&lt;/span&gt;&lt;/code&gt; when reading CSV files.&lt;/p&gt;
&lt;p&gt;Let’s manually set the id4, id5, and id6 columns to a Python &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;float&lt;/span&gt;&lt;/code&gt;, which would avoid any errors on computing our results if these columns contained &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;NaN&lt;/span&gt;&lt;/code&gt; values.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="go"&gt;    &amp;quot;data/G1_1e8_1e2_0_0.csv&amp;quot;,&lt;/span&gt;
&lt;span class="go"&gt;    dtype={&lt;/span&gt;
&lt;span class="go"&gt;        &amp;quot;id4&amp;quot;: float,&lt;/span&gt;
&lt;span class="go"&gt;        &amp;quot;id5&amp;quot;: float,&lt;/span&gt;
&lt;span class="go"&gt;        &amp;quot;id6&amp;quot;: float,&lt;/span&gt;
&lt;span class="go"&gt;    },&lt;/span&gt;
&lt;span class="go"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt;
&lt;span class="go"&gt;id1    string[pyarrow]&lt;/span&gt;
&lt;span class="go"&gt;id2    string[pyarrow]&lt;/span&gt;
&lt;span class="go"&gt;id3    string[pyarrow]&lt;/span&gt;
&lt;span class="go"&gt;id4            float64&lt;/span&gt;
&lt;span class="go"&gt;id5            float64&lt;/span&gt;
&lt;span class="go"&gt;id6            float64&lt;/span&gt;
&lt;span class="go"&gt;v1               int64&lt;/span&gt;
&lt;span class="go"&gt;v2               int64&lt;/span&gt;
&lt;span class="go"&gt;v3             float64&lt;/span&gt;
&lt;span class="go"&gt;dtype: object&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask will infer the data types for the columns that you don’t manually specify. If you specify the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype&lt;/span&gt;&lt;/code&gt; for all the columns, then Dask won’t do any data type inference.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="dask-read-csv-multiple-files"&gt;
&lt;span id="id3"&gt;&lt;/span&gt;&lt;h2&gt;Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;: multiple files&lt;/h2&gt;
&lt;p&gt;Dask can read data from a single file, but it’s even faster for Dask to read multiple files in parallel.&lt;/p&gt;
&lt;p&gt;Let’s write out the large 5.19 GB CSV file from earlier examples as multiple CSV files so we can see how to read multiple CSV files into a Dask DataFrame. Start by writing out the single CSV file as multiple CSV files.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/G1_1e8_1e2_0_0.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;
&lt;span class="go"&gt;81&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/csvs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;[&amp;#39;data/csvs/00.part&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt; &amp;#39;data/csvs/01.part&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt; &amp;#39;data/csvs/02.part&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt; ...&lt;/span&gt;
&lt;span class="go"&gt; &amp;#39;data/csvs/79.part&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt; &amp;#39;data/csvs/80.part&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This will write out 81 files, one for each partition. Let’s read these 81 CSV files into a Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/csvs/*.part&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Reading multiple files into a pandas DataFrame must be done sequentially and requires more code. Here’s the pandas syntax:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;glob&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;all_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./data/csvs/*.part&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_files&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Parallel I/O is a huge strength of Dask compared to pandas. pandas is designed for read / write operations with single files. pandas does not scale well with multiple files. Dask is designed to perform I/O in parallel and is more performant than pandas for operations with multiple files (&lt;a class="reference internal" href="examples/dataframes.html"&gt;&lt;span class="doc"&gt;learn more&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The same &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;blocksize&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;dtype&lt;/span&gt;&lt;/code&gt; arguments we discussed earlier for reading a single file also apply when reading multiple files.&lt;/p&gt;
&lt;p&gt;Dask readers also make it easy to read data that’s stored in remote object data stores, like AWS S3.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-csv-analyzing-remote-files-with-localhost-compute"&gt;
&lt;span id="dask-read-csv-read-remote-files"&gt;&lt;/span&gt;&lt;h2&gt;Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;: analyzing remote files with localhost compute&lt;/h2&gt;
&lt;p&gt;You can easily read a CSV file that’s stored in S3 to your local machine. Here’s how to read a public S3 file.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/csv/0000.part&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You normally should not analyze remote data on your localhost machine because it’s slow to download the data locally. It’s more natural to process cloud data with cloud compute power. Files stored in AWS S3, for example, should be processed with AWS EC2 cloud provisioned compute instances.&lt;/p&gt;
&lt;p&gt;Let’s look at a S3 folder with a lot of data. The &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-datasets/timeseries/20-years/csv/&lt;/span&gt;&lt;/code&gt; S3 bucket has 1,095 files and requires 100 GB of memory when loaded into a DataFrame. Let’s try to run a computation on the entire dataset and see if it works.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/csv/*.part&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I let this computation run for 30 minutes before canceling the query. Running this locally is way too slow.&lt;/p&gt;
&lt;p&gt;Let’s see how to read in this large dataset of CSVs to a Dask cluster that contains multiple compute nodes, so we can execute this query faster.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-csv-read-remote-files-in-cluster-environment"&gt;
&lt;h2&gt;Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_csv&lt;/span&gt;&lt;/code&gt;: read remote files in cluster environment&lt;/h2&gt;
&lt;p&gt;Let’s spin up a 5 node cluster with Coiled and try to run the same computation with more computing power.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt; &lt;span class="c1"&gt;# same region as data, faster + cheaper&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/csv/*.part&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This computation runs in just under 2 minutes and costs ~$0.08 in AWS costs. Running this computation on a cluster is certainly faster than running on localhost. It would have taken a very long time for the query to finish on localhost.&lt;/p&gt;
&lt;p&gt;Storing the data in a different file format could make the query run even faster.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="limitations-of-csv-file-format"&gt;
&lt;span id="id4"&gt;&lt;/span&gt;&lt;h2&gt;Limitations of CSV file format&lt;/h2&gt;
&lt;p&gt;CSV files are commonly used because they’re human readable and support columns with messy data types (like a column with both string and integer values, for example). However, they are usually not the best file format for a data analysis.&lt;/p&gt;
&lt;p&gt;Here are the five reasons:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Parquet files don’t require schema inference / manual schema specification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Parquet files are easier to compress&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Columnar nature of Parquet files allows for column pruning, which often yields big query performance gains&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Row group metadata in Parquet files allows for predicate pushdown filtering&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Parquet files are immutable&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See the &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe-best-practices.html#use-parquet"&gt;Dask docs for more details on the advantages of Parquet files&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="learn-more"&gt;
&lt;h2&gt;Learn more&lt;/h2&gt;
&lt;p&gt;In this post we went through how to work with CSV files using Dask. For more realistic use cases you might consider:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/dataframes.html"&gt;&lt;span class="doc"&gt;DataFrame Workloads with Coiled&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/datashader.html"&gt;&lt;span class="doc"&gt;Visualize 1,000,000,000 Points&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/scaling-xgboost-cloud-coiled.html"&gt;&lt;span class="doc"&gt;XGBoost model training with Dask DataFrame&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-read-csv-to-dataframe.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Feb 9, 2022</summary>
    <published>2022-02-09T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/writing-parquet-files-with-dask-using-to-parquet.html</id>
    <title>Writing Parquet Files with Dask using to_parquet</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="writing-parquet-files-with-dask-using-to-parquet"&gt;

&lt;p&gt;This blog post explains how to write Parquet files with Dask using the to_parquet method.&lt;/p&gt;
&lt;p&gt;The Parquet file format allows users to enjoy several performance optimizations when reading data, benefiting downstream users of the data.&lt;/p&gt;
&lt;p&gt;This blog post will teach you the basics of writing Parquet files and advanced options like customizing the filenames and controlling if the metadata file gets written.  You need to be careful to avoid writing the metadata file when it can become a performance bottleneck.&lt;/p&gt;
&lt;p&gt;Let’s dive in with a simple example.&lt;/p&gt;
&lt;section id="dask-write-parquet-small-example"&gt;
&lt;h2&gt;Dask write Parquet: Small example&lt;/h2&gt;
&lt;p&gt;Let’s create a small Dask DataFrame and then write it out to disk with to_parquet.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;letters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/something&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here are the files that are output to disk.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;something&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="n"&gt;_common_metadata&lt;/span&gt;
  &lt;span class="n"&gt;_metadata&lt;/span&gt;
  &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
  &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask DataFrame’s file I/O writer methods output one file per partition.  This DataFrame has two partitions, so two Parquet files are written.  The Parquet writer also outputs _common_metadata and _metadata files by default.&lt;/p&gt;
&lt;p&gt;Parquet files contain metadata about the file contents including the schema of the data, the column names, the data types, and min/max values for every column in each row group.  This metadata is part of what allows for Parquet files to be read in a more performant manner compared to file formats like CSV.&lt;/p&gt;
&lt;p&gt;Dask writes out the metadata in the Parquet file footers to a single file to bypass the need to fetch the metadata from each Parquet file that’s being read.  Footers are a section of Parquet files where metadata is stored.  It can just look at the metadata file to get the metadata for each file in the directory.  Creating the metadata file is great for small datasets, but can become a performance bottleneck when lots of files are written.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-to-parquet-write-metadata-file"&gt;
&lt;h2&gt;Dask to_parquet: write_metadata_file&lt;/h2&gt;
&lt;p&gt;The write_metadata_file argument is set to True by default.  For large writes, it’s good to set write_metadata_file to False, so collecting all the Parquet metadata in a single file is not a performance bottleneck.&lt;/p&gt;
&lt;p&gt;Here’s how to write out the same data as before, but without a _metadata file.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;data/something2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;write_metadata_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here are the files that are output to disk.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;something2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
  &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When Dask reads a folder with Parquet files that do not have a _metadata file, then Dask needs to read the file footers of all the individual Parquet files to gather the statistics.  This is slower than reading the data directly from a _metadata file.  But, as mentioned previously, trying to write too much data to a single _metadata file can be prohibitively slow when lots of files are written.&lt;/p&gt;
&lt;p&gt;In short, set write_metadata_file to False when doing large Parquet writes.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-to-parquet-name-function"&gt;
&lt;h2&gt;Dask to_parquet: name_function&lt;/h2&gt;
&lt;p&gt;The Parquet writes will output files with names like part.0.parquet and part.1.parquet by default.  You can set the name_function parameter to customize the filenames that are written with to_parquet.&lt;/p&gt;
&lt;p&gt;Let’s output a universally unique identifier (UUID) in the filename that’s output so we can see what files are written per each batch.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;uuid&lt;/span&gt;

&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;batch_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;part-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.parquet&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;data/something3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;write_metadata_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name_function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;batch_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here are the files that are output to disk.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;something3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="n"&gt;a19442&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;309e-485&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b006&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;fd9cb10f9cc7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="n"&gt;a19442&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;309e-485&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b006&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;fd9cb10f9cc7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It’s really nice to write files like this in your production applications.  This lets you easily identify the files that are written, per batch.&lt;/p&gt;
&lt;p&gt;You can also use the name_function to write out files with a timestamp, so they’re ordered chronologically.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-to-parquet-compression"&gt;
&lt;h2&gt;Dask to_parquet: compression&lt;/h2&gt;
&lt;p&gt;Dask writes out Parquet files with Snappy compression by default.  Snappy compression is typically the best for files used in distributed compute contexts.  Snappy doesn’t usually compress files as much as other compression algorithms like gzip, but it’s faster when decompressing files (aka inflating files).  You’ll often read compressed files from disk in big data systems, so fast inflation is important.&lt;/p&gt;
&lt;p&gt;You can specify the Snappy compression algorithm, but this doesn’t change anything because Snappy is used by default.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;data/something4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;write_metadata_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;snappy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can write out the files with the compression algorithm in the filename, which can be convenient.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;with_snappy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;part-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.snappy.parquet&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;data/something5&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;write_metadata_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;snappy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name_function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;with_snappy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here are the files that are output to disk.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;something5&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.&lt;/span&gt;&lt;span class="n"&gt;snappy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
  &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.&lt;/span&gt;&lt;span class="n"&gt;snappy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is a nice way to make it clear to other humans what compression algorithm is being used by the file. Note that you will need to manually update the name_function parameter to reflect any changes made to the compression parameter.&lt;/p&gt;
&lt;p&gt;Different columns of a Parquet file can actually be compressed with different compression algorithms.  Here’s how to compress the nums column with Snappy and the letters column with gzip.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;data/something6&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;write_metadata_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;snappy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;letters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You don’t normally see different compression algorithms for different columns in a Parquet file, but it’s a cool feature that’s incredibly handy for niche use cases.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-write-parquet-fastparquet-vs-pyarrow-engines"&gt;
&lt;h2&gt;Dask write parquet: FastParquet vs PyArrow Engines&lt;/h2&gt;
&lt;p&gt;You can write Parquet files with both the FastParquet and PyArrow engines.  For most use cases, either selection is fine.  There are some subtle differences between the two engines, but they don’t matter for most users.&lt;/p&gt;
&lt;p&gt;PyArrow’s popularity has taken off and it’s well supported in Dask, so PyArrow is a good option if you’re not sure which engine to use.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-write-parquet-partition-on"&gt;
&lt;h2&gt;Dask write parquet: partition_on&lt;/h2&gt;
&lt;p&gt;You can write Parquet files in a Hive-compliant directory structure that allows for readers to leverage disk partition filtering by setting the partition_on argument.&lt;/p&gt;
&lt;p&gt;Disk partition filtering can be a significant performance optimization for certain types of queries, but it will make other types of queries run slower.  For example, queries that filter or group-by on the partition key will run a lot faster than queries operating on different keys.  Queries that don’t operate on the partition key will face the drag of extra files and globbing nested directories.  Globbing is particularly expensive in cloud based object stores like AWS S3.&lt;/p&gt;
&lt;p&gt;See the post on &lt;a class="reference external" href="https://coiled.io/blog/dask-disk-partition-on/"&gt;Creating Dask Partitioned Lakes with Dask using partition_on&lt;/a&gt; for more information.  Disk partitioning allows for huge performance gains for some query patterns, so you should know how it works.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-to-parquet-conclusion"&gt;
&lt;h2&gt;Dask to_parquet Conclusion&lt;/h2&gt;
&lt;p&gt;Dask makes it easy to write Parquet files and provides several options allowing you to customize the operation.&lt;/p&gt;
&lt;p&gt;You can specify the compression algorithm, choose if the metadata file should be written, customize the filename, and write the files in a nested directory structure for disk partition filtering.&lt;/p&gt;
&lt;p&gt;Consider omitting the metadata file when you must write a lot of partitions because it can be a performance bottleneck.  Including a UUID and the compression algorithm in the filename is nice when you’re building production pipelines that repeatedly write to the same directory.&lt;/p&gt;
&lt;p&gt;You can always repartition before writing to change the number of files that are written.  See &lt;a class="reference external" href="https://coiled.io/blog/repartition-dataframe/"&gt;this blog post&lt;/a&gt; for more information about the repartition method.&lt;/p&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://coiled.io/blog/parquet-file-column-pruning-predicate-pushdown/"&gt;this blog post&lt;/a&gt; to learn more about the performance benefits you can get from column pruning and predicate pushdown filtering, two optimization techniques that are afforded by Parquet files but not available in other file formats like CSV or JSON.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/writing-parquet-files-with-dask-using-to-parquet.html"/>
    <summary>This blog post explains how to write Parquet files with Dask using the to_parquet method.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/why-we-passed-on-kubernetes.html</id>
    <title>Why we passed on Kubernetes</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="why-we-passed-on-kubernetes"&gt;

&lt;p&gt;Kubernetes is great if you need to organize many always-on services and have in-house expertise, but can add an extra burden and abstraction when deploying a single bursty service like Dask, especially in a user environment with quickly changing needs.&lt;/p&gt;
&lt;p&gt;At Coiled we chose raw cloud APIs for flexibility and simplicity, and we encourage this choice for others in similar situations. This article explains this recommendation.&lt;/p&gt;
&lt;section id="motivation"&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Coiled deploys Dask clusters in the cloud. We do this using raw cloud APIs rather than with Kubernetes. When new Dask users embark on resolving the deployment question they are often curious about using raw Cloud SaaS (like AWS EC2) vs Kubernetes. This article goes into some of the pros and cons behind this question.&lt;/p&gt;
&lt;p&gt;For context, I’ve deployed Dask on many different Kubernetes systems. Kubernetes is fantastic and I often recommend it, but I’ve run into enough downstream pain that it’s no longer my default recommendation.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="deploying-dask-on-kubernetes-is-great"&gt;
&lt;h2&gt;Deploying Dask on Kubernetes is Great&lt;/h2&gt;
&lt;p&gt;Kubernetes is great to deploy Dask for lots of different reasons:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Great Dask deployment technologies exist including dask-kubernetes, dask-gateway, the helm chart, and QHub.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes is the de-facto standard&lt;/strong&gt; deployment technology today. You’re buying into a system that has strong buy-in and universal support from many different technologies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes is cloud agnostic&lt;/strong&gt;, and so it’s theoretically easy to shift your deployment to a different cloud vendor and avoid lock-in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes is battle tested&lt;/strong&gt; and so you know that it’ll just work, or that any problems will be your fault.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes allows you to share warm nodes&lt;/strong&gt; across services, allowing for faster uptime when you have a large userbase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes can provide a single system for many services&lt;/strong&gt;, allowing you to amortize some of your organizational cost.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In general we find that when a large organization goes all-in on Kubernetes it can provide a lot of benefits.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-kubernetes-is-a-pain-in-the-butt"&gt;
&lt;h2&gt;Dask Kubernetes is a Pain in the Butt&lt;/h2&gt;
&lt;p&gt;My experience is that setting up Dask clusters on Kubernetes initially is delightful, but that over time the Kubernetes abstraction layer provides more pain than benefit, at least when all you’re trying to do is deploy Dask. Let’s dig into why.&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/-VzaC5-Eh38?start=731" title="Matthew Rocklin - Dask in Production | SciPy 2024" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="excess-abstraction"&gt;
&lt;h2&gt;Excess Abstraction&lt;/h2&gt;
&lt;p&gt;Most of the upcoming issues arise because Kubernetes tries to abstract away a system (the cloud) that you don’t actually want to abstract away. Eventually you find yourself modeling much of the cloud VM / Networking stack in Kubernetes, which gets pretty painful.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="instance-types"&gt;
&lt;h2&gt;Instance Types&lt;/h2&gt;
&lt;p&gt;As an example, when you set up a Kubernetes cluster you’ll back it with a node pool for which you will select a standard instance type, say, m5.4xlarge. This instance type determines:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;The maximum size of a pod&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The general ratio between CPU cores and memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Local storage / disk technology used (local SSD or not)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Whether or not you have GPUs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Advanced networking that may exist&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Early on, this will be fine. You don’t really care about your instance type. Dask abstracts that away for you… kind of.&lt;/p&gt;
&lt;p&gt;Invariably you will have a sharp user who wants to use &lt;a class="reference external" href="https://docs.coiled.io/user_guide/gpu-job.html"&gt;GPUs&lt;/a&gt;, or SSDs, or large-memory workers for cost savings, or spot instances, or &lt;a class="reference external" href="https://docs.coiled.io/blog/dask-graviton.html"&gt;ARM instances&lt;/a&gt;, or other aspects.&lt;/p&gt;
&lt;p&gt;That’s ok though! You can still use Kubernetes, you’ll just set up a suite of different node pools, and use various Kubernetes labeling schemes to select between them based on the worker type requested. You can absolutely model out the complexity of cloud instance types in Kubernetes; it’s just work.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="scale-up-scale-down"&gt;
&lt;h2&gt;Scale Up / Scale Down&lt;/h2&gt;
&lt;p&gt;Dask workloads are bursty. Often you want to scale from zero to 100 machines as soon as possible, use them for twenty minutes, and then scale back down to zero. With cloud APIs this is easy. You ask for 100 instances, use them for a while, and then destroy them.&lt;/p&gt;
&lt;p&gt;With Dask Kubernetes there is some interplay between Kubernetes and the underlying auto-scaling node pools. These systems are often defined by the underlying cloud infrastructure, and have various policies like “If there are excess nodes, wait a minute and then remove one”. These policies make sense for the slowly varying services for which Kubernetes was designed, but can be catastrophically costly for bursty computational services like Dask.&lt;/p&gt;
&lt;p&gt;Again, we can reconfigure this, but we find ourselves un-engineering a system that didn’t need engineering in the first place.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="always-on"&gt;
&lt;h2&gt;Always-on&lt;/h2&gt;
&lt;p&gt;Less relevant for larger users, but for smaller groups just running the Kubernetes cluster sometimes does have some non-trivial cost. EKS has about a baseline cost of $0.10 per hour, or roughly $1,000 per year (GKE has a similar base cost). This is often less than computational costs for any corporate user, but isn’t great for individuals or smaller groups.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="maintenance"&gt;
&lt;h2&gt;Maintenance&lt;/h2&gt;
&lt;p&gt;Kubernetes is great, but it does need tending. Invariably we see data professionals start down the Kubernetes path, have good early success, and then within about six months be spending about half of their time tending and modifying the cluster. This is, frankly, how many of the Dask engineers who work on distributed deployment changed careers.&lt;/p&gt;
&lt;p&gt;The human cost is by far the largest monetary cost that we see for leveraging Kubernetes for Dask deployment.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="multi-region"&gt;
&lt;h2&gt;Multi-Region&lt;/h2&gt;
&lt;p&gt;When dealing with larger organizations, or multiple organizations, one tends to need to manage multiple regions. When using raw cloud APIs it’s pretty easy to say “give me 100 workers in us-west-1 or eu-west-2”. When using Kubernetes you tend to create new Kubernetes clusters in each region, which compounds the issues above.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="do-i-need-kubernetes"&gt;
&lt;h2&gt;Do I Need Kubernetes?&lt;/h2&gt;
&lt;p&gt;In general, we find that when organizations have bought into Kubernetes, have qualified professionals maintaining it, and run many always-on services on it then it makes sense to continue using it for bursty computational services like Dask. However when organizations are first starting, don’t have lots of in-house experience, or can’t amortize the maintenance cost across many similar services then Kubernetes is rarely the correct choice long-term.&lt;/p&gt;
&lt;p&gt;A common challenge arises because Kubernetes is pretty easy to get started with, and so people start out, but then the complexities arise late and they’re stuck with a sunken cost.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="coiled-s-decision"&gt;
&lt;h2&gt;Coiled’s Decision&lt;/h2&gt;
&lt;p&gt;Given the experience above, it was obvious for Coiled, an always-on SaaS service provider that we would choose to host our computation with raw cloud APIs. Coiled provides a flexible service for a very wide variety of use cases. Our users want different regions, GPUs, &lt;a class="reference external" href="https://www.coiled.io/blog/save-money-with-spot"&gt;Spot&lt;/a&gt;, ARM chips, etc. and they want it all with rapid scale-up / scale-down properties without an ongoing cost.&lt;/p&gt;
&lt;p&gt;Of course, making a highly robust service wasn’t trivial, but now that this work is done it’s pretty easy for the world to leverage it, a fact of which we are quite proud.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/why-we-passed-on-kubernetes.html"/>
    <summary>Kubernetes is great if you need to organize many always-on services and have in-house expertise, but can add an extra burden and abstraction when deploying a single bursty service like Dask, especially in a user environment with quickly changing needs.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/apple-arm64-mambaforge.html</id>
    <title>Use Mambaforge to Conda Install PyData Stack on your Apple M1 Silicon Machine</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="use-mambaforge-to-conda-install-pydata-stack-on-your-apple-m1-silicon-machine"&gt;

&lt;section id="use-mambaforge"&gt;
&lt;h2&gt;&lt;strong&gt;Use Mambaforge&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Running PyData libraries on an Apple M1 machine requires you to use an ARM64-tailored version of conda-forge. This article provides a step-by-step guide of how to set that up on your machine using &lt;strong&gt;mambaforge&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: this article was first published in August 2021. Some of the issues with conda described below have since been resolved&lt;/em&gt;. &lt;em&gt;I still find that &lt;strong&gt;mambaforge&lt;/strong&gt; builds environments and installs packages&lt;/em&gt; &lt;em&gt;faster and recommend trying it out.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Apple M1 silicon chip is a fantastic innovation: it’s low-power, high-efficiency, has great battery life, and is inexpensive. However, if you’re running the PyData Stack with default builds, you may encounter some strange behaviour.&lt;/p&gt;
&lt;p&gt;When you install a library written for Intel CPU architecture, the code will be run through the Rosetta emulator. This often works great but may cause unexpected problems for specific PyData libraries. For example, I personally encountered this with Dask’s memory usage blowing up when importing a &amp;lt;100MB dataset.&lt;/p&gt;
&lt;p&gt;Since the internals of Rosetta is proprietary, there’s no way to know exactly why this is happening.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="installing-python-libraries-using-mambaforge"&gt;
&lt;h2&gt;Installing Python Libraries using Mambaforge&lt;/h2&gt;
&lt;p&gt;But fear not, there &lt;em&gt;is&lt;/em&gt; a way forward. We can resolve much of this strange behaviour by installing ARM64-native versions of the PyData libraries. While we typically recommend Anaconda/miniconda for most installations, currently, the &lt;strong&gt;mambaforge&lt;/strong&gt; deployment seems to have the smoothest ARM installation process.&lt;/p&gt;
&lt;p&gt;Below is a step-by-step guide to installing this version of conda and using it to install your favorite PyData libraries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Check your version of conda&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;To check your conda installation, type conda info in your terminal and check the value of the platform key. This should say osx-arm64.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If it doesn’t, go ahead and remove the folder containing your conda installation. Note that this will remove your software environments, so be sure to have those saved as .yml or .txt files. You can export the ‘recipe’ for an active software environment by typing conda list — export &amp;gt; env-name.txt OR environment.yml into your terminal.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Download the mambaforge installer&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Navigate to &lt;a class="reference external" href="https://github.com/conda-forge/miniforge"&gt;&lt;em&gt;&lt;strong&gt;this Github repository&lt;/strong&gt;&lt;/em&gt;&lt;/a&gt; and select the Mambaforge-MacOSX-arm64 installer. You can, of course, also opt for one of the other deployment versions listed on that page.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Run the installer.&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;This should ask you if you want it to run conda init for you. Select “yes”.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If for some reason it doesn’t do so (which happened in my case), you will have to run conda init yourself using /path/to/mambaforge/bin/conda init zsh. Note the zsh is only if you’re working with the zsh shell. Use other shell flags as needed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After restarting your terminal, you should now be able to use conda as you always have. You can, for example, begin by rebuilding your software environments using conda env create -f &lt;path to environment.yml&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="specific-issues-when-running-dask-coiled-and-xgboost"&gt;
&lt;h2&gt;Specific issues when running Dask, Coiled and XGBoost&lt;/h2&gt;
&lt;p&gt;Below are some further notes on issues I encountered while trying to run a workload using Dask and XGBoost on Coiled:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;path to 100mb file on s3&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I encountered three issues and would like to share my solutions for them as I can imagine others might be running into this as well:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Dask’s memory usage was blowing up when importing a small  (&amp;lt;100MB) dataset with an error like the one below:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;distributed.nanny — WARNING — Worker exceeded 95% memory budget. 
Restarting
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;&lt;p&gt;In some software environments, my kernel was crashing when calling &lt;strong&gt;import coiled&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After switching over to the mambaforge deployment, I could not install XGBoost.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;1. Dask&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The first issue was resolved by switching over to mambaforge and installing all the required PyData libraries from there, as outlined in the step-by-step guide above.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Coiled&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The second issue was due to a conflict caused by the python-blosc library. For some unknown reason, kernels crash on Apple M1 machines when importing python-blosc (e.g. as part of import coiled) with a segmentation fault. To avoid this, make sure that python-blosc is not installed in your current software environment. If it is, run conda uninstall python-blosc to remove it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. XGBoost&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Finally, If you’re looking to run XGBoost on your Apple M1 machine, you’ll need to jump through some extra hoops as there is currently no arm64-compatible version of XGBoost available on conda-forge. This is not a problem because XGBoost itself can actually run on your Apple M1 machine; the issue is with the dependencies that XGBoost installs, specifically numpy, scipy, and scikit-learn. These dependencies need to be arm64-compatible, which we’ll ensure by completing the following steps:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Create a new conda environment containing Python, numpy, scikit-learn, and scipy. It’s crucial to install these dependencies using conda and not pip here.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;conda&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;boost&lt;/span&gt;
&lt;span class="n"&gt;conda&lt;/span&gt; &lt;span class="n"&gt;activate&lt;/span&gt; &lt;span class="n"&gt;boost&lt;/span&gt;
&lt;span class="n"&gt;conda&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.8.8&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="n"&gt;scipy&lt;/span&gt; &lt;span class="n"&gt;scikit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;learn&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;&lt;p&gt;Install the necessary libraries to compile XGBoost&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;conda&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;cmake&lt;/span&gt; &lt;span class="n"&gt;llvm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;openmp&lt;/span&gt; &lt;span class="n"&gt;compilers&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;&lt;p&gt;Now that we have the right dependencies in place, we can install XGBoost from pip. This is fine since we have all the right arm-64 dependencies installed already.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;xgboost&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ol class="arabic simple" start="4"&gt;
&lt;li&gt;&lt;p&gt;Take it for a spin!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note: the steps above were summarised from &lt;a class="reference external" href="https://towardsdatascience.com/install-xgboost-and-lightgbm-on-apple-m1-macs-cb75180a2dda"&gt;this great tutorial&lt;/a&gt; by &lt;a class="reference external" href="https://medium.com/u/926442548db0"&gt;Fabrice Daniel&lt;/a&gt; and updated after helpful input from &lt;a class="reference external" href="https://twitter.com/isuru_f"&gt;Isuru Fernando&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="moving-forward"&gt;
&lt;h2&gt;Moving Forward&lt;/h2&gt;
&lt;p&gt;As with any technological breakthrough, there are going to be some birthing pains as the industry recalibrates and adjusts to ripple-waves caused by Apple’s switch to M1 silicon. We’re excited to see companies like Anaconda and QuanStack and the Conda-Forge community continue to improve support for ARM hardware and stoked for the powerful future of PyData this promises!&lt;/p&gt;
&lt;p&gt;We hope this post helps get your PyData workflows running smoothly and effectively on your Apple M1 machine. If you have any feedback or suggestions for future material, please do not hesitate to reach out to us on &lt;a class="reference external" href="https://twitter.com/coiledhq?lang=en"&gt;Twitter&lt;/a&gt; or our &lt;a class="reference external" href="https://join.slack.com/t/coiled-users/shared_invite/zt-hx1fnr7k-In~Q8ui3XkQfvQon0yN5WQ"&gt;Coiled Community Slack channel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you’re interested in trying out Coiled, which provides hosted Dask clusters, docker-less managed software, and one-click deployments, you can do so for free today when you click below.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/apple-arm64-mambaforge.html"/>
    <summary>Running PyData libraries on an Apple M1 machine requires you to use an ARM64-tailored version of conda-forge. This article provides a step-by-step guide of how to set that up on your machine using mambaforge.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/managed-dask.html</id>
    <title>Understanding Managed Dask (Dask as a Service)</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="understanding-managed-dask-dask-as-a-service"&gt;

&lt;p&gt;‍&lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; is a flexible Python library for parallel and distributed computing. There are a number of ways you can create Dask clusters, each with their own benefits. In this article, we explore how Coiled provides a managed cloud infrastructure solution for Dask users, addressing:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Deployment challenges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Software environment and credential management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hardware flexibility&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Historical tracking and observability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost optimization&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/eXP-YuERvi4" title="Six Coiled features for Dask users" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;section id="how-to-deploy-a-dask-cluster"&gt;
&lt;h2&gt;How to deploy a Dask cluster&lt;/h2&gt;
&lt;p&gt;There are a number of tools you can use to manage Dask clusters, including:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://kubernetes.dask.org/en/latest/"&gt;Dask Kubernetes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://gateway.dask.org/"&gt;Dask Gateway&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://cloudprovider.dask.org/en/latest/"&gt;Dask Cloud Provider&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In principle, Coiled is just like these other Dask as a service products, in that you can also use Coiled to create a Dask cluster. While this might sound straightforward, there’s significant nuance to deploying a Dask cluster effectively.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="install-packages-and-sync-credentials"&gt;
&lt;h2&gt;Install packages and sync credentials&lt;/h2&gt;
&lt;p&gt;On cluster start, Coiled examines your local machine, identifying pip and conda packages and any development packages you have locally. It packages these libraries and deploys them dynamically on cloud-based Linux machines in your cluster. This approach solves the &lt;a class="reference external" href="https://docs.dask.org/en/stable/deployment-considerations.html#consistent-software-environments"&gt;software environment matching problem&lt;/a&gt; between your local and remote software environments. Additionally, Coiled securely transfers your local cloud credentials to the workers, ensuring they can access your cloud resources, like S3 buckets, safely and securely.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="use-gpus-arm-and-other-hardware"&gt;
&lt;h2&gt;Use GPUs, ARM, and other hardware&lt;/h2&gt;
&lt;p&gt;Coiled provides a range of options for configuring your Dask cluster, making it highly flexible. You can specify the number of virtual machines with n_workers and choose a region close to your remote data with the region option. Set the number of CPU cores with worker_cpu, &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/gpu.html"&gt;access GPUs&lt;/a&gt; with worker_gpu, or opt for more efficient &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/arm.html"&gt;ARM instances&lt;/a&gt; using arm=True.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_cpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;By contrast, if you’re using Kubernetes, you’d have to deploy a new Kubernetes cluster in this region with the node pool attached to a certain node type that has these kinds of machines in it. It can be kind of daunting.&lt;/p&gt;
&lt;p&gt;Coiled’s cluster configuration flexibility extends to AWS, GCP, and Azure allowing you to dynamically start machines of any type in any region, a powerful feature for hardware experimentation.&lt;/p&gt;
&lt;section id="dask-on-aws"&gt;
&lt;h3&gt;Dask on AWS&lt;/h3&gt;
&lt;p&gt;Create Dask clusters on AWS by connecting Coiled to your AWS account. Coiled starts EC2 instances when you need them and turns them off when you’re done. It’s easy to set up Coiled to run from your AWS account:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;login
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;aws
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="dask-on-gcp"&gt;
&lt;h3&gt;Dask on GCP&lt;/h3&gt;
&lt;p&gt;You can also create Dask clusters on GCP by connecting Coiled to your Google Cloud account. Coiled will start virtual machines when you need them and clean up VMs when you’re done.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;login
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;gcp
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="dask-on-azure"&gt;
&lt;h3&gt;Dask on Azure&lt;/h3&gt;
&lt;p&gt;You can also create Dask clusters on Azure by connecting Coiled to your Azure account. Coiled will start virtual machines when you need them and clean up VMs when you’re done.&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;login
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;azure
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="observability-into-your-dask-cluster"&gt;
&lt;h2&gt;Observability into your Dask cluster&lt;/h2&gt;
&lt;p&gt;Coiled provides &lt;a class="reference external" href="https://www.youtube.com/watch?v=4PiZf6UvCv0"&gt;visibility and historical tracking&lt;/a&gt;, offering insights into all activities for your team. You can monitor currently running and past clusters, with flags marking specific workload characteristics. For each cluster, you gain access to information about executed code, errors, and metrics. This visibility lets you optimize work across your team, even for members who are less familiar with the Dask codebase.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="managing-dask-cloud-costs"&gt;
&lt;h2&gt;Managing Dask cloud costs&lt;/h2&gt;
&lt;p&gt;Effective cost management is crucial for organizations using cloud resources. Coiled offers team management features that enable you to limit the number of CPU cores running concurrently and on a monthly basis. You can also control team spending by monitoring usage. By default, all Coiled clusters will shutdown after 20 minutes of inactivity. These features are especially valuable for teams supporting users with varying skill levels, ensuring costs remain within budgetary constraints.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="optimizing-dask-cloud-costs"&gt;
&lt;h2&gt;Optimizing Dask cloud costs&lt;/h2&gt;
&lt;p&gt;Coiled goes beyond cost management and helps optimize expenses in several ways, including minimizing network costs, using &lt;a class="reference external" href="https://docs.coiled.io/blog/burstable-vs-nonburstable.html"&gt;cost-effective non-burstable instance types as the default&lt;/a&gt;, and making it easy to use &lt;a class="reference external" href="https://docs.coiled.io/blog/dask-graviton.html"&gt;efficient hardware like ARM instances&lt;/a&gt;. There’s nuance to offering this level of configurability and also creating a seamless user experience, though. One example of this is how Coiled helps you take advantage of spot (or preemptible) instances.&lt;/p&gt;
&lt;p&gt;Though spot instances are heavily discounted, most users are willing to pay more to avoid interruptions to their workflows as spot instances become unavailable. With Coiled, when you request spot instances, we look at your region and select the data center in that region with the best spot availability. Once we get the warning that these spot instances are being deprovisioned, we move your tasks onto new spot or on-demand instances. This ensures the continuity of your workloads without interruption. Learn more in &lt;a class="reference internal" href="blog/save-money-with-spot.html"&gt;&lt;span class="doc std std-doc"&gt;our blog post&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="in-conclusion"&gt;
&lt;h2&gt;In Conclusion&lt;/h2&gt;
&lt;p&gt;Coiled is a seamless managed Dask solution. It offers a smooth and secure cloud infrastructure experience for both individual users and organizations. Notably, you can use Coiled for free for the first 500 CPU hours per month, making it accessible to many users (you’ll still be responsible for your cloud provider costs).&lt;/p&gt;
&lt;p&gt;If you are currently using other Dask as a service products, like Dask Cloud Provider or Dask Kubernetes, Coiled offers cost savings and an improved user experience, making it worth considering to manage your Dask workflows in the cloud.&lt;/p&gt;
&lt;p&gt;It’s easy to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;get started&lt;/a&gt; with Dask on AWS, GCP, or Azure.&lt;/p&gt;
&lt;section id="related-resources"&gt;
&lt;h3&gt;Related Resources&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Overview on &lt;a class="reference external" href="https://docs.dask.org/en/stable/deploying.html#distributed-computing"&gt;deploying Dask clusters&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Additional &lt;a class="reference external" href="https://docs.dask.org/en/stable/deployment-considerations.html"&gt;Dask deployment considerations&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New to Dask? Here’s &lt;a class="reference external" href="https://www.nvidia.com/en-us/glossary/data-science/dask/"&gt;an overview from NVIDIA&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/managed-dask.html"/>
    <summary>‍Dask is a flexible Python library for parallel and distributed computing. There are a number of ways you can create Dask clusters, each with their own benefits. In this article, we explore how Coiled provides a managed cloud infrastructure solution for Dask users, addressing:</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/tackling-unmanaged-memory-with-dask.html</id>
    <title>Tackling unmanaged memory with Dask</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="tackling-unmanaged-memory-with-dask"&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;TL;DR:&lt;/em&gt; &lt;em&gt;unmanaged memory is RAM that the Dask scheduler is not directly aware of and which can cause workers to run out of memory and cause computations to hang and crash.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this article we:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Shed light on the common error message “Memory use is high but worker has no data to store to disk. Perhaps some other process is leaking memory?”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identify the most common causes of unmanaged memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the Dask dashboard to monitor it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn techniques to deflate it in some of the most typical cases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get an overview of what’s changing in how Dask deals with unmanaged memory&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="what-is-managed-memory"&gt;
&lt;h2&gt;What is managed memory?&lt;/h2&gt;
&lt;p&gt;At any given moment, the Dask scheduler tracks chunks of data scattered over the workers of the cluster, which is typically either the input or output of Dask tasks. The RAM it occupies is called &lt;em&gt;managed memory&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For example, if we write:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numpy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now one of the workers is holding 8 MB of managed memory, which will be released as soon as the future will be dereferenced.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-is-unmanaged-memory"&gt;
&lt;h2&gt;What is unmanaged memory?&lt;/h2&gt;
&lt;p&gt;Managed memory never fully accounts for all of the &lt;em&gt;process memory&lt;/em&gt;, that is the RAM occupied by the worker processes and observed by the OS.&lt;/p&gt;
&lt;p&gt;The difference between process memory and managed memory is called &lt;em&gt;unmanaged memory&lt;/em&gt; and it is the sum of:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;The Python interpreter code, loaded modules, and global variables&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;sizeof&lt;/strong&gt;() method of objects in managed memory returning incorrect results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Memory temporarily used by running tasks (sometimes improperly referred to as &lt;em&gt;heap&lt;/em&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dereferenced Python objects that have not been garbage-collected yet&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unused memory that the Python memory allocator did not return to libc through free() yet&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unused memory that the user-space libc free() function did not release to the OS yet (read &lt;em&gt;Memory trimming&lt;/em&gt; below)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Memory leaks (unreachable memory in C space and/or Python objects that are never released for some reason)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Memory fragmentation (large contiguous objects - e.g. numpy arrays - failing to fit in the empty space left by deallocated smaller objects; large memory pages that are mostly free but can’t be released because a small part of them is still in use)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In an ideal situation, all of the above are negligible in size. This is not always the case, however, and unmanaged memory can accumulate to unsustainable levels. This can cause workers to run out of memory and cause whole computations to hang or crash. You may have experienced this yourself. For example, when using Dask you may have seen error reports like the following:&lt;/p&gt;
&lt;p&gt;Memory use is high but worker has no data to store to disk.&lt;/p&gt;
&lt;p&gt;Perhaps some other process is leaking memory?&lt;/p&gt;
&lt;p&gt;Process memory: 61.4GiB – Worker memory limit: 64 GiB&lt;/p&gt;
&lt;/section&gt;
&lt;section id="monitor-unmanaged-memory-with-the-dask-dashboard"&gt;
&lt;h2&gt;Monitor unmanaged memory with the Dask dashboard&lt;/h2&gt;
&lt;p&gt;Since distributed 2021.04.1, the Dask dashboard breaks down the memory usage of each worker and of the cluster total:&lt;/p&gt;
&lt;img alt="Image 9" src="https://docs.coiled.io/_images/633f7b59687ae47a13a58bc2_image-9-700x789.png" /&gt;
&lt;p&gt;In the graph we can see:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Managed&lt;/em&gt; memory in solid color (blue or, if the process memory is close to the limit, orange)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Unmanaged&lt;/em&gt; memory in a lighter shade&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Unmanaged recent&lt;/em&gt; memory in an even lighter shade (read below)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spilled memory (managed memory that has been moved to disk and no longer occupies RAM - see &lt;a class="reference external" href="https://distributed.dask.org/en/latest/worker.html#spill-data-to-disk"&gt;https://distributed.dask.org/en/latest/worker.html#spill-data-to-disk&lt;/a&gt;) in grey&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Unmanaged recent&lt;/em&gt; is the portion of unmanaged memory that has appeared over the last 30 seconds. The idea is that, hopefully, most of it is due to garbage collection lag or tasks heap and should disappear soon. If it doesn’t, it will transition into unmanaged memory. Unmanaged recent is tracked separately so that it can be ignored by heuristics - namely, rebalance() - for increased stability.&lt;/p&gt;
&lt;p&gt;By construction, &lt;strong&gt;process memory = managed + unmanaged + unmanaged recent&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The same information is available in tabular format on the &lt;em&gt;Workers&lt;/em&gt; tab:&lt;/p&gt;
&lt;img alt="Image 15" src="https://docs.coiled.io/_images/633f7b5a040abd1604964aa0_image-15-700x502.png" /&gt;
&lt;p&gt;In the screenshots above, we see large amounts of &lt;em&gt;unmanaged recent&lt;/em&gt; memory - which should hopefully help us identify what event caused it.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="investigate-and-deflate-unmanaged-memory"&gt;
&lt;h2&gt;Investigate and deflate unmanaged memory&lt;/h2&gt;
&lt;p&gt;If you see a large amount of unmanaged memory, it would be a mistake to blindly assume it is a leak. As listed earlier, there are several other frequent causes:&lt;/p&gt;
&lt;section id="task-heap"&gt;
&lt;h3&gt;Task heap&lt;/h3&gt;
&lt;p&gt;How much RAM is the execution of your tasks taking? Do large amounts of unmanaged memory appear only when running a task and disappear immediately afterwards? If so, you should consider breaking your data into smaller chunks.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="garbage-collection"&gt;
&lt;h3&gt;Garbage collection&lt;/h3&gt;
&lt;p&gt;Do you get a substantial reduction in memory usage if you manually trigger the garbage collector?&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gc&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# collect garbage on all workers&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In CPython (and unlike, namely, pypy and Java) the garbage collector is only involved in the case of circular references. If calling gc.collect() on your workers makes your unmanaged memory drop, you should investigate your data for circular references and/or tweak the gc settings on the workers through a Worker Plugin [&lt;a class="reference external" href="https://distributed.dask.org/en/latest/plugins.html#worker-plugins"&gt;https://distributed.dask.org/en/latest/plugins.html#worker-plugins&lt;/a&gt;].&lt;/p&gt;
&lt;/section&gt;
&lt;section id="memory-trimming"&gt;
&lt;h3&gt;Memory trimming&lt;/h3&gt;
&lt;p&gt;Another important cause of unmanaged memory on Linux and MacOSX, which is not widely known about, derives from the fact that the libc malloc()/free() manage a user-space memory pool, so free() won’t necessarily release memory back to the OS. This is particularly likely to affect you if you deal with large amounts of small Python objects - basically, all non-NumPy data as well as small NumPy chunks (which underlie both dask.array and dask.dataframe). What constitutes a “small” NumPy chunk varies by OS, distribution, and possibly the amount of installed RAM; it’s been empirically observed on chunks sized 1 MiB each or smaller.&lt;/p&gt;
&lt;p&gt;To see if trimming helps, you can run (Linux workers only):&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ctypes&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;trim_memory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;libc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CDLL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;libc.so.6&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;libc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;malloc_trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trim_memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here’s an example of how the dashboard can look like before and after running malloc_trim:&lt;/p&gt;
&lt;img alt="Image 1-3" src="https://docs.coiled.io/_images/633f7b5a040abd76b7964a9f_image1-3-700x407.png" /&gt;
&lt;p&gt;If you observe a reduction in unmanaged memory like in the above images, read &lt;a class="reference external" href="https://distributed.dask.org/en/latest/worker.html#memtrim"&gt;https://distributed.dask.org/en/latest/worker.html#memtrim&lt;/a&gt; for how you can robustly tackle the problem in production.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="what-s-changing-in-how-dask-handles-unmanaged-memory"&gt;
&lt;h2&gt;What’s changing in how Dask handles unmanaged memory&lt;/h2&gt;
&lt;p&gt;Starting from distributed 2021.06.0, rebalance()[&lt;a class="reference external" href="https://distributed.dask.org/en/latest/api.html#distributed.Client.rebalance"&gt;https://distributed.dask.org/en/latest/api.html#distributed.Client.rebalance&lt;/a&gt;] takes unmanaged memory into consideration, so if e.g. you have a memory leak on a single worker, that worker will hold less managed data than its peers after rebalancing. This is something to keep in mind if you are both a rebalance() user and are affected by trimming issues (as described in the previous section).&lt;/p&gt;
&lt;p&gt;In the future, we will revise other heuristics that measure worker memory - namely, those underlying scatter() [&lt;a class="reference external" href="https://distributed.dask.org/en/latest/locality.html#data-scatter"&gt;https://distributed.dask.org/en/latest/locality.html#data-scatter&lt;/a&gt;]&lt;/p&gt;
&lt;p&gt;and replicate() [&lt;a class="reference external" href="https://distributed.dask.org/en/latest/api.html#distributed.Client.replicate"&gt;https://distributed.dask.org/en/latest/api.html#distributed.Client.replicate&lt;/a&gt;] to ensure that they consider unmanaged memory.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Recent developments in Dask made memory usage on the cluster a lot more transparent to the user. Unexplained memory usage is not necessarily caused by a memory leak, and hopefully, this article added some tools to your kit when you need to debug it.&lt;/p&gt;
&lt;p&gt;To learn more about Dask, visit the Dask website.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/tackling-unmanaged-memory-with-dask.html"/>
    <summary>TL;DR: unmanaged memory is RAM that the Dask scheduler is not directly aware of and which can cause workers to run out of memory and cause computations to hang and crash.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/speed-up-pandas-query-10x-with-dask.html</id>
    <title>Speed up a pandas query 10x with these 6 Dask DataFrame tricks</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="speed-up-a-pandas-query-10x-with-these-6-dask-dataframe-tricks"&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Updated April 18th, 2024&lt;/strong&gt;&lt;/em&gt;: &lt;em&gt;For Dask versions &amp;gt;= 2024.3.0, Dask will perform a number of the optimizations discussed in this blog post automatically. See &lt;a class="reference external" href="https://youtu.be/HTKzEDa2GA8?si=FkYToNKW7ooBQ-bA&amp;amp;amp;t=38"&gt;this demo&lt;/a&gt; for more details.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This post demonstrates how to speed up a pandas query to run 10 times faster with Dask using six performance optimizations. You’ll often want to incorporate these tricks into your workflows so your data analyses can run faster.&lt;/p&gt;
&lt;p&gt;After learning how to optimize the pandas queries on your local machine, you’ll see how to query much larger datasets with cloud resources using Dask clusters that are managed by Coiled.&lt;/p&gt;
&lt;p&gt;Here are the 6 strategies covered in this post:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Parallelize the query with Dask&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;object&lt;/span&gt;&lt;/code&gt; columns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Split up single large file into 100 MB files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Parquet instead of CSV&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compress files with Snappy&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leverage column pruning&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This diagram illustrates how the query time decreases as each performance optimization is applied.&lt;/p&gt;
&lt;img alt="Screen shot 2022-02-11" src="https://docs.coiled.io/_images/633f7b603a4f10fc6f5db5b1_Screen-Shot-2022-02-11-at-9.12.15-AM-700x425.png" /&gt;
&lt;p&gt;After seeing how to optimize this query on a 5 GB dataset, we’ll check out how to run the same query on a 50 GB dataset with a Dask cluster.  The cluster computation will show that these performance optimizations also help when querying large datasets in the cloud.&lt;/p&gt;
&lt;p&gt;Let’s start by running a groupby operation on a 5 GB dataset with pandas to establish a runtime baseline.&lt;/p&gt;
&lt;section id="pandas-query-baselines"&gt;
&lt;h2&gt;Pandas query baselines&lt;/h2&gt;
&lt;p&gt;The queries in this post will be run on a 5 GB dataset that contains 9 columns. Here are the first five rows of data, so you can get a feel of the data contents.&lt;/p&gt;
&lt;img alt="Screen shot 2022-01-25" src="https://docs.coiled.io/_images/633f7b603a4f10ccb25db5b0_9795-Screen-Shot-2022-01-25-at-6.01.17-AM.png" /&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;You can run this AWS CLI command to download the dataset.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;aws s3 cp s3://coiled-datasets/h2o-benchmark/N_1e8_K_1e2_single.csv data/&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s use pandas to run a groupby computation and establish a performance baseline.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/N_1e8_K_1e2_single&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query takes 182 seconds to run.&lt;/p&gt;
&lt;p&gt;Here’s the query result:&lt;/p&gt;
&lt;img alt="Screen shot 2022-01-25" src="https://docs.coiled.io/_images/633f7b603a4f1003945db5b2_9795-Screen-Shot-2022-01-25-at-6.14.36-AM.png" /&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;Let’s see how Dask can make this query run faster, even when the Dask computation is not well structured.&lt;/p&gt;
&lt;p&gt;If you don’t want to download this dataset locally, you can also run these computations on the cloud. Later in this blog post, we’ll provide a demo that’ll show you how to use Coiled to easily spin up a Dask cluster and run these computations with machines in the cloud.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-query-baseline"&gt;
&lt;h2&gt;Dask query baseline&lt;/h2&gt;
&lt;p&gt;Let’s run the same groupby query with Dask. We’re going to intentionally type all the columns as object columns, which should give us a good worst case Dask performance benchmark. object columns are notoriously memory hungry and inefficient.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id5&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id6&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;v2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;v3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/N_1e8_K_1e2_single.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s type v1 as an integer column and then run the same groupby query as before.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;int64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;That query runs in 122 seconds.&lt;/p&gt;
&lt;p&gt;Dask runs faster than pandas for this query, even when the most inefficient column type is used, because it parallelizes the computations. pandas only uses 1 CPU core to run the query. My computer has 4 cores and Dask uses all the cores to run the computation.&lt;/p&gt;
&lt;p&gt;Let’s recreate the DataFrame with more efficient data types and see how that improves the query runtime.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="avoiding-object-columns-with-dask"&gt;
&lt;h2&gt;Avoiding object columns with Dask&lt;/h2&gt;
&lt;p&gt;We can type id1, id2, and id3 as string type columns, which are more efficient as described in the following video.&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/RAAcDkQgBTI?start=364" title="Dask DataFrame is Fast Now" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;id4, id5, id6, v1, and v2 can be typed as integers. v3 can be typed as a floating point number. Here is the revised computation.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;better_dtypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;string[pyarrow]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;string[pyarrow]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;string[pyarrow]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;int64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id5&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;int64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id6&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;int64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;int64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;v2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;int64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;v3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;float64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/N_1e8_K_1e2_single.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;better_dtypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query runs in 67 seconds. Avoiding object type columns allows for a significant performance boost.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="split-data-in-multiple-files"&gt;
&lt;h2&gt;Split data in multiple files&lt;/h2&gt;
&lt;p&gt;Let’s split up the data into multiple files instead of a single 5 GB CSV file. Here’s code that’ll split up the data into 100 MB CSV files.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;100MB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/csvs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s rerun the query on the smaller CSV files.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/csvs/*.part&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;better_dtypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The query now runs in 61 seconds, which is a bit faster. Dask can read and write multiple files in parallel. Parallel I/O is normally a lot faster than working with a single large file.&lt;/p&gt;
&lt;p&gt;Let’s try to use the Parquet file format and see if that helps.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="use-parquet-instead-of-csv"&gt;
&lt;h2&gt;Use Parquet instead of CSV&lt;/h2&gt;
&lt;p&gt;Here’s the code that’ll convert the small CSV files into small Parquet files.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s rerun the same query off the Parquet files.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query executes in 39 seconds, so Parquet provides a nice performance boost. Columnar file formats that are stored as binary usually perform better than row-based, text file formats like CSV. Compressing the files to create smaller file sizes also helps.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="compress-parquet-files-with-snappy"&gt;
&lt;h2&gt;Compress Parquet files with Snappy&lt;/h2&gt;
&lt;p&gt;Let’s recreate the Parquet files with Snappy compression and see if that helps.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/snappy-parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;snappy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s rerun the same query off the Snappy compressed Parquet files.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/snappy-parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query runs in 38 seconds, which is a bit of a performance boost.&lt;/p&gt;
&lt;p&gt;In this case, the files are stored on my hard drive and are relatively small. When data is stored in cloud based object stores and sent over the wire, file compression can result in an even bigger performance boost.&lt;/p&gt;
&lt;p&gt;Let’s leverage the columnar nature of the Parquet file format to make the query even faster.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="leverage-column-pruning"&gt;
&lt;h2&gt;Leverage column pruning&lt;/h2&gt;
&lt;p&gt;Parquet is a columnar file format, which means you can selectively grab certain columns from the file. This is commonly referred to as column pruning. Column pruning isn’t possible for row based file formats like CSV.&lt;/p&gt;
&lt;p&gt;Let’s grab the columns that are relevant for our query and run the query again.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/snappy-parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query runs in 19 seconds.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="overall-performance-improvement"&gt;
&lt;h2&gt;Overall performance improvement&lt;/h2&gt;
&lt;p&gt;The original pandas query took 182 seconds and the optimized Dask query took 19 seconds, which is about 10 times faster.&lt;/p&gt;
&lt;p&gt;Dask can provide performance boosts over pandas because it can execute common operations in parallel, where pandas is limited to a single core. For this example, I configured Dask to use the available hardware resources on my laptop to execute the query faster. Dask scales with your hardware to optimize performance.&lt;/p&gt;
&lt;p&gt;My machine only has 4 cores and Dask already gives great results. A laptop that has 14 cores would be able to run the Dask operations even faster.&lt;/p&gt;
&lt;p&gt;Parquet files allow this query to run a lot faster than CSV files. The query speeds get even better when Snappy compression and column pruning are used.&lt;/p&gt;
&lt;p&gt;The query optimization patterns outlined in this blog are applicable to a wide variety of use cases. Splitting your data into optimally sized files on disk makes it easier for Dask to read the files in parallel easier. Strategically leveraging file compression and column pruning almost always helps. There are often easy tweaks that’ll let you run your pandas code a lot faster with Dask.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="querying-a-large-dataset-with-the-cloud"&gt;
&lt;h2&gt;Querying a large dataset with the cloud&lt;/h2&gt;
&lt;p&gt;Let’s take a look at how to run this same query on a 50 GB dataset that’s stored in the cloud.&lt;/p&gt;
&lt;p&gt;Downloading the 50 GB dataset to our laptop and querying it in a localhost environment would be tedious and slow.  You’re limited to the computing power of your laptop when running analysis locally.&lt;/p&gt;
&lt;p&gt;Dask is designed to either be run on a laptop or with a cluster of computers that process the data in parallel.  Your laptop may only have 8GB or 32GB of RAM, so its computation power is limited.  Cloud clusters can be constructed with as many workers as you’d like, so they can be made quite powerful.&lt;/p&gt;
&lt;p&gt;Cloud devops is challenging, so let’s use Coiled to build our Dask cluster.  This will make it a lot easier than building a production grade Dask solution ourselves.&lt;/p&gt;
&lt;p&gt;Here’s how to make a Dask cluster with 15 nodes (each with 16 GiB of RAM) to query the 50 GB dataset.&lt;/p&gt;
&lt;p&gt;Here’s how to spin up the Dask cluster with Coiled:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                     
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;# run in the same region as data&lt;/span&gt;
    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# save money with spot instances&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Each instance has 16 GiB of RAM.  The cluster has 15 nodes, so the entire cluster has 240 GiB of RAM.  That’s a lot of computing resources to perform an analysis on a large dataset!&lt;/p&gt;
&lt;p&gt;Once the cluster is created, we can read in data from S3 into a Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/h2o-benchmark/N_1e9_K_1e2_parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s see how many rows of data the DataFrame contains.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 1000000000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Wow, this DataFrame contains a billion rows of data.  Let’s run the same query as before and see how long it takes:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The query runs in 10 seconds. We can see that Coiled allows us to easily spin up a Dask cluster and query a large dataset quickly. It’s free and easy to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;get started with Coiled&lt;/a&gt; and run the query yourself.&lt;/p&gt;
&lt;p&gt;Part of the reason this query is running so fast is because we’re using the query optimizations covered earlier.  We’re querying snappy compressed Parquet files that each contain 100 MB of data and Dask DataFrame automatically uses column pruning to grab the relevant columns for the query.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;pandas is a fine technology for smaller datasets, but when the data volume is large, you’ll want to use a cluster computing technology like Dask that can scale beyond the limits of a single machine.&lt;/p&gt;
&lt;p&gt;You can manually create a Dask cluster yourself, but it’s a lot of devops work.  Most data scientists and data engineers don’t want to be bothered with a challenging devops project like productionalizing and tuning a Dask environment.  It’s generally best to use Coiled, a service that lets you focus on your analysis without worrying about Dask devops.&lt;/p&gt;
&lt;p&gt;In this post you’ve seen how to significantly speed up a pandas query and you’ve also taken a glimpse of harnessing the power of the cloud to run a computation in a cluster.  These tactics will serve you well when you’re running analyses on larger datasets.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/speed-up-pandas-query-10x-with-dask.html"/>
    <summary>Updated April 18th, 2024: For Dask versions &gt;= 2024.3.0, Dask will perform a number of the optimizations discussed in this blog post automatically. See this demo for more details.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/moving-from-spark-to-dask.html</id>
    <title>Spark to Dask: The Good, Bad, and Ugly of Moving from Spark to Dask</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="spark-to-dask-the-good-bad-and-ugly-of-moving-from-spark-to-dask"&gt;

&lt;p&gt;Apache Spark has long been a popular tool for handling petabytes of data in analytics applications. It’s often used for big data and machine learning, and most organizations use it with cloud infrastructure to run models and build algorithms. Spark is no doubt a fast analytical tool that provides high-speed queries for large datasets, but recent client testimonials tell us that Dask is even faster. So, what should you keep in mind when moving from Spark to Dask?&lt;/p&gt;
&lt;p&gt;We interviewed &lt;a class="reference external" href="https://www.steppingblocks.com/"&gt;Steppingblocks&lt;/a&gt;, an analytic organization that provides valuable insights on workforce and education outcomes based on 130 million individuals registered in US universities, employed at private organizations, and working at government agencies. Their data journey started with basic linear SQL, they moved to Spark, and later they migrated to Dask. By moving to Dask, Steppingblocks shortened their processing times from 24 hours in Spark to 6 hours in Dask, but they learned several lessons in the process. They’ve provided us (and you!) with some valuable insights on the good, bad, and ugly should you decide to migrate your organization’s machine learning and big data processing over to Dask.&lt;/p&gt;
&lt;section id="spark-to-dask-what-prompted-the-move"&gt;
&lt;h2&gt;Spark to Dask: What Prompted the Move?&lt;/h2&gt;
&lt;p&gt;For most organizations, migration is a huge undertaking that isn’t decided lightly. You need a return on your investment, and Steppingblocks was no different. They saw some improvements when moving from standard relational SQL to Spark, but they also found several challenges with Spark development that motivated their team to look elsewhere.&lt;/p&gt;
&lt;p&gt;The biggest challenge was debugging code in Spark. Developers knew Python but did not know Spark, so it was a learning experience that didn’t come with much support. They found that traceback errors were not very intuitive, and they spent too much time on Stack Overflow to find answers to their problems. Support from Spark was too expensive, so any problems were left to their own devices. Finally, Spark felt like overkill for what they were doing, and it showed in their computing costs. Costs and resources were high for what Steppingblocks needed.&lt;/p&gt;
&lt;p&gt;Also, Steppingblocks was purely a Python development team. They found that they were trying to force-fit their Python into the Spark framework. What attracted them to Dask was the more native way it worked with Python, and it made development simpler.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-good-that-led-to-migration-success"&gt;
&lt;h2&gt;The Good That Led to Migration Success&lt;/h2&gt;
&lt;p&gt;Every migration takes planning and work, but not every migration is quick and painless. Before getting into Steppingblocks challenges, they revealed several successes that can be attributed to Dask. First, let’s talk about the migration successes.&lt;/p&gt;
&lt;p&gt;The biggest win was the improvement in ETL speed. Steppingblocks processing time went from 24 hours to run their ETL procedures to 6 hours. Integration of MLFlow models with Dask was much more efficient with better feedback from the Dask system should they run into any errors from their Python code. The Coiled team was supportive and helped them work through difficult errors that developers could not figure out on their own.&lt;/p&gt;
&lt;p&gt;Running ETLs in Spark requires cloud resources, but Dask can run locally. With Spark, you need a powerful machine only available in the cloud even for small datasets and computing tasks. Developing on Spark with datasets of any size requires access to cloud resources.  By contrast, the ability for developers to run Dask locally accelerated the development lifecycle.&lt;/p&gt;
&lt;p&gt;Overall, Steppingblocks was able to migrate 90% of their codebase from Spark to Dask in only a few weeks with a few extra months needed for optimization. The entire initial migration was done in 3 months. They shared a few success metrics below:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Timeframe: &lt;strong&gt;Migration took three months&lt;/strong&gt; with two engineers and 9 months for optimization, but optimization delays were not related to Dask.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost: The &lt;strong&gt;cost to run Dask is 40% less than Spark&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run time: &lt;strong&gt;Dask tasks run three times faster than Spark&lt;/strong&gt; ETL queries and use less CPU resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Codebase: The &lt;strong&gt;main ETL codebase took three months to build&lt;/strong&gt; with 13,000 lines of code. Developers then built the codebase to 33,000 lines of code in nine months of optimization, much of which was external library integration.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt="Dask vs Spark benefits" src="https://docs.coiled.io/_images/6372d8132bea68602fa92c08_Dask%2520v%2520Spark%2520benefits.png" /&gt;
&lt;/section&gt;
&lt;section id="the-bad-and-ugly-or-challenges-during-spark-to-dask-migration"&gt;
&lt;h2&gt;The Bad and Ugly, or Challenges during Spark to Dask Migration&lt;/h2&gt;
&lt;p&gt;Scaling Dask was a challenge at times. Steppingblocks developers could scale Dask locally but then production Dask servers would return errors. This is where support helped Steppingblocks work through their errors and figure out what was wrong. This type of support is costly for commercial companies developing with Spark for enterprise applications.&lt;/p&gt;
&lt;p&gt;The biggest challenge was porting Spark SQL code to Dask. It’s what took the most time, including the time to optimize it. As with any new environment, developers need time to learn the new framework and grow accustomed to changes.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="migration-aftermath-and-the-dask-journey-since-deployment"&gt;
&lt;h2&gt;Migration Aftermath and the Dask Journey Since Deployment&lt;/h2&gt;
&lt;p&gt;As any developer knows, initial migration doesn’t stop when code is finally ported. You still need to maintain and reliably work with the new framework. Moving away from linear relational SQL is always a benefit for machine learning and modeling, but choosing the right platform is the challenge to ensure that you have the right tools and environment.&lt;/p&gt;
&lt;p&gt;Steppingblocks found it much easier to migrate machine learning models and iterate them more rapidly. Their developers work in a fast-paced environment, and Dask supports their efforts. Coiled also works quickly, so developers must ensure that they are aware of the latest updates and changes to Coiled versions during testing and deployment. Whenever they have issues, however, the Coiled team helps support them.&lt;/p&gt;
&lt;p&gt;Because Dask can work locally, developers can test their Python code in all three stages – local, local Dask, and Coiled. Moving Python modules rather than notebooks makes development iterations much faster and more reliable. Developers can build software following industry best practices including linting, unit testing, and integration testing.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="moving-from-spark-to-dask-here-are-lessons-learned-and-tips-and-tricks"&gt;
&lt;h2&gt;Moving from Spark to Dask? Here are Lessons Learned and Tips and Tricks&lt;/h2&gt;
&lt;p&gt;If you’re thinking about moving from Spark to Dask, the Steppingblocks developers have some tips and tricks from their own lessons learned. You might find that you have your own challenges based on your unique business requirements, but one consistent story from developers is that the Coiled team was helpful in migration success and will be there to help work through any issues.&lt;/p&gt;
&lt;p&gt;Here are a few tips before you plan out your migration from Spark to Dask:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;You must run mirror development environments locally and on the cluster, or you may run into errors from version differences, but the newly released Coiled Runtime helps alleviate this issue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Beware of defaults around partitioning and prepare to split out, but it’s useful to take advantage of map_partitions to help reduce issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use worker.data in Dask for lookup tables and don’t forget to leverage caching for speed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don’t rely on the cloud for 100% uptime. Instead, build defensive programming techniques for retries in case of timeouts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leverage distributed.wait to break down large computer graphs if memory usage becomes an issue.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Watch the recording of our webinar with Sébastien Arnaud of Steppingblocks: &lt;a class="reference external" href="https://www.youtube.com/watch?v=jR0Y7NqKJs8"&gt;“Why We Switched from Spark to Dask | Our Data Journey”&lt;/a&gt;.&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/jR0Y7NqKJs8" title="Spark vs Dask | Why We Switched from Spark to Dask | Sébastien Arnaud at Steppingblocks | June 2022" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/moving-from-spark-to-dask.html"/>
    <summary>Apache Spark has long been a popular tool for handling petabytes of data in analytics applications. It’s often used for big data and machine learning, and most organizations use it with cloud infrastructure to run models and build algorithms. Spark is no doubt a fast analytical tool that provides high-speed queries for large datasets, but recent client testimonials tell us that Dask is even faster. So, what should you keep in mind when moving from Spark to Dask?</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/snowflake-and-dask.html</id>
    <title>Snowflake and Dask: a Python Connector for Faster Data Transfer</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>James Bourbeau and Florian Jetter</name>
    </author>
    <content type="html">&lt;section id="snowflake-and-dask-a-python-connector-for-faster-data-transfer"&gt;

&lt;p&gt;&lt;strong&gt;Snowflake&lt;/strong&gt; is a leading cloud data platform and SQL database. Many companies store their data in a Snowflake database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dask&lt;/strong&gt; is a leading framework for scalable data science and machine learning in Python.&lt;/p&gt;
&lt;p&gt;This article discusses why and how to use both together, and dives into the challenges of bulk parallel reads and writes into data warehouses, which is necessary for smooth interoperability. It presents the new dask-snowflake connector as the best possible solution for fast, reliable data transfer between Snowflake and Python.&lt;/p&gt;
&lt;p&gt;Here is a video walkthrough of what that looks like:&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/pinFo1YBD-0" title="Dask Snowflake Integration | Tutorial on Snowflake and Dask | Richard Pelgrim" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;section id="motivation"&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Data warehouses are great at SQL queries but less great at more advanced operations, like machine learning or the free-form analyses provided by general-purpose languages like Python.&lt;/p&gt;
&lt;p&gt;That’s ok. If you’re working as a Python data scientist or data engineer, you’re probably no stranger to extracting copies of data out of databases and using Python for complex data transformation and/or analytics.&lt;/p&gt;
&lt;p&gt;It’s common to use a database and/or data warehouse to filter and join different datasets, and then pass that result off to Python for more custom computations. We use each tool where it excels. This works well as long as it is easy to perform the handoff of large results.&lt;/p&gt;
&lt;p&gt;However, as we work on larger datasets and our results grow beyond the scale of a single machine, passing results between a database and a general purpose computation system is challenging.&lt;/p&gt;
&lt;p&gt;This article describes a few ways in which we can move data between Snowflake and Dask today to help explain why this is both hard and important to do well, namely:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Use pandas and the Snowflake connector&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Break your query up into subqueries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Perform a bulk export to Apache Parquet&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At the end, we’ll preview the new functionality, which provides the best possible solution, and show an example of use.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="three-ways-to-pass-data-from-snowflake-to-dask"&gt;
&lt;h2&gt;Three ways to pass data from Snowflake to Dask&lt;/h2&gt;
&lt;p&gt;There are different ways to accomplish this task, each with different benefits and drawbacks.  By looking at all three, we’ll gain a better understanding of the problem, and what can go wrong.&lt;/p&gt;
&lt;section id="just-use-pandas"&gt;
&lt;h3&gt;1. Just use Pandas&lt;/h3&gt;
&lt;p&gt;First, if your data is small enough to fit in memory then you should just use pandas and Python.  There is no reason to use a distributed computing framework like Dask if you don’t need it.&lt;/p&gt;
&lt;p&gt;Snowflake publishes a Snowflake Python connector with pandas compatibility.  The following should work fine:&lt;/p&gt;
&lt;div class="highlight-shell notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;snowflake-connector-python
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;snowflake&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snowflake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch_pandas_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Snowflake Dask" src="https://docs.coiled.io/_images/63867ce54255d6bde81e66f5_Snowflake%2520Dask%2520-%2520pros%2520and%2520cons.jpg" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="break-into-many-subqueries"&gt;
&lt;h3&gt;2. Break into many subqueries&lt;/h3&gt;
&lt;p&gt;For larger datasets, we can also break one large table-scan query into many smaller queries and then run those in parallel to fill the partitions of a Dask dataframe.&lt;/p&gt;
&lt;p&gt;This is the approach implemented in the &lt;a class="reference external" href="https://docs.dask.org/en/latest/dataframe-api.html#dask.dataframe.read_sql_table"&gt;dask.dataframe.read_sql_table&lt;/a&gt; function. This function does the following steps:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Query the length and schema of the table to determine the expected size, and determine an ideal number of partitions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Query for the minimum and maximum of a column on which to split&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Submit many small queries that segment that column linearly between the minimum and maximum values.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, if we’re reading from a time series then we might submit several queries which each pull off a single month of data.  These each lazily return a pandas dataframe.  We construct a Dask dataframe from these lazily generated subqueries.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_sql_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;accounts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="s1"&gt;&amp;#39;snowflake://user:pass@...warehouse=...role=...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;index_col&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This scales and works with any database that supports a SQLAlchemy Python connector (which Snowflake does).  However, there are a few problems:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;It only works for very simple queries, really just full table scans that can optionally select down columns and filter out rows.  For example, you couldn’t run a big join query and then feed that result to Dask without first writing out to a temporary table&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It’s kinda slow.  SQLAlchemy/ODBC connectors are rarely well optimized (see &lt;a class="reference external" href="https://turbodbc.readthedocs.io/en/latest/"&gt;Turbodbc&lt;/a&gt; for a good counter-example)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It’s inconsistent.  Those subqueries aren’t guaranteed to run at the same time, and so if other processes are writing to your database while you’re reading you could get incorrect/inconsistent results.  In some applications, this doesn’t matter.  In some, it matters a great deal&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Snowflake Dask 2" src="https://docs.coiled.io/_images/63867d2cc3e1fc7395544936_Snowflake%2520Dask%2520-%2520pros%2520and%2520cons%25202.jpg" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;For more information see this guide to &lt;a class="reference external" href="https://coiled.io/blog/getting-started-with-dask-and-sql/"&gt;Getting Started with Dask and SQL&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="bulk-export-to-parquet"&gt;
&lt;h3&gt;3. Bulk Export to Parquet&lt;/h3&gt;
&lt;p&gt;We can also perform a bulk export.  Both Snowflake and Dask (and really any distributed system these days) can read and write Parquet data on cloud object stores.  So we can perform a query with Snowflake and then write the output to Parquet, and then read in that data with Dask dataframe.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Snowflake Dask 3" src="https://docs.coiled.io/_images/63587d9db3c62b5abacab6a1_image1-1-700x632.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;snowflake&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;COPY&lt;/span&gt; &lt;span class="n"&gt;INTO&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;s3://my_storage_location&amp;#39;&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;file_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aws_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xxxx&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;aws_secret_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xxxxx&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;aws_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xxxxxx&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;con&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snowflake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;XXXX&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;passwoard&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;XXXX&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;XXXX&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;curson&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3://my_storage_location&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is great because now we can perform large complex queries and export those results at high speed to Dask in a fully consistent way.  All of the challenges of the previous approach are removed.&lt;/p&gt;
&lt;p&gt;However, this creates new challenges with data management.  We now have two copies of our data.  One in Snowflake and one in S3.  This is technically suboptimal in many ways (cost, storage, …) but the major flaw here is that of data organization.  Inevitably these copies persist over time and get passed around the organization.  This results in many copies of old data in various states of disrepair.  We’ve lost all the benefits of centralized data warehousing, including cost efficiency, correct results, data security, governance, and more.&lt;/p&gt;
&lt;p&gt;Bulk exports provide performance at the cost of the organization.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Although note, users interested in this option could also look into &lt;a class="reference external" href="https://docs.snowflake.com/en/user-guide/data-unload-s3.html#unloading-data-into-an-external-stage"&gt;Snowflake Stages&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="new-managed-bulk-transfers"&gt;
&lt;h2&gt;New: Managed Bulk Transfers&lt;/h2&gt;
&lt;p&gt;Recently, Snowflake has added a capability for staged bulk transfers to external computation systems like Dask.  It combines the raw performance and support for complex queries of bulk exports, with the central management of directly reading SQL queries from the database.&lt;/p&gt;
&lt;p&gt;Using this, we’re able to provide an excellent Snowflake &amp;lt;-&amp;gt; Dask data movement tool.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="understanding-snowflake-s-parallel-data-exports"&gt;
&lt;h2&gt;Understanding Snowflake’s Parallel Data Exports&lt;/h2&gt;
&lt;p&gt;Now Snowflake can take the result of any query, and stage that result for external access.&lt;/p&gt;
&lt;p&gt;After running the query, Snowflake gives us back a list of pointers to chunks of data that we can read. We can then send pointers to our workers and they can directly pull out those chunks of data. This is very similar to the bulk export option described above, except that now rather than us being responsible for that new Parquet-on-S3 dataset, Snowflake manages it. This gives us all of the efficiency of a bulk export, while still letting Snowflake maintain control of the resulting artifact. Snowflake can clean up that artifact for us automatically so that we’re always going back to the database as the single source of truth as we should.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="snowflake-dask-integration"&gt;
&lt;h2&gt;Snowflake + Dask Integration&lt;/h2&gt;
&lt;p&gt;Given this new capability, we then wrapped Dask around it, resulting in the following experience:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_snowflake&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;snowflake&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;snowflake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dask_snowflake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_snowflake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
      &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;TableA&lt;/span&gt; &lt;span class="n"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;TableB&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;,&lt;/span&gt;
      &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is a smooth and simple approach that combines the best of all previous methods.  It is easy to use, performs well, and maturely handles any query.&lt;/p&gt;
&lt;p&gt;For more details on how to use distributed XGBoost training with Dask, see &lt;a class="reference external" href="https://github.com/coiled/dask-xgboost-nyctaxi"&gt;this GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/WJ5yamxarfM" title="Snowflake &amp; Dask - Miles Adkins, James Bourbeau, Mark Keller | PyData Global 2021" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="snowflake-dask-fine-print"&gt;
&lt;h2&gt;Snowflake/Dask Fine Print&lt;/h2&gt;
&lt;p&gt;There are a few things to keep in mind:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;We recommend using Dask on the same cloud and region as your Snowflake deployment for optimal speed and to avoid data ingress/egress charges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The performance of your Dask queries will be impacted by the size and concurrency settings of your Snowflake Warehouse.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your driving Python session (where you’re importing dask-snowflake) needs to be properly authenticated with Snowflake.  Your remote workers do not.  Dask-snowflake doesn’t require any additional considerations for authentication beyond what you do normally.  However, because authentication information is included in the messages sent to each worker we encourage you to ensure that you use secure network connections.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Snowflake Python connector has some known issues on Apple M1 machines. See &lt;a class="reference external" href="https://github.com/snowflakedb/snowflake-connector-python/issues/986"&gt;this Github issue&lt;/a&gt; for more details.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section id="give-it-a-spin"&gt;
&lt;h2&gt;Give it a spin!&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/main/blogs/snowflake-and-dask.ipynb"&gt;Download the dask-snowflake Jupyter Notebook to get started.&lt;/a&gt; If you run into any issues or have thoughts about further improvements to the package, come find us on &lt;a class="reference external" href="https://dask.discourse.group/"&gt;the Dask Discourse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The technical work described in this post was done by various engineers at Snowflake, and James Bourbeau and Florian Jetter from Coiled.&lt;/em&gt; &lt;em&gt;Watch James Bourbeau’s talk at PyData Global 2021 together with Mark Keller and Miles Adkinds from Snowflake for a detailed walkthrough.&lt;/em&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/snowflake-and-dask.html"/>
    <summary>Snowflake is a leading cloud data platform and SQL database. Many companies store their data in a Snowflake database.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/stages-of-openness.html</id>
    <title>Seven Stages of Open Software</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="seven-stages-of-open-software"&gt;

&lt;p&gt;This post lays out the different stages of openness in Open Source Software (OSS) and the benefits and costs of each.&lt;/p&gt;
&lt;section id="motivation"&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;“Open Source Software” is a hot term today.&lt;/p&gt;
&lt;p&gt;As a result, people are reasonably encouraged to open up their software. This is great, but means that the term “open source” can get a bit confusing. Is Linux as open as TensorFlow? How about my personal project? Is that the same?&lt;/p&gt;
&lt;p&gt;To help give depth to this topic, this post structures opening software into a sequence of stages of openness.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Publicly visible source code:&lt;/strong&gt; &lt;em&gt;We uploaded our code to GitHub&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Licensed for reuse:&lt;/strong&gt; &lt;em&gt;And let people use it for free&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accepting contributions:&lt;/strong&gt; &lt;em&gt;And if they submit a patch, we’ll take the time to look at it, and work with them to merge it in&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open development:&lt;/strong&gt; &lt;em&gt;And when we work we’ll make sure that all of our communication happens in the open as well, so that others can see what we’re doing and why&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open decision making:&lt;/strong&gt; &lt;em&gt;And that communication will be open to the public, so that everyone can weigh in, vote, and determine what happens to the project&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-institution engagement:&lt;/strong&gt; &lt;em&gt;So much so that no single institution or individual has control over the project&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retirement:&lt;/strong&gt; &lt;em&gt;So now we can retire, and know that the software will live on forever&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To be clear, I’m not advocating that going deeper into this hierarchy is a good thing. Often it’s more productive to stop somewhere around 3 to 5 (this is what most companies do). There are costs and benefits to every layer. We’ll now go into each layer more deeply.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="publicly-visible-source-code"&gt;
&lt;h2&gt;1 - Publicly visible source code&lt;/h2&gt;
&lt;p&gt;Technically all that “open sourcing” your code means is that you’ve made the source code publicly visible. This might be throwing a tarball up on some website, but today it mostly means pushing a repository to GitHub.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is great because your code is now auditable. For example if your code handles personal data or communications people can see what is happening internally to gain confidence that everything you’re doing is secure and ethical. Or if your code runs some complex mathematical algorithm your users can inspect inside your black box and see why things behave the way that they do.&lt;/p&gt;
&lt;p&gt;Opening up your code creates trust among your users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But of course if you are trying to make money on this software then your competition can now gain insight.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="licensed-for-reuse"&gt;
&lt;h2&gt;2 - Licensed for reuse&lt;/h2&gt;
&lt;p&gt;Publishing your code doesn’t actually legally enable anyone to use it. Code is still legally protected by copyright unless the author provides a license. For proprietary software you purchase this license. Often for open source software a license is distributed with the software which lays out the ways in which anyone can use it without ever asking you for permission.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yay, free things&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You no longer control this software. If someone makes a bunch of money off it it then you can’t complain. If someone uses it to build bombs then you also can’t complain.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="accepting-contributions"&gt;
&lt;h2&gt;3 - Accepting Contributions&lt;/h2&gt;
&lt;p&gt;So you get an e-mail, and someone who you’ve never met has found and fixed a bug for you. Great, right?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;People do work for you for free. Assuming that you depend on this software this is great, you’re getting free engineering time and a new perspective.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As this happens more and more it becomes a major time sink. You now have to educate these bug fixers, integrate all of their work, and make sure that things stay on the right track. Often you will have to reject contributions and handle the fallout of that.&lt;/p&gt;
&lt;p&gt;These activities require a level of social engineering that you may not be familiar with or want.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="open-development"&gt;
&lt;h2&gt;4 - Open development&lt;/h2&gt;
&lt;p&gt;You spend enough time educating the people contributing to your code that you realize that it would be better if your internal team started having all of their conversations in the open as well. You push them away from in-person conversations (this is hard), out of the company Slack (this is harder), and force everyone to have all of your conversations in the open on public and searchable forums like GitHub or mailing lists.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yay transparency!&lt;/p&gt;
&lt;p&gt;The community outside of your organization now has much more insight on why certain choices have been made. They really appreciate this and it builds a lot of trust around your product. Additionally, this attracts developers of a higher calibre to the project because they’re now able to operate with the full context.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is really hard to do. It’s also pretty inefficient in the short term, especially if you already have good internal communication practices. Fully open and public communication is really time consuming to do correctly.&lt;/p&gt;
&lt;p&gt;You’ll also be fighting an uphill battle to get your teammates to move off of internal communication, particularly if the rest of the company is there.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="open-decision-making"&gt;
&lt;h2&gt;5 - Open decision making&lt;/h2&gt;
&lt;p&gt;Now that you’ve opened development, you have attracted some developers who fully understand the project, and have thoughts on where it should go. You listen to them and make sure that the aggregate needs are met, and not just the needs of your company. Sometimes you disagree with them, and sometimes they win these disagreements. This ends up being mostly a good thing.&lt;/p&gt;
&lt;p&gt;You give them commit rights, the ability to push releases, and generally the ability to change things that you might not agree with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You get additional developer power and perspective at a completely different level. This isn’t just the occasional bugfix, this is people pouring their work-life into your project, and using it to do things that you never thought possible.&lt;/p&gt;
&lt;p&gt;Other people in the community see that, and stop seeing your project as just a bit of corporate software, but as something that might be infrastructure some day.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You’ve given up a critical amount of control. Certainly, you still have the ability to make changes and make sure that the software meets your needs, but now you may have to negotiate from time to time.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="multi-institution-engagement"&gt;
&lt;h2&gt;6 - Multi-institution engagement&lt;/h2&gt;
&lt;p&gt;Those core maintainers grow to the point where they dwarf your original team of contributors. There are now multiple companies/universities/institutions that build and maintain this software. You’ve given up the majority of control in order to enable a large and diverse workforce.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This thing has grown in maturity and scale beyond what you could have predicted&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Costs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It may not grow in the way that you want. You no longer have control&lt;/p&gt;
&lt;/section&gt;
&lt;section id="retirement"&gt;
&lt;h2&gt;7 - Retirement&lt;/h2&gt;
&lt;p&gt;You can die now. Your software will survive without you :)&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I wrote this in response to a variety of people saying “Oh, we open sourced our software”&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/stages-of-openness.html"/>
    <summary>This post lays out the different stages of openness in Open Source Software (OSS) and the benefits and costs of each.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-set-index-dataframe.html</id>
    <title>Setting a Dask DataFrame index</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="setting-a-dask-dataframe-index"&gt;

&lt;p&gt;This post demonstrates how to change a DataFrame index with set_index and explains when you should perform this operation.&lt;/p&gt;
&lt;p&gt;Setting an index can make downstream queries faster, but usually requires an expensive data shuffle, so it should be used strategically.&lt;/p&gt;
&lt;p&gt;This post demonstrates the set_index syntax on small datasets and then shows some query benchmarks on larger datasets.&lt;/p&gt;
&lt;section id="what-is-an-index"&gt;
&lt;h2&gt;What is an index?&lt;/h2&gt;
&lt;p&gt;Indexes are used by normal pandas DataFrames, Dask DataFrames, and many databases in general.&lt;/p&gt;
&lt;p&gt;Indexes let you efficiently find rows that have a certain value, without having to scan each row. In plain pandas, it means that after a set_index(“col”), df.loc[“foo”] is faster than df[df.col == “foo”] was before. The loc uses an efficient data structure that only has to check a couple rows to figure out where “foo” is. Whereas df[df.col == “foo”] has to scan every single row to see which ones match.&lt;/p&gt;
&lt;p&gt;The thing is, computers are really really fast at scanning memory, so when you’re running pandas computations on one machine, index optimizations aren’t as important.&lt;/p&gt;
&lt;p&gt;But scanning memory across many distributed machines is not fast. So index optimizations that you don’t notice much with pandas makes an enormous difference with Dask.&lt;/p&gt;
&lt;p&gt;A Dask DataFrame is composed of many pandas DataFrames, potentially living on different machines, each one of which we call a &lt;em&gt;partition&lt;/em&gt;. See the Dask docs for an &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe.html#design"&gt;illustration&lt;/a&gt; and &lt;a class="reference external" href="https://docs.dask.org/en/stable/dataframe-design.html#partitions"&gt;in-depth explanation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Dask client has its own version of an index for the distributed DataFrame as a whole, called divisions. divisions is like an index for the indexes—it tracks which &lt;em&gt;partition&lt;/em&gt; will contain a given value (just like pandas’s index tracks which &lt;em&gt;row&lt;/em&gt; will contain a given value). When there are millions of rows spread across hundreds of machines, it’s too much to track every single row. We just want to get in the right ballpark—which machine will hold this value?—and then tell that machine to go find the row itself.&lt;/p&gt;
&lt;p&gt;So divisions is just a simple list giving the lower and upper bounds of values that each partition contains. Using this, Dask does a quick binary search locally to figure out which partition contains a given value.&lt;/p&gt;
&lt;p&gt;Just like with a pandas index, having known divisions let us change a search that would scan every row (df[df.col == “foo”]) to one that quickly goes straight to the right place (df.loc[“foo”]). It’s just that scanning every row is much, much slower in a distributed context with Dask than on one machine with plain pandas.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="when-to-set-an-index"&gt;
&lt;h2&gt;When to set an index&lt;/h2&gt;
&lt;p&gt;Setting an index in Dask is a lot slower than setting an index in pandas.  You shouldn’t always set indexes in Dask like you do in pandas.&lt;/p&gt;
&lt;p&gt;In pandas, you can get away with calling set_index even when it’s not necessary. Sometimes, you’ll see pandas code like set_index(“foo”).reset_index() in the wild, which sorts the data for no reason. In pandas, you can get away with this, because it’s cheap. In Dask it’s very, very not cheap. So you really should understand what set_index does and why you’re doing it before you use it.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="set-index-syntax"&gt;
&lt;h2&gt;set_index syntax&lt;/h2&gt;
&lt;p&gt;Let’s create a small DataFrame and set one of the columns as the index to gain some intuition about how Dask leverages an index.&lt;/p&gt;
&lt;p&gt;Create a pandas DataFrame with two columns of data, and a 2-partition Dask DataFrame from it.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;col1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aa&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;cc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;bb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;aa&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;col2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Print the DataFrame and see that it has one index column that was created by default by pandas and two columns with data.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="n"&gt;col1&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;   &lt;span class="n"&gt;aa&lt;/span&gt;   &lt;span class="n"&gt;a1&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="n"&gt;dd&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="n"&gt;cc&lt;/span&gt;    &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;   &lt;span class="n"&gt;bb&lt;/span&gt;    &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;   &lt;span class="n"&gt;aa&lt;/span&gt;   &lt;span class="n"&gt;a2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Take a look at the divisions of ddf.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;ddf has two divisions.  Let’s create a print_partitions() helper method and print ddf to better illustrate the divisions.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_partitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
         &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;print_partitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;col1&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;   &lt;span class="n"&gt;aa&lt;/span&gt;   &lt;span class="n"&gt;a1&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="n"&gt;dd&lt;/span&gt;    &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="n"&gt;cc&lt;/span&gt;    &lt;span class="n"&gt;c&lt;/span&gt;
  &lt;span class="n"&gt;col1&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;   &lt;span class="n"&gt;bb&lt;/span&gt;    &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;   &lt;span class="n"&gt;aa&lt;/span&gt;   &lt;span class="n"&gt;a2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The first partition contains values with indexes from 0 to 2.  The second division contains values with indexes 3 and above.&lt;/p&gt;
&lt;p&gt;Create a new DataFrame with col1 as the index and print the result.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;col1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;divisions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aa&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;cc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;span class="n"&gt;col1&lt;/span&gt;  &lt;span class="n"&gt;col2&lt;/span&gt;     
&lt;span class="n"&gt;aa&lt;/span&gt;     &lt;span class="n"&gt;a1&lt;/span&gt;
&lt;span class="n"&gt;aa&lt;/span&gt;     &lt;span class="n"&gt;a2&lt;/span&gt;
&lt;span class="n"&gt;bb&lt;/span&gt;      &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="n"&gt;cc&lt;/span&gt;      &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="n"&gt;dd&lt;/span&gt;      &lt;span class="n"&gt;d&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s confirm that the divisions of ddf2 are set as we specified.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;aa&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;cc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dd&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s look at how the data is distributed in ddf2 by partition.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;print_partitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;col1&lt;/span&gt;  &lt;span class="n"&gt;col2&lt;/span&gt;     
&lt;span class="n"&gt;aa&lt;/span&gt;     &lt;span class="n"&gt;a1&lt;/span&gt;
&lt;span class="n"&gt;aa&lt;/span&gt;     &lt;span class="n"&gt;a2&lt;/span&gt;
&lt;span class="n"&gt;bb&lt;/span&gt;      &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="n"&gt;col1&lt;/span&gt;  &lt;span class="n"&gt;col2&lt;/span&gt;     
&lt;span class="n"&gt;cc&lt;/span&gt;      &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="n"&gt;dd&lt;/span&gt;      &lt;span class="n"&gt;d&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice that the set_index method sorted the DataFrame based on col1.  Take note of two important changes after set_index is run:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;The divisions of ddf2 are now set as specified&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The data is repartitioned, as per our division specifications. bb has moved to the first partition, and dd to the second, because that’s what our divisions specified.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeated values are all in the same partition. That is, both aa’s are in the first partition.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sorting a Dask DataFrame can be a slow computation, see &lt;a class="reference external" href="https://coiled.io/blog/better-shuffling-in-dask-a-proof-of-concept/"&gt;this blog post for more detail&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You will only want to run set_index when you can leverage it to make downstream queries faster.&lt;/p&gt;
&lt;p&gt;Let’s look at a big dataset and see how much faster some queries execute when an index is set.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="reading-dataframes-with-index"&gt;
&lt;h2&gt;Reading DataFrames with index&lt;/h2&gt;
&lt;p&gt;Let’s grab a few rows of data from a 662 million row DataFrame when filtering on the index.  Then, let’s run the same query when the index isn’t set to quantify the performance speedup offered by the index.&lt;/p&gt;
&lt;p&gt;Read in the DataFrame using a 5 node Coiled cluster and verify that the timestamp is the index.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;demo-cluster&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distributed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
     &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Image" src="https://docs.coiled.io/_images/633f7b5f9ab3551ed39bcd65_image-700x332.png" /&gt;
&lt;p&gt;Let’s take a look at what Dask knows about the divisions of this DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;known_divisions&lt;/span&gt;

&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can print out all the divisions too if you’d like to inspect them.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2000-01-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2000-01-08 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2000-01-15 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2000-01-22 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2000-01-29 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2000-02-05 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This DataFrame has 1,096 divisions.&lt;/p&gt;
&lt;p&gt;Here’s a query that’ll grab three records from the DataFrame and takes 2 seconds to execute.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2000-01-01 00:00:02&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2000-01-01 00:00:04&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Read in the same dataset without an index, so we can see how much slower the same query runs on a regular column that’s not the index.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ddf2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;timestamp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2000-01-01 00:00:02&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;timestamp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2000-01-01 00:00:04&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;That query takes 115 seconds to execute because it had to load and scan all of the data looking for those values, instead of jumping straight to the correct partition. The query without the index is 58 times slower in this example.&lt;/p&gt;
&lt;p&gt;Filtering on an index value is clearly a lot quicker than filtering on non-index columns.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-long-does-it-take-to-set-the-index"&gt;
&lt;h2&gt;How long does it take to set the index?&lt;/h2&gt;
&lt;p&gt;Let’s look at a query to compute the number of unique names in the data when the id equals 1001. We’ll see if setting an index can improve the performance of the query.&lt;/p&gt;
&lt;p&gt;Start by reading in the data and running a query to establish a baseline.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
     &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ddf3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nunique&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query takes 75 seconds to execute.&lt;/p&gt;
&lt;p&gt;dd3 has 1,095 partitions, which you can see by running ddf3.npartitions.&lt;/p&gt;
&lt;section id="setting-an-index-before-running-the-query"&gt;
&lt;h3&gt;Setting an index before running the query&lt;/h3&gt;
&lt;p&gt;Let’s set an index and run the same query to see if that’ll make the query run faster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;dask_computed_divisions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt;
&lt;span class="n"&gt;unique_divisions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromkeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dask_computed_divisions&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unique_divisions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 149&lt;/span&gt;
&lt;span class="n"&gt;ddf3u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;divisions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;unique_divisions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf3u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt; &lt;span class="c1"&gt;# 148&lt;/span&gt;
&lt;span class="n"&gt;ddf3u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nunique&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query takes 130 seconds to run. Setting the index before running the query is slower than simply running the query. It’s not surprising that this runs slower. Setting the index requires a data sort, which is expensive.&lt;/p&gt;
&lt;p&gt;This example just shows one way you can create the divisions that are used when the index is set. You can create the divisions however you’d like. Choosing the optimal divisions to make downstream queries faster is challenging.&lt;/p&gt;
&lt;p&gt;It’s not surprising that this particular index does not improve the overall query time: we’re sorting &lt;em&gt;all&lt;/em&gt; of the values, but we only needed to extract just one. It’s faster to just scan the un-sorted data and find the single value we’re interested in.&lt;/p&gt;
&lt;p&gt;Let’s look at some more examples to gather some intuition and then look and when setting an index is more likely to improve query times.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="setting-an-index-without-specifying-divisions"&gt;
&lt;h3&gt;Setting an index without specifying divisions&lt;/h3&gt;
&lt;p&gt;Let’s look at setting an index without explicitly setting divisions and running the same count query.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf3a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 134 seconds&lt;/span&gt;
&lt;span class="n"&gt;ddf3a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nunique&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 118 seconds&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This takes a total of 252 seconds to compute and is slower than the original query. In this case, setting an index doesn’t help our query run faster.&lt;/p&gt;
&lt;p&gt;Notice that ddf3.set_index(“id”) does not specify divisions, so Dask needs to go figure them out. To do this, it has to load and compute all of ddf3 immediately to look at the distribution of its values. Then, when you later call .compute(), it’ll load and compute ddf3 a second time. This is slow in general, and particularly bad if the DataFrame already has lots of operations applied to it—all those operations also have to run twice.&lt;/p&gt;
&lt;p&gt;When divisions are passed, Dask doesn’t need to compute the whole DataFrame to figure them out, which is obviously a lot faster.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="setting-an-index-and-specifying-divisions-poorly"&gt;
&lt;h3&gt;Setting an index and specifying divisions poorly&lt;/h3&gt;
&lt;p&gt;Now let’s try to manually set the divisions and run the same query.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf3b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;divisions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;# 0.3 seconds&lt;/span&gt;
&lt;span class="n"&gt;ddf3b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nunique&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# didn&amp;#39;t work&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This didn’t work because we’re taking a DataFrame that used to have 1,095 partitions and trying to smash all that data into just 4 partitions. This runs out of memory. Clearly, using bad divisions is worse than the extra time for Dask to compute them for you.&lt;/p&gt;
&lt;p&gt;Not setting the divisions well can cause problems. You want to pick divisions that:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Separate the data into approximately equally-sized groups. If some values are more common than others, adjust partitions accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Result in reasonably-sized partitions—around 100MB is typically a good target, which often means row counts around 100,000 per partition.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To pick good divisions, you must use your knowledge of the dataset. What range of values is there for the column? What sort of general distribution do they follow—a normal bell curve, a continuous distribution? Are there known outliers?&lt;/p&gt;
&lt;p&gt;Another strategy is to let Dask compute the divisions once, then copy-paste them to reuse later:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;dask_computed_divisions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt;
&lt;span class="n"&gt;unique_divisions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromkeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dask_computed_divisions&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unique_divisions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# ^ copy this and reuse&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is especially helpful if you’ll be rerunning a script or notebook on the same (or similar) data many times.&lt;/p&gt;
&lt;p&gt;However, you shouldn’t set divisions if the data you’re processing is very unpredictable. In that case, it’s better to spend the extra time and let Dask re-compute good divisions each time.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="high-level-index-strategies"&gt;
&lt;h2&gt;High level index strategies&lt;/h2&gt;
&lt;p&gt;Since set_index is an expensive operation, you should only run the computation when it’ll help subsequent computations run faster.&lt;/p&gt;
&lt;p&gt;Here are the main operations that’ll run faster when an index is set:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Filtering on the index&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Joining on the index&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Groupby on the index (soon! &lt;a class="github reference external" href="https://github.com/dask/dask/pull/8442"&gt;dask/dask#8442&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sophisticated custom code in map_partitions (advanced use case)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By all means, set indexes whenever it’ll make your analysis faster. Just don’t run these expensive computations unnecessarily.&lt;/p&gt;
&lt;p&gt;For example, you shouldn’t &lt;em&gt;always&lt;/em&gt; set_index before you .loc. If you just need to pull a value out once, it’s not worth the cost of a whole shuffle. But if you need to pull lots of values out, then it is. Same with a merge: if you’re just merging a DataFrame to another, don’t set_index first (the merge will do this internally anyway). But if you’re merging the same DataFrame multiple times, then the set_index is worth it.&lt;/p&gt;
&lt;p&gt;As a rule of thumb, you should only set_index if you’ll do a merge, groupby(df.index), or .loc on the re-indexed DataFrame &lt;strong&gt;more than once&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You may also want to re-index your data before writing it to storage in a partitioned format like Parquet. That way, when you read the data later, it’s already partitioned the way you want, and you don’t have to re-index it every time.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;set_index changes the index of a DataFrame, sorts the underlying data, and ensures the data is partitioned in a manner consistent with the divisions. Divisions allow for you to cheaply look up values without having to process every partition to search for them.&lt;/p&gt;
&lt;p&gt;Sorting is an expensive operation when data is distributed, but it can be worth it if you’re performing multiple operations that run faster on data with an index.&lt;/p&gt;
&lt;p&gt;You may also want to sort data before persisting it on disk, so Dask can read it with an index and you don’t need to perform the sort later.&lt;/p&gt;
&lt;p&gt;Keep this powerful performance optimization in mind when you’re performing analyses with Dask.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-set-index-dataframe.html"/>
    <summary>This post demonstrates how to change a DataFrame index with set_index and explains when you should perform this operation.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/grubhub-science-thursday.html</id>
    <title>Search at Grubhub and User Intent</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="search-at-grubhub-and-user-intent"&gt;

&lt;p&gt;Alex Egg, Senior Data Scientist at Grubhub, joins Matt Rocklin and Hugo Bowne-Anderson to talk and code about how Dask and distributed compute are used throughout the user intent classification pipeline at Grubhub!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; Search is the top-of-funnel at Grubhub. That means when a user interacts with the Grubhub search engine, they want to be able to service their request with high precision and recall. One way to do that is to understand the intent of the user. Do they have a favourite restaurant in mind or are they just browsing cuisines? Are they looking for an obscure dish? Depending on the intent of the user, Grubhub can optimize the backend query or apply UI treatments to help the user find just what they had in mind. In this session, we’ll take a look at how to build an end-to-end search query intent classifier from data labeling to serving live requests in production.&lt;/p&gt;
&lt;p&gt;We’ll cover:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Classic ETL pipelines with Dask Dataframes,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Weak-supervision (labeling) w/ Snorkel &amp;amp; Dask (what a modern combo!),&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Language Modeling and deployment w/ Tensorflow.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/_jqZzN2JRxQ" title="Search at Grubhub and User Intent: Dask, Snorkel, and TensorFlow!" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/grubhub-science-thursday.html"/>
    <summary>Alex Egg, Senior Data Scientist at Grubhub, joins Matt Rocklin and Hugo Bowne-Anderson to talk and code about how Dask and distributed compute are used throughout the user intent classification pipeline at Grubhub!</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/scikit-learn-cloud-coiled.html</id>
    <title>Scikit-learn in the Cloud with Coiled</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="scikit-learn-in-the-cloud-with-coiled"&gt;

&lt;p&gt;You can train scikit-learn models in parallel using the &lt;a class="reference external" href="https://scikit-learn.org/stable/computing/parallelism.html#higher-level-parallelism-with-joblib"&gt;scikit-learn joblib interface&lt;/a&gt;. This allows scikit-learn to take full advantage of the multiple cores in your machine (or, spoiler alert, on your cluster) and speed up training.&lt;/p&gt;
&lt;p&gt;Using the Dask joblib backend, you can maximize parallelism by scaling your scikit-learn model training out to a remote cluster.&lt;/p&gt;
&lt;p&gt;This works for all scikit-learn models that expose the &lt;em&gt;n_jobs&lt;/em&gt; keyword, like random forests, linear regressions, and other machine learning algorithms. You can also use the joblib library for distributed hyperparameter tuning with grid search cross-validation.&lt;/p&gt;
&lt;section id="scikit-learn-models-vs-data-size"&gt;
&lt;h2&gt;Scikit-learn Models vs Data Size&lt;/h2&gt;
&lt;p&gt;When training machine learning models, you can run into 2 types of scalability issues: your model size may get too large, or your data size may start to cause issues (or even worse: both!). You can typically recognize a scalability problem related to your model size by long training times: the computation will complete (eventually), but it’s becoming a bottleneck in your data processing pipeline. This means you’re in the top-left corner of the diagram below:&lt;/p&gt;
&lt;img alt="Unnamed image" src="https://docs.coiled.io/_images/633f7b61761c5f00ce2a8360_unnamed.png" /&gt;
&lt;p&gt;Using the scikit-learn joblib integration, you can address this problem by processing your scikit-learn models in parallel, distributed over the multiple cores in your machine.&lt;/p&gt;
&lt;p&gt;Note that this is only a good solution if your training data and test data fit in memory comfortably. If this is not the case (and you’re in the “I give up!” quadrant of large models and large datasets), see the “Dataset Too Large?” section below to learn how Dask-ML or XGBoost-on-Dask can help you out.&lt;/p&gt;
&lt;img alt="Sklearn joblib" src="https://docs.coiled.io/_images/633f7b61761c5f1f022a835f_unnamed-2-1.png" /&gt;
&lt;/section&gt;
&lt;section id="sklearn-joblib-run-random-forest-in-parallel-locally"&gt;
&lt;h2&gt;Sklearn + Joblib: Run Random Forest in Parallel Locally&lt;/h2&gt;
&lt;p&gt;Let’s see this in action with an scikit-learn algorithm that is embarrassingly parallel: a random forest model. A random forest model fits a number of decision tree classifiers on sub-samples of the training data , then combines the results from the various decision trees to make a prediction. Because each decision tree is an independent process, this model can easily be trained in parallel.&lt;/p&gt;
&lt;p&gt;First, we’ll create a new virtual environment for this work. (If you’re following along, you can also use pip or any other package manager, or your own existing environment.)&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;conda&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;sklearn-example&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;conda-forge&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9&lt;span class="w"&gt; &lt;/span&gt;scikit-learn&lt;span class="w"&gt; &lt;/span&gt;joblib&lt;span class="w"&gt; &lt;/span&gt;ipython
conda&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;sklearn-example
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’ll import the necessary libraries and create a synthetic classification dataset.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.ensemble&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RandomForestClassifier&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.datasets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_classification&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_classification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_samples&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;n_informative&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_redundant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then we’ll instantiate our Random Forest classifier&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;clf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RandomForestClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We can then use a joblib context manager to train this classifier in parallel, specifying the number of cores we want to use with the n_jobs argument.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;joblib&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;joblib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallel_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;loky&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To save users from having to use context managers every time they want to train a model in parallel, scikit-learn’s developers exposed the n_jobs argument as part of the instantiating call:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;clf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RandomForestClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The n_jobs keyword communicates to the joblib backend, so you can directly call clf.fit(X, y) without wrapping it in a context manager. &lt;strong&gt;This is the recommended approach for using joblib to train sklearn models in parallel locally.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Running this locally with n_jobs = -1 on a MacBook Pro with 8 cores and 16GB of RAM takes just under 3 minutes.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CPU&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;17.8&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt; &lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;Wall&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="sklearn-joblib-run-rf-on-the-cluster"&gt;
&lt;h2&gt;Sklearn + Joblib: Run RF on the Cluster&lt;/h2&gt;
&lt;p&gt;The context manager syntax can still come in handy when your model size increases beyond the capabilities of your local machine, or if you want to train your model even faster. In those situations, you could consider scaling out to remote clusters on the cloud. This can be especially useful if you’re running heavy grid search cross-validation, or other forms of hyperparameter tuning.&lt;/p&gt;
&lt;p&gt;You can use the Dask joblib backend to delegate the distributed training of your model to a Dask cluster of virtual machines in the cloud.&lt;/p&gt;
&lt;p&gt;We first need to install some additional packages:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;conda&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;conda-forge&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;dask&lt;span class="w"&gt; &lt;/span&gt;xgboost&lt;span class="w"&gt; &lt;/span&gt;dask-ml
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Next, we’ll launch a Dask cluster of 20 machines in the cloud.&lt;/p&gt;
&lt;p&gt;When you create the cluster, Coiled will automatically replicate your local &lt;em&gt;sklearn-example&lt;/em&gt; environment in your cluster (see &lt;a class="reference external" href="https://docs.coiled.io/user_guide/package_sync.html"&gt;Package Sync&lt;/a&gt;).&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’ll then connect Dask to our remote cluster. This will ensure that all future Dask computations are routed to the remote cluster instead of to our local cores.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# point Dask to remote cluster&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now use the joblib context manager to specify the Dask backend. This will delegate all model training to the workers in your remote cluster:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;joblib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallel_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dask&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Model training is now occurring in parallel on your remote Dask cluster.&lt;/p&gt;
&lt;p&gt;This runs in about a minute and a half on my machine—that’s a 2x speed-up. You could make this run even faster by adding more workers to your cluster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;CPU&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="mf"&gt;1.93&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;601&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.53&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;Wall&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can also use your Dask cluster to run an extensive hyperparameter grid search that would be too heavy to run on your local machine:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.model_selection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GridSearchCV&lt;/span&gt;

&lt;span class="c1"&gt;# Create a parameter grid&lt;/span&gt;
&lt;span class="n"&gt;param_grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;bootstrap&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;max_depth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;max_features&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;min_samples_leaf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;min_samples_split&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;n_estimators&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Instantiate the grid search model&lt;/span&gt;
&lt;span class="n"&gt;grid_search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GridSearchCV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;estimator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;param_grid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;param_grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Before we execute this compute-heavy Grid Search, let’s just scale up our cluster to 100 workers to speed up training and ensure we don’t run into memory overloads:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_for_workers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now let’s execute the grid search in parallel:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;joblib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallel_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dask&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;grid_search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="dataset-too-large"&gt;
&lt;h2&gt;Dataset too large?&lt;/h2&gt;
&lt;p&gt;Is your dataset becoming too large, for example because you have acquired new data? In that case, you may be experiencing two scalability issues at once: both your model and your dataset are becoming too large. You typically notice an issue with your dataset size by pandas throwing a MemoryError when trying to run a computation over the entire dataset.&lt;/p&gt;
&lt;img alt="Sklearn joblib" src="https://docs.coiled.io/_images/633f7b61761c5f37b22a8361_unnamed-3.png" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; exists precisely to solve this problem. Using Dask, you can scale your existing Python data processing workflows to larger-than-memory datasets. You can use Dask DataFrames or Dask Arrays to process data that is too large for pandas or NumPy to handle.&lt;/p&gt;
&lt;p&gt;Dask also scales machine learning for larger-than-memory datasets. Use &lt;a class="reference external" href="https://ml.dask.org/"&gt;dask-ml&lt;/a&gt; or the distributed &lt;a class="reference external" href="https://xgboost.readthedocs.io/en/stable/tutorials/dask.html"&gt;Dask backend to XGBoost&lt;/a&gt; to train machine learning models on data that doesn’t fit into memory. Dask-ML is a library that contains parallel implementation of many scikit-learn models.&lt;/p&gt;
&lt;p&gt;The code snippet below demonstrates training a Logistic Regression model on larger-than-memory data in parallel.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_ml.linear_model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LogisticRegression&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_ml.datasets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_classification&lt;/span&gt;

&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_classification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LogisticRegression&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask also integrates natively with XGBoost to train XGBoost models in parallel:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xgboost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xgb&lt;/span&gt;

&lt;span class="c1"&gt;# create DMatrix&lt;/span&gt;
&lt;span class="n"&gt;dtrain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xgb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DaskDMatrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# train model&lt;/span&gt;
&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xgb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtrain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_boost_round&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;evals&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;dtrain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;train&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="scikit-learn-joblib-summary"&gt;
&lt;h2&gt;Scikit-learn + Joblib Summary&lt;/h2&gt;
&lt;p&gt;We’ve seen that:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;You can use the scikit-learn’s joblib integration to distribute certain scikit-learn tasks over all the cores in your machine for a faster runtime.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can connect joblib to the Dask backend to scale out to a remote cluster for even faster processing times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can use XGBoost-on-Dask and/or dask-ml for distributed machine learning training on datasets that don’t fit into local memory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/scikit-learn-cloud-coiled.html"/>
    <summary>You can train scikit-learn models in parallel using the scikit-learn joblib interface. This allows scikit-learn to take full advantage of the multiple cores in your machine (or, spoiler alert, on your cluster) and speed up training.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/scale-your-data-science-workflows-with-python-and-dask.html</id>
    <title>Scale your data science workflows with Python and Dask</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="scale-your-data-science-workflows-with-python-and-dask"&gt;

&lt;p&gt;Data Scientists are increasingly using Python and the Python ecosystem of tools for their analysis. Combined with the growing popularity of big data, this brings the challenge of scaling data science workflows. Dask is a library built for this exact purpose - making it easy to scale your Python code, and serve as a toolbox for distributed computing!&lt;/p&gt;
&lt;p&gt;In this post, we will discuss:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Why use Python for Data Science?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why is there a need for scalability?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How does Dask make scaling simple, and where is it used?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="python-for-data-science"&gt;
&lt;h2&gt;Python for Data Science&lt;/h2&gt;
&lt;p&gt;Python’s wide adoption is attributed to three key factors:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;It has a more &lt;em&gt;readable and human-friendly syntax&lt;/em&gt; when compared to a lot of other programming languages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It has a &lt;em&gt;free and open source foundation&lt;/em&gt;, which allows more people to learn, teach, and improve the language.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It has an &lt;em&gt;active and growing community&lt;/em&gt; around it. There are numerous supporting tools and libraries that make Python one of the most powerful languages to use, including for data science.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Python has made Data Science more accessible to everyone from researchers to analysts to students:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Libraries like NumPy, pandas, scikit-learn, XGBoost, Matplotlib, etc. are free and open source alternatives to proprietary data science tools like MATLAB.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tools like Dask also help reduce reliance on high-cost infrastructure like supercomputers for scalability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Jupyter ecosystem makes collaboration easy by providing executable and shareable environments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New specialized libraries are being developed everyday, adding more layers of abstraction, and speaking the language of a specific community like Astropy for Astrophysics, SunPy for Solar physics, Biopython for computational biology, and many more.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Scientific Python community is growing rapidly, hence we also have a growing pool of resources to support new users!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="scaling-data-science-workflows"&gt;
&lt;h2&gt;Scaling Data Science Workflows&lt;/h2&gt;
&lt;p&gt;Recent advancements in technology, algorithms, and computational power has led to a “big data revolution”. Data scientists require scalable solutions to work with these large and complex datasets, and they look towards parallel and distributed computing.&lt;/p&gt;
&lt;p&gt;Scalable computing can be anything that extends beyond a single thread on a single core. Parallel computing is the process of computing multiple tasks simultaneously to accelerate workflows, and distributed computing is the process of leveraging computing resources of multiple machines.&lt;/p&gt;
&lt;img alt="Scale Data Science" src="https://docs.coiled.io/_images/633f7b569ab3553e1e9bcd42_architecture-1024x631.png" /&gt;
&lt;p&gt;Another important concept in scalable computing is cloud computing. Providers like AWS, Azure, and GCP, provide a lot of computing resources that can be accessed from anywhere. Cloud computing involves leveraging these resources to perform big computations.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="scaling-made-simpler-with-dask"&gt;
&lt;h2&gt;Scaling made simpler with Dask&lt;/h2&gt;
&lt;p&gt;Dask is a library for parallel and distributed computing in Python. Dask makes it easy to use all resources on your local machine, set up distributed computing environments, as well as scale to the cloud. It’s familiar API, flexible design, and synergy with the Python ecosystem make Dask the tool of choice for both individuals and institutions.&lt;/p&gt;
&lt;p&gt;Dask is used by industry leaders from Walmart and Capital One, to government agencies like NASA and the UK Met Office, among many others to scale their data science and machine learning pipelines. Dask is also used behind-the-scenes in many popular tools including Nvidia RAPIDS, Apache Airflow, and PyTorch!&lt;/p&gt;
&lt;p&gt;Dask is a very powerful library, but scaling Dask computations to the cloud involves many DevOps challenges like networking, security, and environment configurations. Coiled Cloud is a product built by the creators and maintainers of Dask, that takes care of these challenges, so that data scientists can focus on their analysis. Coiled lets you scale to the cloud in just one-click, while also providing essential features for team and cost management.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/scale-your-data-science-workflows-with-python-and-dask.html"/>
    <summary>Data Scientists are increasingly using Python and the Python ecosystem of tools for their analysis. Combined with the growing popularity of big data, this brings the challenge of scaling data science workflows. Dask is a library built for this exact purpose - making it easy to scale your Python code, and serve as a toolbox for distributed computing!</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/save-money-with-spot.html</id>
    <title>Save Money with Spot</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="save-money-with-spot"&gt;

&lt;p&gt;The cloud is wonderful but expensive.&lt;/p&gt;
&lt;p&gt;Spot/preemptible instances offer dramatic cost savings (2–3x), but only if you can…&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Find enough in your region&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handle their sudden disappearance&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Doing this well requires considerable nuance which we explore in this post.&lt;/p&gt;
&lt;p&gt;We optimized this process while making Coiled, a cloud SaaS product around deploying Python distributed computing with Dask. We’ll use Coiled/Dask workloads as a running example, but these lessons should apply broadly.&lt;/p&gt;
&lt;section id="spot-is-cheap"&gt;
&lt;h2&gt;Spot is Cheap&lt;/h2&gt;
&lt;p&gt;Both AWS and Google Cloud let you buy idle compute resources at a significant discount. For example, here are the costs for a 2vCPU 8GiB machine on each cloud:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Spot vs OnDemand Chart" src="https://docs.coiled.io/_images/63b73b6da9392c4a847b8558_Spot%2520vs%2520OnDeman%2520Chart.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Spot instances are about a quarter the cost… so what’s the catch?&lt;/p&gt;
&lt;p&gt;Because spot instances are excess capacity, &lt;strong&gt;they can be harder to get and harder to keep&lt;/strong&gt;. Example issues:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;You might request 100 spot workers and only get 80&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You might get 100, but after your cluster is up and running you might lose some&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For some workloads, this is fine. For others, less so. Which raises the question:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;How can we get as much spot as possible, while also delivering a consistent user experience?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After iterating on this problem with our users, we’ve come up with an approach that we’ve found to work well:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Select the best zone for getting spot instances&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use on-demand instances when spot is unavailable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatically replace spot instances with changes in cloud provider availability&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you spin up a cluster using Coiled, you can now enable these features with &lt;a class="reference external" href="https://docs.coiled.io/user_guide/costs/spot.html"&gt;these keyword arguments&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="language-python"&gt;&lt;code class="language-python hljs"&gt;&lt;span class="hljs-keyword"&gt;import&lt;/span&gt; coiled

cluster = coiled.Cluster(
    use_best_zone=&lt;span class="hljs-literal"&gt;True&lt;/span&gt;, 
    spot_policy=&lt;span class="hljs-string"&gt;"spot_with_fallback"&lt;/span&gt;,
    ...,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;section id="choose-the-right-availability-zone"&gt;
&lt;h2&gt;1 – Choose the right availability zone&lt;/h2&gt;
&lt;p&gt;Before we worry about spot/preemptible instances going away, first we need to find enough spot. To do this well, we want to choose the data center or availability zone with the most excess capacity.&lt;/p&gt;
&lt;p&gt;Clouds (AWS, GCP, Azure) have lots of data centers. These data centers are grouped into “&lt;strong&gt;zones&lt;/strong&gt;”, and one or more zones make up a “&lt;strong&gt;region&lt;/strong&gt;”. Usually you care about what &lt;strong&gt;region&lt;/strong&gt; you use, because the sorts of workloads that people run on Coiled usually involve processing lots of data, and moving data between &lt;strong&gt;regions&lt;/strong&gt; costs money.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Spot Savings Cloud Diagram" src="https://docs.coiled.io/_images/63b744967f3626915157c631_Spot%2520Savings%2520-%2520Cloud%2520Diagram.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;You should also care that all of your instances are in a &lt;strong&gt;single zone&lt;/strong&gt;, because moving data between zones also costs money, but usually you don’t need to care which &lt;strong&gt;specific zone&lt;/strong&gt; you use. Data is typically stored in a region and not a specific zone within that region.&lt;/p&gt;
&lt;p&gt;Different availability zones have different capacity. This applies to Spot, and also other rare instance types like GPUs and TPUs. By being flexible to the right availability zone we can often get more of the thing we want, while still being local to our data.&lt;/p&gt;
&lt;section id="example"&gt;
&lt;h3&gt;Example&lt;/h3&gt;
&lt;p&gt;We applied this approach to internal workloads, large scale Dask benchmarks run by our in-house OSS teams. These regular jobs spin up many hundreds of workers for benchmarking Dask performance at scale. In the first two weeks of December, we spun up &lt;strong&gt;over 52000&lt;/strong&gt; m6i.large spot workers. Out of all those requests for workers, there were exactly &lt;strong&gt;60&lt;/strong&gt; times when we failed to get the spot instance but would have been able to get the worker as an on-demand instance.&lt;/p&gt;
&lt;p&gt;As availability shifted, you can see that these workers came from different AWS availability zones:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Image 3" src="https://docs.coiled.io/_images/63b607ac0681a7842c28c666_image3.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Prior to this feature, we didn’t even use Spot. Consistency in these benchmarks was important enough to our internal teams, and the experience was poor enough that we just paid the higher bill. &lt;em&gt;Since implementing this feature (and those below) we’ve heard no complaints from engineering, and nothing but praise from finance&lt;/em&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="spot-with-fallback-to-on-demand"&gt;
&lt;h2&gt;2 – Spot with Fallback to On-Demand&lt;/h2&gt;
&lt;p&gt;Even when choosing the best zone, sometimes you still hit limits. This is especially common when you want many hundreds of instances, or when you want very large instances, or when you want GPUs. All of these have limited spot availability.&lt;/p&gt;
&lt;p&gt;Sometimes this is fine. Sometimes it isn’t. Sometimes you need things to run predictably with the requested number of workers at the requested time. Our solution to this is to use spot but fall back to using on-demand if spot isn’t available. We offer three different policies&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Spot: If you’re very cost conscious and never want to pay the full price for your compute, we’ll only give you spot instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spot-with-fallback: good if you’re using spot instances but it’s also important to you that you get clusters of a certain size and are willing to pay full price as necessary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On-demand: which we’ve pretty much stopped using, but which you could use if you want to be very very sure that everything is stable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ve switched our internal Dask benchmarking to use spot instances with fallback. As I mentioned before, we get spot instances about 99.9% of the time—but for the remaining 0.1% we get on-demand fallback instances so that there’s no impact on our benchmarks from the ups and downs of spot availability.&lt;/p&gt;
&lt;p&gt;On-demand is currently the default, we’re strongly considering making spot-with-fallback the default.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="instance-replacement"&gt;
&lt;h2&gt;3 – Instance replacement&lt;/h2&gt;
&lt;p&gt;Are there any downsides to using spot with fallback compared to using only on-demand instances?&lt;/p&gt;
&lt;p&gt;Unfortunately there are.&lt;/p&gt;
&lt;p&gt;Spot instances aren’t just harder to get, they’re also harder to keep—the cloud provider can “interrupt” (AWS) or “preempt” (Google Cloud) your instances when they need to free up availability for other customers.&lt;/p&gt;
&lt;p&gt;So how do we address this?&lt;/p&gt;
&lt;p&gt;Coiled relies on Dask for scheduling work on your cluster. Dask’s architecture is to have a single scheduler (we run this on its own on-demand instance) and potentially very many workers (this is where you get scale). The scheduler needs to stay up for the entire duration of your workload, but Dask is resilient to workers coming and going—this makes it easy to scale up and down in response to changes in your workload &lt;em&gt;or changes in cloud provider instance availability.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We &lt;strong&gt;always use on-demand for the scheduler&lt;/strong&gt; even when there’s available spot capacity. On the one hand, that means you’re paying full price for the scheduler instance. But on the other hand, the scheduler rarely needs to be a large instance—for our benchmarking on AWS we use an m6i.large—and there’s only one scheduler, whereas there are usually many workers.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;workers&lt;/strong&gt; we watch for advanced notification that a spot worker is going to be terminated, and we attempt to then gracefully shut it down so that computed results can be transferred to other workers. At the same time, we start spinning up another instance—which may be a spot instance, but if none are available and you’ve enabled fallback, it may be an on-demand instance. Dask is able to handle worker replacement midstream, so your workloads are able to keep running.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="smooth-user-experience-for-cost-savings"&gt;
&lt;h2&gt;Smooth User Experience for Cost Savings&lt;/h2&gt;
&lt;p&gt;Coiled makes it easy to quickly and reliably spin up clusters in the cloud to run your Python code in parallel at scale. Because we remove the hassle of manually provisioning and configuring servers and infrastructure, it’s trivial for you to get a cluster with hundreds of cores and terabytes of memory that you might only need for a few minutes.&lt;/p&gt;
&lt;p&gt;Before making the changes above, we found that while spot provided substantial cost savings in theory, it wasn’t really usable. Users kept not getting the machines they needed, or their workloads would crash unpredictably. In practice folks stopped using spot and kept on with on-demand instances. Today folks are pretty happy. We use the default configuration in our own testing suites and our internal bills have gone down. Same with many of our customers.&lt;/p&gt;
&lt;p&gt;You can see &lt;a class="reference external" href="https://docs.coiled.io/user_guide/costs/spot.html"&gt;our docs&lt;/a&gt; for the relevant keyword arguments when creating Coiled clusters.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/save-money-with-spot.html"/>
    <summary>The cloud is wonderful but expensive.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/repartition-dataframe.html</id>
    <title>Repartitioning Dask DataFrames</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="repartitioning-dask-dataframes"&gt;

&lt;p&gt;This article explains how to redistribute data among partitions in a Dask DataFrame with repartitioning…&lt;/p&gt;
&lt;p&gt;Dask DataFrames consist of partitions, each of which is a pandas DataFrame. Dask performance will suffer if there are lots of partitions that are too small or some partitions that are too big. Repartitioning a Dask DataFrame solves the issue of “partition imbalance”.&lt;/p&gt;
&lt;p&gt;Let’s start with some simple minimal complete verifiable examples (&lt;a class="reference external" href="https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports"&gt;MCVE)&lt;/a&gt; repartition examples to get you familiar with the repartition syntax.&lt;/p&gt;
&lt;p&gt;We’ll then illustrate the performance gains from repartitioning a 63 million row dataset. This example demonstrates how the small file problem causes a computation to run 18 times slower and how this can be fixed with repartitioning.&lt;/p&gt;
&lt;section id="simple-examples"&gt;
&lt;h2&gt;Simple examples&lt;/h2&gt;
&lt;p&gt;Let’s create a Dask DataFrame with six rows of data organized in three partitions.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;letters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Print the content of each DataFrame partition.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;     &lt;span class="mi"&gt;1&lt;/span&gt;       &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;     &lt;span class="mi"&gt;2&lt;/span&gt;       &lt;span class="n"&gt;b&lt;/span&gt;
 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;     &lt;span class="mi"&gt;3&lt;/span&gt;       &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;     &lt;span class="mi"&gt;4&lt;/span&gt;       &lt;span class="n"&gt;d&lt;/span&gt;
 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;     &lt;span class="mi"&gt;5&lt;/span&gt;       &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="mi"&gt;6&lt;/span&gt;       &lt;span class="n"&gt;f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Repartition the DataFrame into two partitions.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;     &lt;span class="mi"&gt;1&lt;/span&gt;       &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;     &lt;span class="mi"&gt;2&lt;/span&gt;       &lt;span class="n"&gt;b&lt;/span&gt;
 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;     &lt;span class="mi"&gt;3&lt;/span&gt;       &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;     &lt;span class="mi"&gt;4&lt;/span&gt;       &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;     &lt;span class="mi"&gt;5&lt;/span&gt;       &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="mi"&gt;6&lt;/span&gt;       &lt;span class="n"&gt;f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;repartition(2) causes Dask to combine partition 1 and partition 2 into a single partition. Dask’s repartition algorithm is smart to coalesce existing partitions and avoid full data shuffles.&lt;/p&gt;
&lt;p&gt;You can also increase the number of partitions with repartition. Repartition the DataFrame into 5 partitions.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;     &lt;span class="mi"&gt;1&lt;/span&gt;       &lt;span class="n"&gt;a&lt;/span&gt;
 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;     &lt;span class="mi"&gt;2&lt;/span&gt;       &lt;span class="n"&gt;b&lt;/span&gt;
 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;     &lt;span class="mi"&gt;3&lt;/span&gt;       &lt;span class="n"&gt;c&lt;/span&gt;
 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;     &lt;span class="mi"&gt;4&lt;/span&gt;       &lt;span class="n"&gt;d&lt;/span&gt;
 &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;     &lt;span class="mi"&gt;5&lt;/span&gt;       &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="mi"&gt;6&lt;/span&gt;       &lt;span class="n"&gt;f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In practice, it’s easier to repartition by specifying a target size for each partition (e.g. 100 MB per partition). You want Dask to do the hard work of figuring out the optimal number of partitions for your dataset. Here’s the syntax for repartitioning into 100MB partitions.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;100MB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="example-of-small-file-problem"&gt;
&lt;h2&gt;Example of small file problem&lt;/h2&gt;
&lt;p&gt;Let’s illustrate the small file problem and then explain how repartitioning can eliminate an excessive number of small files.&lt;/p&gt;
&lt;p&gt;Suppose you have two years of data, one row per second.&lt;/p&gt;
&lt;p&gt;That’s 63 million rows of data.&lt;/p&gt;
&lt;p&gt;Let’s run a query on a 5 node Dask cluster powered by Coiled.&lt;/p&gt;
&lt;p&gt;When the data is stored in 104 CSV files (41 MB each), it takes 16.6 seconds to get the number of unique values in the name column.&lt;/p&gt;
&lt;p&gt;When the data is stored in 17,496 tiny CSV files (248 KB each), it takes a whopping 4 minutes and 16 seconds to perform the same computation. &lt;em&gt;Having too many files causes the computation to run 15 times slower!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When the tiny CSV files are repartitioned into 100 partitions and written out as Parquet files, then the count operation can be performed in 4 seconds. Tiny files should be eliminated via repartitioning, so Dask can perform computations more efficiently.&lt;/p&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/master/repartitioning-dataframes/benchmarks.ipynb"&gt;this notebook&lt;/a&gt; for the full benchmarking analysis with computations to support the results. Here’s &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/main/envs/standard-coiled.yml"&gt;the conda environment&lt;/a&gt; you can use to run the notebook, as described in the project &lt;a class="reference external" href="https://github.com/coiled/coiled-resources"&gt;README&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="when-to-repartition"&gt;
&lt;h2&gt;When to repartition&lt;/h2&gt;
&lt;p&gt;Of course, repartitioning isn’t free and takes time. The cost of performing a full data shuffle can outweigh the benefits of subsequent query performance.&lt;/p&gt;
&lt;p&gt;You shouldn’t always repartition whenever a dataset is imbalanced. Repartitioning should be approached on a case-by-case basis and only performed when the benefits outweigh the costs.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="common-causes-of-partition-imbalance"&gt;
&lt;h2&gt;Common causes of partition imbalance&lt;/h2&gt;
&lt;p&gt;Filtering is a common cause of DataFrame partition imbalance.&lt;/p&gt;
&lt;p&gt;Suppose you have a DataFrame with a first_name column and the following data:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Partition 0: Everyone has a first_name “Allie”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Partition 1: Everyone has first_name “Matt”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Partition 2: Everyone has first_name “Sam”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you filter for all the rows with first_name equal to “Allie”, then Partition 1 and Partition 2 will be empty. Empty partitions cause inefficient Dask execution. It’s often wise to repartition after filtering.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;This post has shown you how to fix DataFrame partition imbalance with repartitioning.&lt;/p&gt;
&lt;p&gt;Repartitioning can be costly because it requires data to be shuffled. The overall performance gains from repartitioning should be measured on a case-by-case basis. The performance gains from having data evenly distributed across DataFrame partitions may be outweighed by the cost of performing the shuffle.&lt;/p&gt;
&lt;p&gt;You’re well equipped to determine when it’s best to run this important performance optimization after learning about repartitioning in this post.&lt;/p&gt;
&lt;p&gt;Thanks for reading. If you’re interested in trying out Coiled, which provides hosted Dask clusters, docker-less managed software, and one-click deployments, you can do so for free today when you click below.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/repartition-dataframe.html"/>
    <summary>This article explains how to redistribute data among partitions in a Dask DataFrame with repartitioning…</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/reducing-dask-memory-usage.html</id>
    <title>Reducing memory usage in Dask workloads by 80%</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="reducing-memory-usage-in-dask-workloads-by-80"&gt;

&lt;p&gt;There’s a saying in emergency response: “slow is smooth, smooth is fast”.&lt;/p&gt;
&lt;p&gt;That saying has always bothered me, because it doesn’t make sense at first, yet it’s entirely correct.&lt;/p&gt;
&lt;p&gt;By applying this philosophy to the scheduling algorithm in the latest release of Dask (2022.11.0), &lt;strong&gt;we’re seeing common workloads use up to 80% less memory than before. This means some workloads that used to be outright un-runnable are now running smoothly&lt;/strong&gt;—an infinity-X speedup!&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Hero image" src="https://docs.coiled.io/_images/636d7784ba3e9065fa319741_hero.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;Cluster memory use on common workloads—blue is the latest release. The blue lines are lower by a factor of “a lot”.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To see this in action, here’s a demo by Florian from last month’s Dask Demo Day:&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/VlTgcLqb1DQ?start=757" title="Dask Demo Day - 2022-10-27" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/dask/distributed/issues/2602"&gt;The second-most upvoted and commented issue of all time&lt;/a&gt; on the dask/distributed repo describes, “tasks early in my graph generate data faster than it can be consumed downstream, causing data to pile up, eventually overwhelming my workers”.&lt;/p&gt;
&lt;p&gt;Dask users often struggle with workloads that run out of memory like this. Studying these situations, we realized that the Dask scheduler wasn’t following this “slow is smooth, smooth is fast” adage.&lt;/p&gt;
&lt;p&gt;Here’s what the problem was, and &lt;a class="reference external" href="https://github.com/dask/distributed/pull/6614"&gt;how we’ve addressed it&lt;/a&gt;:&lt;/p&gt;
&lt;section id="root-task-overproduction"&gt;
&lt;h2&gt;Root task overproduction&lt;/h2&gt;
&lt;p&gt;Historically, Dask tried hard to get each individual task done as fast as possible: if a task &lt;em&gt;could&lt;/em&gt; run, it &lt;em&gt;would&lt;/em&gt;. So sometimes, tasks would run even if their outputs weren’t going to be used immediately—leaving them sitting around in memory.&lt;/p&gt;
&lt;p&gt;If you had thousands of initial tasks loading data—say, fetching Parquet from S3, CSVs from disk, or rows from a database—all those tasks would be scheduled and sent to workers up front.&lt;/p&gt;
&lt;p&gt;The workers would churn through them, fetching chunks of data (and accumulating it in memory) as quickly as possible. A worker would tell the scheduler when each chunk was loaded, and the scheduler would respond with what to do with it next—but until that message arrived, there were more data-loading tasks runnable right now, so why not run them?&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Overproduction" src="https://docs.coiled.io/_images/636d733d2f19c63b2e0d5eb8_overproduction.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;This slight gap in timing—between the worker immediately starting on a less-useful task, then only later finding out about a more-useful task it should have run instead—allowed this lower-priority data to pile up in memory. We call this “root task overproduction”.&lt;/p&gt;
&lt;p&gt;Overall, there could be at least twice as much initial data in memory at once as necessary—and, therefore, twice as many intermediate results. (See &lt;a class="reference external" href="https://github.com/dask/distributed/pull/6614#discussion_r956515223"&gt;this comment&lt;/a&gt; for a detailed explanation of why the 2x happens.)&lt;/p&gt;
&lt;p&gt;When this put workers under memory pressure, this initial problem would snowball into a bigger one. Workers had to spill data to disk (slow), then read it back from disk to use it or transfer it (slow). Workers might exceed their memory limits and crash, losing progress and requiring tasks to be recomputed on a pool of workers that were already struggling.&lt;/p&gt;
&lt;p&gt;In the end, this meant that a whole class of workloads were slow, or even un-runnable, without using hugely oversized clusters.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="queuing"&gt;
&lt;h2&gt;Queuing&lt;/h2&gt;
&lt;p&gt;There were plenty of ways to approach this problem, but we wanted to try the simplest thing first: just don’t tell the workers about more tasks than they can run at once.&lt;/p&gt;
&lt;p&gt;We’re calling this mode of scheduling &lt;a class="reference external" href="https://distributed.dask.org/en/stable/scheduling-policies.html#queuing"&gt;“queuing”&lt;/a&gt;, or “root task withholding”. The scheduler puts data-loading tasks in an internal queue, and only drips one out to a worker once it’s finished its current work &lt;em&gt;and&lt;/em&gt; there’s nothing more useful to run instead that utilizes the work it just completed.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Dashboard" src="https://docs.coiled.io/_images/636d73d89811138543c33fd9_dashboard.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="slow-is-smooth"&gt;
&lt;h2&gt;Slow is smooth&lt;/h2&gt;
&lt;p&gt;Queuing adds a cost in latency. Every time workers finish a task, they have to ask the scheduler what to do next and sit under-utilized, or even idle, until they get an answer. (Before, they had a backlog of things they could do before the answer came back.)&lt;/p&gt;
&lt;p&gt;For a while, we hadn’t considered this approach, because intuitively, we assumed the latency would be too much of a slow-down.&lt;/p&gt;
&lt;p&gt;However, by slowing down the pace of task assignment, and running only the best tasks, scheduling gets much smoother. And with that smoothness, we see that most benchmark workloads use much less memory across the board:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Memory benchmarks" src="https://docs.coiled.io/_images/636d747a519468f4c6ab3f0f_memory-benchmarks.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;Percent decrease in peak memory use in the latest release. Notice the axes: up to 80% reduction.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This large a reduction in memory use is a big deal!&lt;/p&gt;
&lt;p&gt;For many users, this might mean that workloads that weren’t runnable before can now run smoothly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;By setting worker saturation, I can reliably calculate a trend over time for the first time 🎉&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;– &lt;em&gt;Climate scientist Julius Busecke &lt;a class="reference external" href="https://github.com/dask/distributed/discussions/7128#discussioncomment-3964014"&gt;reporting&lt;/a&gt; that a common geoscience task which used to always crash now works out of the box.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Some workloads will also be cheaper to run in the cloud, since they can use instances with less memory. We see some benchmarks that could, in theory, be run for 30-50% less total cost. This is not universal: others would cost &lt;em&gt;more&lt;/em&gt; because they get slower. More on that later.&lt;/p&gt;
&lt;p&gt;Beyond this, execution is just more predictable. Memory usage is much more consistent and less likely to spike rapidly:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Constant memory" src="https://docs.coiled.io/_images/636d7619a2c0f9eb3673953a_constant-memory.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="smooth-is-fast"&gt;
&lt;h2&gt;Smooth is fast&lt;/h2&gt;
&lt;p&gt;In a few cases, it turns out that smooth scheduling can be even faster.&lt;/p&gt;
&lt;p&gt;On average, one representative oceanography workload ran 20% faster. A few other workloads showed modest speedups as well. This is mostly because they no longer load too much data into memory and then have to spill it to disk, which creates significant slowdowns.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;We have immediately seen 30% reduction in runtime and thus cost, 60% reduction in peak memory usage, and significantly more reasonable and consistent task assignment&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;– &amp;#64;roe246 &lt;a class="reference external" href="https://github.com/dask/distributed/discussions/7128#discussioncomment-4140076"&gt;reporting&lt;/a&gt; on a DataFrame workload.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Additionally, we found that the extra latency we were worried about didn’t actually slow things down in typical cases. There was no measurable change in pure task throughput on a cluster with fast networking and multi-CPU workers, like &lt;a class="reference external" href="https://coiled.io/"&gt;Coiled clusters&lt;/a&gt; or &lt;a class="reference external" href="https://docs.dask.org/en/latest/deploying-python.html"&gt;a single-machine LocalCluster&lt;/a&gt;. This was a good lesson in trying the simplest thing first.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="sometimes-slow-is-still-slow-but-not-why-you-d-expect"&gt;
&lt;h2&gt;Sometimes, slow is still slow (but not why you’d expect)&lt;/h2&gt;
&lt;p&gt;However, we did notice that a few benchmarks run slower with scheduler-side queuing. The typical slowdown is 5-10%, but in the extreme case, they are ~50% slower (though they also use about half the memory).&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Slow memory profiles" src="https://docs.coiled.io/_images/636d788e3ba6630bf4ec1104_slow-memory-profiles.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;These cases use less memory now (blue), but take longer.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The problem is that implementing queuing meant giving up a scheduling feature &lt;a class="reference external" href="https://github.com/dask/distributed/pull/4967"&gt;introduced last year&lt;/a&gt; called &lt;em&gt;co-assignment&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;As described &lt;a class="reference external" href="https://distributed.dask.org/en/stable/scheduling-policies.html#initial-task-placement"&gt;in the docs&lt;/a&gt;, co-assignment tries to schedule initial tasks on the same worker if their outputs will be combined later. This avoids having to transfer data from one worker to another when the downstream task runs, because all the data is already on one worker.&lt;/p&gt;
&lt;p&gt;In a graph like this, we’d want a and b to run on the same worker. Otherwise, one of e or f would have to be transferred between workers before i can run (read graph bottom-up):&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;  &lt;span class="n"&gt;i&lt;/span&gt;       &lt;span class="n"&gt;j&lt;/span&gt;
 &lt;span class="o"&gt;/&lt;/span&gt; \     &lt;span class="o"&gt;/&lt;/span&gt; \
&lt;span class="n"&gt;e&lt;/span&gt;   &lt;span class="n"&gt;f&lt;/span&gt;   &lt;span class="n"&gt;g&lt;/span&gt;   &lt;span class="n"&gt;h&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;   &lt;span class="n"&gt;b&lt;/span&gt;   &lt;span class="n"&gt;c&lt;/span&gt;   &lt;span class="n"&gt;d&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Avoiding these transfers speeds things up, because network is &lt;a class="reference external" href="https://medium.com/&amp;#64;hondanhon/more-latency-numbers-every-programmer-should-know-3142f0cf614d"&gt;relatively slow&lt;/a&gt;. It also reduces memory usage by avoiding having to hold replicas of the same data on multiple workers.&lt;/p&gt;
&lt;p&gt;Unfortunately, the current implementation of co-assignment isn’t compatible with queuing, and updating it is non-trivial. We plan to do this next, to get the best of both worlds.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="a-new-default-for-scheduling"&gt;
&lt;h2&gt;A new default for scheduling&lt;/h2&gt;
&lt;p&gt;In the short term, we had to decide if queuing was beneficial enough to be worth enabling by default right away, despite the loss of co-assignment.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://observablehq.com/&amp;#64;gjoseph92/snakebench?commits=2d37536&amp;amp;amp;commits=f6ef40b&amp;amp;amp;commits=cfe91dd&amp;amp;amp;measure=peak_memory&amp;amp;amp;groupby=branch&amp;amp;amp;show=passed"&gt;After running a number of benchmarks&lt;/a&gt;, and getting some &lt;a class="reference external" href="https://github.com/dask/distributed/discussions/7128"&gt;initial community feedback&lt;/a&gt;, we think it is.&lt;/p&gt;
&lt;p&gt;Queuing makes things possible that used to not work at all. But it doesn’t break anything that works today: everything will still work, some things just could be slower. We feel that that’s a worthwhile enough tradeoff to enable it by default—especially given how much Dask users have struggled with memory issues.&lt;/p&gt;
&lt;p&gt;Additionally, to avoid impacting workloads that could become latency-bound, the new algorithm is still doing a &lt;em&gt;little&lt;/em&gt; overproduction. It’s pushing a handful of extra root tasks to the worker in advance (as opposed to all of them, like before). This comes at the price of some extra memory use, but prevents painful slowdowns in high-latency clusters.&lt;/p&gt;
&lt;p&gt;So in the latest release (2022.11.0), queuing is enabled by default. Most memory-intensive Array and DataFrame workloads should see reductions in memory use out of the box, ranging from noticeable to 🤩.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="let-us-know-how-it-goes"&gt;
&lt;h2&gt;Let us know how it goes&lt;/h2&gt;
&lt;p&gt;We’ve opened a &lt;a class="reference external" href="https://github.com/dask/distributed/discussions/7128"&gt;discussion on GitHub&lt;/a&gt; for feedback on this change. Please let us know how it helps (or doesn’t).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="keeping-old-behavior"&gt;
&lt;h2&gt;Keeping old behavior&lt;/h2&gt;
&lt;p&gt;For users who are sensitive to runtime and have low memory use, you can deactivate queuing and use the old scheduling mode (including co-assignment) via &lt;a class="reference external" href="https://docs.dask.org/en/stable/configuration.html"&gt;Dask configuration&lt;/a&gt;, by setting the new &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;distributed.scheduler.worker-saturation&lt;/span&gt;&lt;/code&gt; config value to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;inf&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can read more about adjusting this setting &lt;a class="reference external" href="https://distributed.dask.org/en/latest/scheduling-policies.html#adjusting-or-disabling-queuing"&gt;in the docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On Coiled, you can set it with:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;distributed.scheduler.worker-saturation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;inf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}):&lt;/span&gt;
    &lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# coiled sends current dask config automatically&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;You can see examples of setting this configuration for various deployment systems on the &lt;a class="reference external" href="https://github.com/dask/distributed/discussions/7128"&gt;discussion issue&lt;/a&gt; (when copy-pasting, be sure to change the 1.0 to inf!). And if you find the need to set worker-saturation back to inf, please let us know on the discussion.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="acknowledgements"&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Much credit goes to &lt;a class="reference external" href="https://github.com/tomnicholas"&gt;Tom Nicholas&lt;/a&gt; for his collaboration and persistence in &lt;a class="reference external" href="https://github.com/dask/distributed/issues/6360"&gt;advocating for this problem&lt;/a&gt;, developing &lt;a class="reference external" href="https://github.com/dask/distributed/issues/6571"&gt;minimal reproducers for it&lt;/a&gt;, and testing out multiple iterations of fixes on his real scientific workloads (he even gave a &lt;a class="reference external" href="https://speakerdeck.com/tomnicholas/scipy-2022-can-we-analyse-the-largest-ocean-simulation-ever"&gt;talk&lt;/a&gt; at SciPy about why this matters!). Similarly, &lt;a class="reference external" href="https://github.com/rabernat"&gt;Ryan Abernathy&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/JSKenyon"&gt;&amp;#64;JSKenyon&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/dougiesquire"&gt;Dougie Squire&lt;/a&gt;, and &lt;a class="reference external" href="https://github.com/TomAugspurger"&gt;Tom Augspurger&lt;/a&gt; all (unwittingly, years ago) provided &lt;a class="reference external" href="https://github.com/dask/distributed/issues/2602"&gt;example code&lt;/a&gt; used in validating this work. This was a great example of the power of open-source!&lt;/p&gt;
&lt;p&gt;Additionally, thanks to the many people who tested out the new setting and gave feedback &lt;a class="reference external" href="https://github.com/dask/distributed/discussions/7128"&gt;on GitHub&lt;/a&gt;, plus the whole team at Coiled who collaborated to get this feature over the line.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/reducing-dask-memory-usage.html"/>
    <summary>There’s a saying in emergency response: “slow is smooth, smooth is fast”.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-dtype-astype.html</id>
    <title>Reduce memory usage with Dask dtypes</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="reduce-memory-usage-with-dask-dtypes"&gt;

&lt;p&gt;Columns in Dask DataFrames are typed, which means they can only hold certain values (e.g. integer columns can’t hold string values). This post gives an overview of DataFrame datatypes (dtypes), explains how to set dtypes when reading data, and shows how to change column types.&lt;/p&gt;
&lt;p&gt;Using column types that require less memory can be a great way to speed up your workflows. Properly setting dtypes when reading files is sometimes needed for your code to run without error.&lt;/p&gt;
&lt;p&gt;Here’s what’s covered in this post:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Inspecting DataFrame column types (dtypes)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Changing column types&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inferring types when reading CSVs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manually specifying types&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Problems with type inference&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using Parquet to avoid type inference&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="inspecting-dataframe-types"&gt;
&lt;h2&gt;Inspecting DataFrame types&lt;/h2&gt;
&lt;p&gt;Let’s start by creating some DataFrames and viewing the dtypes.&lt;/p&gt;
&lt;p&gt;Create a pandas DataFrame and print the dtypes. All code snippets in this post are from &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/main/dataframes/dask-dtypes.ipynb"&gt;this notebook&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;letters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;nums&lt;/span&gt;       &lt;span class="n"&gt;int64&lt;/span&gt;
&lt;span class="n"&gt;letters&lt;/span&gt;    &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The nums column has the int64 type and the letters column has the object type.&lt;/p&gt;
&lt;p&gt;Let’s use this pandas DataFrame to create a Dask DataFrame and inspect the dtypes of the Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt;

&lt;span class="n"&gt;nums&lt;/span&gt;       &lt;span class="n"&gt;int64&lt;/span&gt;
&lt;span class="n"&gt;letters&lt;/span&gt;    &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The Dask DataFrame has the same dtypes as the pandas DataFrame.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="changing-column-types"&gt;
&lt;h2&gt;Changing column types&lt;/h2&gt;
&lt;p&gt;Change the nums column to int8. You can use Dask’s astype function to cast an object to a different type.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nums&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nums&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;int8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt;

&lt;span class="n"&gt;nums&lt;/span&gt;        &lt;span class="n"&gt;int8&lt;/span&gt;
&lt;span class="n"&gt;letters&lt;/span&gt;    &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;8 bit integers don’t require as much memory as 64 bit integers, but can only hold values between -128 and 127, so this datatype is only suitable for columns that contain small numbers.&lt;/p&gt;
&lt;p&gt;Selecting the best dtypes for your DataFrame is discussed in more detail later in this post.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="inferring-types"&gt;
&lt;h2&gt;Inferring types&lt;/h2&gt;
&lt;p&gt;Let’s read a 663 million row CSV time series dataset from a cloud filestore (S3) and print the dtypes.&lt;/p&gt;
&lt;p&gt;We’ll use the Coiled Dask platform to execute these queries on a cluster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;demo-cluster&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distributed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/csv/*.part&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;use_ssl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;timestamp&lt;/span&gt;    &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="nb"&gt;id&lt;/span&gt;           &lt;span class="n"&gt;int64&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;         &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;            &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt;            &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask infers the column types by taking a sample of the data. It doesn’t analyze every row of data when inferring types because that’d be really slow on a large dataset.&lt;/p&gt;
&lt;p&gt;Dask may incorrectly infer types because it only uses a sample. If the inferred type is wrong then subsequent computations will error out. We’ll discuss these errors and common work-arounds later in this post.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="manually-specifying-types"&gt;
&lt;h2&gt;Manually specifying types&lt;/h2&gt;
&lt;p&gt;This section demonstrates how manually specifying types can reduce memory usage.&lt;/p&gt;
&lt;p&gt;Let’s look at the memory usage of the DataFrame:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;Index&lt;/span&gt;           &lt;span class="mi"&gt;140160&lt;/span&gt;
&lt;span class="nb"&gt;id&lt;/span&gt;          &lt;span class="mi"&gt;5298048000&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;       &lt;span class="mi"&gt;41289103692&lt;/span&gt;
&lt;span class="n"&gt;timestamp&lt;/span&gt;  &lt;span class="mi"&gt;50331456000&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;           &lt;span class="mi"&gt;5298048000&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt;           &lt;span class="mi"&gt;5298048000&lt;/span&gt;
&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;int64&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The id column takes 5.3GB of memory and is typed as an int64. The id column contains values between 815 and 1,193. We don’t need 64 bit integers to store values that are so small. Let’s type id as a 16 bit integer and see how much memory that saves.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/csv/*.part&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;use_ssl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;int16&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;Index&lt;/span&gt;           &lt;span class="mi"&gt;140160&lt;/span&gt;
&lt;span class="nb"&gt;id&lt;/span&gt;          &lt;span class="mi"&gt;1324512000&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;       &lt;span class="mi"&gt;41289103692&lt;/span&gt;
&lt;span class="n"&gt;timestamp&lt;/span&gt;  &lt;span class="mi"&gt;50331456000&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;           &lt;span class="mi"&gt;5298048000&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt;           &lt;span class="mi"&gt;5298048000&lt;/span&gt;
&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;int64&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The id column only takes up 1.3 GB of memory when it’s stored as a 16 bit integer.&lt;/p&gt;
&lt;p&gt;It’s not surprising that the memory requirements are 4 times lower because int16 is 4x smaller than int64 ;)&lt;/p&gt;
&lt;p&gt;Manually specifying column dtypes can be tedious, especially for wide DataFrames, but it’s less error prone than inferring types.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="problems-when-inferring-types"&gt;
&lt;h2&gt;Problems when inferring types&lt;/h2&gt;
&lt;p&gt;As mentioned earlier, Dask does not look at every value when inferring types. It only looks at a sample of the rows.&lt;/p&gt;
&lt;p&gt;Suppose a column has the following values: 14, 11, 10, 22, and “hi”. If Dask only samples 3 values (e.g. 11, 10, and 22), then it might incorrectly assume this is an int64 column.&lt;/p&gt;
&lt;p&gt;Let’s illustrate this issue with the New York City Parking Tickets dataset.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/nyc-parking-tickets/csv/*.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;use_ssl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# this works&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# this errors out&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/var/folders/d2/116lnkgd0l7f51xr7msb2jnh0000gn/T/ipykernel_9266/95726415.py in &amp;lt;module&amp;gt;
---&amp;gt; 1 ddf.head()

ValueError: Mismatched dtypes found in `pd.read_csv`/`pd.read_table`.

+-----------------------+---------+----------+
| Column                | Found   | Expected |
+-----------------------+---------+----------+
| House Number          | object  | float64  |
| Issuer Command        | object  | int64    |
| Issuer Squad          | object  | int64    |
| Time First Observed   | object  | float64  |
| Unregistered Vehicle? | float64 | int64    |
| Violation Description | object  | float64  |
| Violation Legal Code  | object  | float64  |
| Violation Location    | float64 | int64    |
| Violation Post Code   | object  | float64  |
+-----------------------+---------+----------+

The following columns also raised exceptions on conversion:

- House Number
  ValueError(&amp;quot;could not convert string to float: &amp;#39;67-21&amp;#39;&amp;quot;)
- Issuer Command
  ValueError(&amp;quot;invalid literal for int() with base 10: &amp;#39;T730&amp;#39;&amp;quot;)
- Issuer Squad
  ValueError(&amp;#39;cannot convert float NaN to integer&amp;#39;)
- Time First Observed
  ValueError(&amp;quot;could not convert string to float: &amp;#39;1134P&amp;#39;&amp;quot;)
- Violation Description
  ValueError(&amp;quot;could not convert string to float: &amp;#39;BUS LANE VIOLATION&amp;#39;&amp;quot;)
- Violation Legal Code
  ValueError(&amp;quot;could not convert string to float: &amp;#39;T&amp;#39;&amp;quot;)
- Violation Post Code
  ValueError(&amp;quot;could not convert string to float: &amp;#39;H -&amp;#39;&amp;quot;)
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Usually this is due to Dask’s dtype inference failing, and
&lt;em&gt;may&lt;/em&gt; be fixed by specifying dtypes manually by adding:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;dtype={&amp;#39;House Number&amp;#39;: &amp;#39;object&amp;#39;,
       &amp;#39;Issuer Command&amp;#39;: &amp;#39;object&amp;#39;,
       &amp;#39;Issuer Squad&amp;#39;: &amp;#39;object&amp;#39;,
       &amp;#39;Time First Observed&amp;#39;: &amp;#39;object&amp;#39;,
       &amp;#39;Unregistered Vehicle?&amp;#39;: &amp;#39;float64&amp;#39;,
       &amp;#39;Violation Description&amp;#39;: &amp;#39;object&amp;#39;,
       &amp;#39;Violation Legal Code&amp;#39;: &amp;#39;object&amp;#39;,
       &amp;#39;Violation Location&amp;#39;: &amp;#39;float64&amp;#39;,
       &amp;#39;Violation Post Code&amp;#39;: &amp;#39;object&amp;#39;}

to the call to `read_csv`/`read_table`.
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;House Number was inferred to be a float64, but it should actually be an object because it contains values like “67-21”.&lt;/p&gt;
&lt;p&gt;Let’s look at the full set of dtypes in this DataFrame with print(ddf.dtypes).&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Summons Number                    int64
Plate ID                          object
Registration State                object
Plate Type                        object
Issue Date                        object
Violation Code                     int64
Vehicle Body Type                 object
Vehicle Make                      object
Issuing Agency                    object
Street Code1                       int64
Street Code2                       int64
Street Code3                       int64
Vehicle Expiration Date            int64
Violation Location                 int64
Violation Precinct                 int64
Issuer Precinct                    int64
Issuer Code                        int64
Issuer Command                     int64
Issuer Squad                       int64
Violation Time                    object
Time First Observed              float64
Violation County                  object
Violation In Front Of Or Opposite object
House Number                     float64
Street Name                       object
Intersecting Street               object
Date First Observed                int64
Law Section                        int64
Sub Division                      object
Violation Legal Code             float64
Days Parking In Effect            object
From Hours In Effect              object
To Hours In Effect                object
Vehicle Color                     object
Unregistered Vehicle?              int64
Vehicle Year                       int64
Meter Number                      object
Feet From Curb                     int64
Violation Post Code              float64
Violation Description            float64
No Standing or Stopping Violation float64
Hydrant Violation                float64
Double Parking Violation         float64
Latitude                         float64
Longitude                        float64
Community Board                  float64
Community Council                float64
Census Tract                     float64
BIN                              float64
BBL                              float64
NTA                              float64
dtype: object
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Figuring out the right dtypes for each column is really annoying.&lt;/p&gt;
&lt;p&gt;We can get past this initial error by specifying the dtypes, as recommended in the error message.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/nyc-parking-tickets/csv/*.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;House Number&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;&amp;#39;Issuer Command&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;&amp;#39;Issuer Squad&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;&amp;#39;Time First Observed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;&amp;#39;Unregistered Vehicle?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;float64&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;&amp;#39;Violation Description&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;&amp;#39;Violation Legal Code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;&amp;#39;Violation Location&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;float64&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;&amp;#39;Violation Post Code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;ddf.head()&lt;/span&gt;&lt;/code&gt; will work now but &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;ddf.describe().compute()&lt;/span&gt;&lt;/code&gt; will error out with this message:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)

ValueError: Mismatched dtypes found in `pd.read_csv`/`pd.read_table`.

+-------------------------+--------+----------+
| Column                  | Found  | Expected |
+-------------------------+--------+----------+
| Date First Observed     | object | int64    |
| Vehicle Expiration Date | object | int64    |
+-------------------------+--------+----------+

The following columns also raised exceptions on conversion:

- Date First Observed
  ValueError(&amp;quot;invalid literal for int() with base 10: &amp;#39;01/05/0001 12:00:00 PM&amp;#39;&amp;quot;)
- Vehicle Expiration Date
  ValueError(&amp;quot;invalid literal for int() with base 10: &amp;#39;01/01/20160411 12:00:00 PM&amp;#39;&amp;quot;)
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Usually this is due to Dask’s dtype inference failing, and
&lt;em&gt;may&lt;/em&gt; be fixed by specifying dtypes manually by adding:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;dtype={&amp;#39;Date First Observed&amp;#39;: &amp;#39;object&amp;#39;,
       &amp;#39;Vehicle Expiration Date&amp;#39;: &amp;#39;object&amp;#39;}

to the call to `read_csv`/`read_table`.
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;These dtype inference problems are common when using CSV files. This is one of the many reasons to avoid the CSV file format and use files better suited for data analyses.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="avoiding-type-inference"&gt;
&lt;h2&gt;Avoiding type inference&lt;/h2&gt;
&lt;p&gt;You should avoid type inference whenever possible.&lt;/p&gt;
&lt;p&gt;Certain file formats, like Parquet, let you store file metadata in the footer. The Parquet metadata includes the schema, so you don’t need to manually specify the dtypes or rely on Dask to perform schema inference.&lt;/p&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://coiled.io/blog/writing-parquet-files-with-dask-using-to-parquet/"&gt;this blog post on the Advantages of the Parquet format&lt;/a&gt; for more detail.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="new-string-type"&gt;
&lt;h2&gt;New string type&lt;/h2&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://www.youtube.com/watch?v=RAAcDkQgBTI&amp;amp;amp;t=364s"&gt;this video&lt;/a&gt; for the string[pyarrow] datatype that was added to pandas 1.3.&lt;/p&gt;
&lt;p&gt;The object datatype is notoriously memory hungry and this new datatype will help a lot!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Dask DataFrame columns are typed, just like pandas DataFrames.&lt;/p&gt;
&lt;p&gt;You’ll sometimes run into type-inference errors, especially when you’re working with CSV files.&lt;/p&gt;
&lt;p&gt;Use file formats that don’t require type inference so you don’t need to deal with this headache.&lt;/p&gt;
&lt;p&gt;It’s great to see the PyData ecosystem constantly evolving and the pandas team adding a new dtype for strings that’s a lot more efficient. Constant improvements are part of what make the PyData ecosystem so wonderful!&lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-dtype-astype.html"/>
    <summary>Columns in Dask DataFrames are typed, which means they can only hold certain values (e.g. integer columns can’t hold string values). This post gives an overview of DataFrame datatypes (dtypes), explains how to set dtypes when reading data, and shows how to change column types.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/pyarrow-strings-in-dask-dataframes.html</id>
    <title>PyArrow Strings in Dask DataFrames</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="pyarrow-strings-in-dask-dataframes"&gt;

&lt;p&gt;pandas 2.0 has been released!  🎉&lt;/p&gt;
&lt;p&gt;Improved PyArrow data type support is a major part of this release, notably for PyArrow strings, which are faster and more compact in memory than Python object strings, the historic solution. This change impacts pandas users everywhere, but especially impacts Dask DataFrame users, who often run at the capacity of their hardware.&lt;/p&gt;
&lt;p&gt;PyArrow strings often use less memory, and so allow you to process more data on less hardware. In the example below we’ll find that we can operate on the same data, faster, using a cluster of one third the size. This corresponds to about a 75% overall cost reduction.&lt;/p&gt;
&lt;img alt="Arrow Strings vs Python Strings" src="https://docs.coiled.io/_images/642c76ac9d63cb504c88a2f6_Arrow%2520Strings%2520vs%2520Python%2520Strings.png" /&gt;
&lt;section id="how-to-use-pyarrow-strings-in-dask"&gt;
&lt;h2&gt;How to use PyArrow strings in Dask&lt;/h2&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;
&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataframe.convert-string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Note, support isn’t perfect yet. Most operations work fine, but some (any, all, …) still require upstream fixes. Thank you to pandas and PyArrow maintainers for their collaboration in this effort.&lt;/p&gt;
&lt;p&gt;This option is available starting with Dask 2023.03.01 and it requires pandas 2.0 to be installed.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-pyarrow-example"&gt;
&lt;h2&gt;Dask PyArrow Example&lt;/h2&gt;
&lt;p&gt;Let’s consider the following example, where we load some public Uber/Lyft Parquet data onto a cluster running on the cloud.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;

&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataframe.convert-string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
	 &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;backend_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;region&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;
  
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/uber-lyft-tlc/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This takes about 33 seconds to load. We can then do operations to find out, for example, how many rides are tipped, broken down by Uber and Lyft rides:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tipped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tips&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hvfhs_license_num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tipped&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="String comparison benchmark" src="https://docs.coiled.io/_images/driver-received-tip.png" /&gt;
&lt;p&gt;If you’d like to play more with this dataset (it’s fun!) there’s a monthly free tutorial that you can sign up &lt;a class="reference external" href="https://coiled.io/tutorials"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="turn-off-pyarrow-strings"&gt;
&lt;h2&gt;Turn off PyArrow Strings&lt;/h2&gt;
&lt;p&gt;We can run this exact same computation, but now without PyArrow strings:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataframe.convert-string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Unfortunately, the first step to make this work is to ask for 3x the hardware. Otherwise the dataset doesn’t fit into memory, and we’re stuck repeatedly reading from storage:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_for_workers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then everything works fine, and we get the same results as above, although operations like the groupby-aggregation above take twice as long (even with 3x the hardware).&lt;/p&gt;
&lt;img alt="String equality benchmark" src="https://docs.coiled.io/_images/groupby-agg-perf.png" /&gt;
&lt;/section&gt;
&lt;section id="learn-try-report"&gt;
&lt;h2&gt;Learn, Try, Report&lt;/h2&gt;
&lt;p&gt;PyArrow strings are a significant win for the entire PyData community. They are also a work in progress. While we’re excited to adopt them within our own benchmarking suites, we also know that early adopters will experience some rough edges. If you’d like to learn more, here are a few resources:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;pandas: &lt;a class="reference external" href="https://pandas.pydata.org/docs/dev/whatsnew/v2.0.0.html"&gt;What’s new in pandas 2.0.0&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uber/Lyft dataset: &lt;a class="reference external" href="https://www.coiled.io/tutorials"&gt;Regular free tutorial playing with this dataset  &lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dask + PyArrow: &lt;a class="reference external" href="https://github.com/dask/dask/issues/10139"&gt;Discussion issue where you can report experience, positive or negative&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have the time to use PyArrow strings with (or without) Dask and report your experience we’d love to hear about what’s working well and what needs improvement.&lt;/p&gt;
&lt;p&gt;Thank you to everyone who contributed to this, but especially Matthew Roeschke (NVIDIA), Patrick Hoefler (Coiled), and Rick Zamora (NVIDIA) who worked to make sure all of the different libraries needed here came together.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/pyarrow-strings-in-dask-dataframes.html"/>
    <summary>pandas 2.0 has been released!  🎉</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/prioritizing-pragmatic-performance-for-dask.html</id>
    <title>Prioritizing Pragmatic Performance for Dask</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="prioritizing-pragmatic-performance-for-dask"&gt;

&lt;p&gt;Many people say the following to me:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;You should focus on X in Dask!  X makes things really fast!&lt;/em&gt;&lt;/p&gt;
&lt;img alt="Dask logo" src="https://docs.coiled.io/_images/637473192b9adb450419d4ac_Logo%2520Full%2520Color.svg" /&gt;
&lt;p&gt;Some examples of X:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Polars&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;High level query optimization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;C/C++/Rust&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;POSIX shared memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reducing memory copies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Massive scale&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In some cases I think that they’re absolutely right. In other cases I agree that the thing that they’re talking about is fast, but I don’t think that it will meaningfully improve common workloads, especially in a distributed cloud computing environment.&lt;/p&gt;
&lt;p&gt;Instead, I tend to orient development pretty strongly around what profiling, benchmarking, and user engagement say is the biggest bottleneck, and go from there. This points me to topics like the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Improving S3 bandwidth&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Intelligent scheduling to avoid memory buildup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removing Python object dtypes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removing Python for loops from user code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better observability to help users know what’s going on&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means that Dask often loses in flat-out performance benchmarks in artificial scenarios, but that it continues to be the standard of use when it comes to scalable Python data processing.&lt;/p&gt;
&lt;p&gt;This article will go into a few things that I think are great ideas, but are not yet major bottlenecks and why. Then it will go into a few things that I think are less-than-exciting-but-really-important improvements for common workloads.&lt;/p&gt;
&lt;section id="things-people-ask-for"&gt;
&lt;h2&gt;Things people ask for&lt;/h2&gt;
&lt;section id="anything-that-is-faster-than-s3"&gt;
&lt;h3&gt;Anything that is faster than S3&lt;/h3&gt;
&lt;p&gt;First, many workloads are bound by S3/GCS/object-store read performance. This tends to be 200 MB/s per node (with variation). If the thing that you’re talking about is much faster than this then it probably doesn’t matter that much (unless you’re doing it hundreds of times per byte (in which case you’re maybe doing something wrong)).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="memory-copies"&gt;
&lt;h3&gt;Memory Copies&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Memory copies make things slow, but only by a tiny bit&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Memory bandwidth often operates at 20 GB/s on a modest machine. So while it’s true that memory copies slow things down, they only slow them down a little bit. It’s like asking a racecar to go once more around the racetrack when we’re still waiting for a donkey (s3/disk/network) to finish going around once.  It just doesn’t matter.&lt;/p&gt;
&lt;p&gt;But for the sake of argument let’s imagine for a moment that you’re at a performance point where this &lt;em&gt;does&lt;/em&gt; matter. That means that you are processing data at 20 GB/s on a modest machine, or around 1 TB per minute. Per day you’re processing about 1 PB on this single node.&lt;/p&gt;
&lt;p&gt;If you’re in that world, then you either …&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Generate many petabytes of data per day&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don’t need parallel computing, and so don’t need Dask (Hooray!  Fewer tools!)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section id="high-level-query-optimization"&gt;
&lt;h3&gt;High Level Query Optimization&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Doing less work is good&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In contrast, I think that high level optimization is very very good. For one, it often means that you get to reduce the amount of data that you load, which reduces the demand on the slow parts of your system (like S3). Dask Dataframes/Arrays doesn’t do this well today, but it should.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Updated August 2023: You can now try out Dask Expressions with pip install dask-expr. Learn more in &lt;a class="reference external" href="https://medium.com/coiled-hq/high-level-query-optimization-in-dask-995640564ed7"&gt;our blog post&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="c-c-rust"&gt;
&lt;h3&gt;C/C++/Rust&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;You’re already using these for 99% of your computation, the last 1% doesn’t matter much&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you’re doing things well then you’re already relying on mostly low level code (numpy, pandas, pytorch, scikit-learn, arrow, …) The code calling those routines accounts for a small fraction of your total runtime and is not worth optimizing.&lt;/p&gt;
&lt;p&gt;Said another way, most data workloads are saturated by network and disk. I can, with Python, saturate network and disk and so go as fast as possible. If we’re in this regime then, unless a faster language can somehow make your disk/network hardware go faster, then language choice doesn’t matter.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="polars"&gt;
&lt;h3&gt;Polars&lt;/h3&gt;
&lt;p&gt;Taking a look at the options above, I’m really excited about Polars mostly due to the high level query optimization. The other bits are nice, but mostly on single machines where you have really fast access to disk and don’t need to worry about network as much.&lt;/p&gt;
&lt;p&gt;Ritchie Vink (polars core dev) and I actually started a &lt;a class="reference external" href="https://github.com/pola-rs/dask-polars"&gt;dask-polars project&lt;/a&gt; several months ago. I’d love to work more on it (it’s fun!).  I’m waiting for this need to come up more in benchmarks before I personally prioritize it more.&lt;/p&gt;
&lt;p&gt;The best arguments that I’ve heard for prioritizing it early so far are …&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;High level query optimization (Dask dataframes should have this, and don’t)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some folks like the API more than Pandas&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The more I hear folks report #2 above the more I’ll shift efforts over to it. So far I’m seeing 50x more Pandas usage (but down from 1000x a few months ago, so the trend here is worth watching).&lt;/p&gt;
&lt;p&gt;To be clear though, folks should use Polars. It helps to avoid the distributed computing problem significantly. Polars is great.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="posix-shared-memory"&gt;
&lt;h3&gt;POSIX Shared Memory&lt;/h3&gt;
&lt;p&gt;This mostly comes from ray users who like the idea of sharing memory between processes. The conversation usually goes like this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Them: Dask should use POSIX shared memory so that you can share data between cores!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Me: Dask does that today!  We use a single normal process with lots of normal threads and normal shared memory!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Them: But then what about the GIL?!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Me: The GIL isn’t an issue!  The GIL has been released in most PyData libraries ever since Dask came out seven years ago!  Here is an &lt;a class="reference external" href="https://matthewrocklin.com/blog/work/2015/03/10/PyData-GIL"&gt;ancient blogpost&lt;/a&gt; on the topic!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Threads + normal memory is less fancy, but also way simpler than posix-shared-memory!&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Them: Hrm, maybe this is important if people are writing lots of for-loopy python code?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Me: That’s true!  But that’s also a 100x performance hit! I encourage you to address that much much deeper issue before trying to parallelize it. Maybe consider teaching the pandas API or using Numba?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, this is really important if you’re doing distributed training of very large machine learning models. Almost no one I talk to is doing this though. Mostly this issue comes up because, I think, people misunderstand the GIL.&lt;/p&gt;
&lt;p&gt;To address this misunderstanding, I think that we need to improve observability around the GIL. See &lt;a class="github reference external" href="https://github.com/dask/distributed/issues/7290"&gt;dask/distributed#7290&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But let’s say that folks here are right and that we do want to reduce intra-machine communication costs with shared memory. How much of a savings is this, actually? The common alternative is just to ask for many more smaller machines. It’s true that inter-machine communication is more expensive than intra-machine communication, but most communication is going to be inter-machine anyway, so the savings here is minimal. There’s just no way that this respects &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Amdahl%27s_law"&gt;Amdahl’s law.&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="massive-scale"&gt;
&lt;h3&gt;Massive Scale&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;We work at really big scale. What if we want to scale to thousands of machines?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Well, Dask actually works fine at thousands of machines. You’ll probably want to change some of the default configuration; let’s chat. Really though, if you’re doing things well then you probably don’t need thousands of machines.&lt;/p&gt;
&lt;p&gt;I work with some of the largest organizations doing some of the largest computations. It is &lt;em&gt;very rare&lt;/em&gt; for someone to need thousands of machines. It is much more common for them to think that they need thousands of machines, get feedback from Dask about how their workflow is running, and then learn that they can accomplish more than they wanted with far fewer resources.&lt;/p&gt;
&lt;p&gt;Most Dask clusters in large organizations operate at the 10-500 machines range with the bottom of that range being far more common. Some folks get up to a thousand machines. Even at this point the scheduler can be idling most of the time if you’re doing things efficiently.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="things-i-m-excited-about"&gt;
&lt;h2&gt;Things I’m excited about&lt;/h2&gt;
&lt;p&gt;Most dataframe workloads that are slow are due to one of these things:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Running out of memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Object dtypes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Iterating over rows with a for loop&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Network/disk bandwidth&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Loading in data we didn’t need&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This covers about 95% of slow workflows. People who get rid of these issues are invariably happy. Let’s talk through a few things that are in flight today that can be done to address them.&lt;/p&gt;
&lt;section id="task-queuing-more-intelligent-memory-scheduling"&gt;
&lt;h3&gt;Task Queuing / more intelligent memory scheduling&lt;/h3&gt;
&lt;p&gt;The Dask scheduler is &lt;em&gt;very&lt;/em&gt; smart about placing and prioritizing tasks to reduce overall memory use. If you are running into a workload that you think should run in small memory but doesn’t then I encourage you to try out the 2022.11.0 release. It’s pretty awesome. We’ve written more about it &lt;a class="reference internal" href="blog/reducing-dask-memory-usage.html"&gt;&lt;span class="doc std std-doc"&gt;in this blog post&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is a snippet from the last Dask Demo Day with engineer Florian Jetter talking about it and showing the impact:&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/VlTgcLqb1DQ?start=516" title="Dask Demo Day - 2022-10-27" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="pyarrow-strings"&gt;
&lt;h3&gt;PyArrow Strings&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Updated August 2023: as of &lt;strong&gt;Dask 2023.7.1, PyArrow backed strings are the default in Dask DataFrames.&lt;/strong&gt; This option is automatically enabled if you have at least pandas 2.0 and PyArrow 12.0 installed. See the &lt;a class="reference external" href="https://docs.dask.org/en/stable/changelog.html#v2023-7-1"&gt;Dask release notes&lt;/a&gt; for implementation details and our &lt;a class="reference external" href="https://www.coiled.io/blog/pyarrow-strings-in-dask-dataframes"&gt;blog post&lt;/a&gt; on how this change improves performance.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Python is slow. Normally we get past this by writing performant code in C/C++/Cython and then linking to it. Pandas does this, but unfortunately they didn’t have a good way to handle strings, so they stayed with Python for that kind of data. 😞&lt;/p&gt;
&lt;p&gt;Fortunately, Arrow handles this. They have nice strings implemented in C++. This results in lower memory use and faster speeds. Here is a demonstrative video:&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/_zoPmQ6J1aE?start=151" title="Pandas/Arrow/Dask String Performance Improvements | Matt Rocklin" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;For a long time PyArrow strings were exciting, but not fully supported. It seems like today they are. Pandas itself is considering switching over by default. My guess is that Dask may lead pandas here a bit and make the switch earlier. This is in active development.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="extension-data-types-more-generally"&gt;
&lt;h3&gt;Extension data types more generally&lt;/h3&gt;
&lt;p&gt;This also holds for other data types, including nullable dtypes (ints, bools, etc..), decimals, and others. There’s good movement towards just switching over generally.&lt;/p&gt;
&lt;p&gt;Pandas 2.0 is coming out soon (rc branch already tagged) and in general I expect to see a big shift in this direction over the next year.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="s3-bandwidth"&gt;
&lt;h3&gt;S3 Bandwidth&lt;/h3&gt;
&lt;p&gt;Sometimes we don’t handle things as well as we should. Recently we found a nice 2x speed boost.&lt;/p&gt;
&lt;p&gt;&lt;a class="github reference external" href="https://github.com/dask/dask/issues/9619#issuecomment-1302306306"&gt;dask/dask#9619&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="shuffle-service"&gt;
&lt;h3&gt;Shuffle service&lt;/h3&gt;
&lt;p&gt;When you want to sort/set-index/join dataframes you often need to do an all-to-all shuffle. Dask’s task scheduling system wasn’t designed for this and while it does it, it does it poorly in a way that often stresses the scheduler and bloats memory.&lt;/p&gt;
&lt;p&gt;There is an experimental shuffle service (similar to what Spark uses) in Dask today that has solid performance, but doesn’t yet have rock-solid-reliability (and so is not yet default). We hope to have this in by early 2023.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="id1"&gt;
&lt;h3&gt;High Level Query Optimization&lt;/h3&gt;
&lt;aside class="system-message"&gt;
&lt;p class="system-message-title"&gt;System Message: INFO/1 (&lt;span class="docutils literal"&gt;/home/runnerx/actions-runner/_work/platform/platform/python-api/docs/source/blog/prioritizing-pragmatic-performance-for-dask.md&lt;/span&gt;, line 174); &lt;em&gt;&lt;a href="#id1"&gt;backlink&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Duplicate implicit target name: “high level query optimization”.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;&lt;em&gt;Updated August 2023: You can now try out Dask Expressions with pip install dask-expr. Learn more in &lt;a class="reference external" href="https://medium.com/coiled-hq/high-level-query-optimization-in-dask-995640564ed7"&gt;our blog post&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Probably sometime in 2023 we should focus on updating our high level graph specification, which grew organically and has a bit of cruft. After this we should be able to start development on basic query optimizations. This plus shuffling and good dtype support should get us into a point where Dask dataframes are genuinely competitive performance-wise in ways that matter.&lt;/p&gt;
&lt;p&gt;Additionally, high level query optimization should also be applicable to other Dask collections, like Arrays/Xarray, which should be fun.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="things-that-aren-t-performance"&gt;
&lt;h3&gt;Things that aren’t performance&lt;/h3&gt;
&lt;p&gt;It’s worth pointing out that most distributed data computing users I meet would happily trade in a 3x performance hit if things would “just work”. Topics like ease of deployment, visibility, stability, and ease-of-use for less technical teammates often supersede a desire for things to go fast.&lt;/p&gt;
&lt;p&gt;Dask has never been the fastest framework (go to MPI for that) but we have probably always been the easiest to use and understand and adapt to user needs. This results in a joint human+computer performance that can’t be beat.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Dask developers care about performance, we’ve always taken a pragmatic rather than exciting approach to the problem.  We interact with users, identify user pain, and focus on that pain.&lt;/p&gt;
&lt;p&gt;This focus on user pain has served us well in the past, but often orients us differently from the focus of the public, which is understandably excited by shiny developments. I encourage folks to profile and benchmark their workloads (Dask provides excellent tools for this) and &lt;a class="reference external" href="https://github.com/dask/dask/issues/new"&gt;let us know what you see&lt;/a&gt;. We’re excited for more input.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/prioritizing-pragmatic-performance-for-dask.html"/>
    <summary>Many people say the following to me:</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/spatial-join-dask-geopandas-sjoin.html</id>
    <title>Perform a Spatial Join in Python</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="perform-a-spatial-join-in-python"&gt;

&lt;p&gt;This blog explains how to perform a spatial join in Python. Knowing how to perform a spatial join is an important asset in your data-processing toolkit: it enables you to join two datasets based on spatial predicates. For example, you can join a point-based dataset with a polygon-based dataset based on whether the points fall within the polygon.&lt;/p&gt;
&lt;table class="tg" align="center"&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th class="tg-bc4c"&gt;&lt;/th&gt;
    &lt;th class="tg-bc4c"&gt;GeoPandas&lt;/th&gt;
    &lt;th class="tg-bc4c"&gt;Dask-GeoPandas&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
  &lt;tr&gt;
    &lt;td class="tg-ohrm"&gt;Perform Spatial Join&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class="tg-rnf2"&gt;Support Geographic Data&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class="tg-ohrm"&gt;Parallel Processing&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/274c.svg" alt="❌" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class="tg-rnf2"&gt;Larger-than-memory Datasets&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/274c.svg" alt="❌" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In this blog, we will use the New York City Taxi and Limousine Commission dataset to illustrate performing a spatial join in Python. We will join the taxi pickup location of each individual trip record to the NYC neighborhood it is located within. This will then allow us to bring in public health data at the neighborhood level and discover interesting patterns in the data that we could not have seen without performing a spatial join.&lt;/p&gt;
&lt;section id="spatial-join-different-python-libraries"&gt;
&lt;h2&gt;Spatial Join: Different Python Libraries&lt;/h2&gt;
&lt;p&gt;There are several different ways to perform a spatial join in Python. This blog will walk you through two popular open-source Python you can use to perform spatial joins:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Geopandas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dask-GeoPandas&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dask-GeoPandas has recently undergone some impressive changes due to the hard work of maintainers &lt;a class="reference external" href="https://github.com/jorisvandenbossche"&gt;Joris Van den Bossche&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/martinfleis"&gt;Martin Fleischmann&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/jsignell"&gt;Julia Signell&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For each library, we will first demonstrate and explain the syntax, and then tell you when and why you should use it. You can follow along in &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/main/blogs/spatial-join-dask-geopandas-sjoin.ipynb"&gt;this Jupyter notebook&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s jump in.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="spatial-join-geopandas"&gt;
&lt;h2&gt;Spatial Join: GeoPandas&lt;/h2&gt;
&lt;p&gt;GeoPandas extends pandas DataFrames to make it easier to work with geospatial data directly from Python, instead of using a dedicated spatial database such as PostGIS.&lt;/p&gt;
&lt;p&gt;A GeoPandas GeoDataFrame looks and feels a lot like a regular pandas DataFrame. The main difference is that it has a geometry column that contains &lt;a class="reference external" href="https://shapely.readthedocs.io/en/stable/index.html"&gt;shapely&lt;/a&gt; geometries. This column is what enables us to perform a spatial join.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sidenote: geometry is just the default name of this column, a geometry column can have any name. There can also be multiple geometry columns, but one is always considered the “primary” geometry column on which the geospatial predicates operate.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let’s load in the NYC TLC data. We’ll start by working with first 100K trips made in January 2009:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://nyc-tlc/trip data/yellow_tripdata_2009-01.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;nrows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100_000&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The dataset requires some basic cleaning up which we perform in &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/main/blogs/spatial-join-dask-geopandas-sjoin.ipynb"&gt;the accompanying notebook&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next, let’s read in our geographic data. This is a shapefile sourced from &lt;a class="reference external" href="https://www1.nyc.gov/site/doh/data/data-sets/maps-gis-data-files-for-download.page"&gt;the NYC Health Department&lt;/a&gt; and contains the shapely geometries of 35 New York neighborhoods. We’ll load this into a GeoPandas GeoDataFrame:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;geopandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gpd&lt;/span&gt;
&lt;span class="n"&gt;ngbhoods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gpd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;../data/CHS_2009_DOHMH_2010B.shp&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)[[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;geometry&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;FIRST_UHF_&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;UHF_CODE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s inspect some characteristics of the nghboods object including the object type, how many rows it contains, and the first 3 rows of data:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngbhoods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;geopandas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geodataframe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GeoDataFrame&lt;/span&gt;

&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngbhoods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;35&lt;/span&gt;

&lt;span class="n"&gt;ngbhoods&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Coiled blog spatial join 1" src="https://docs.coiled.io/_images/6372bb47444f4894ebf7dd6c_Coiled%2520blog%2520-%2520spatial%2520join%25201.png" /&gt;
&lt;p&gt;ngbhoods is a GeoPandas GeoDataFrame containing the shapely polygons that define the 35 New York U neighborhoods &lt;a class="reference external" href="https://www1.nyc.gov/assets/doh/downloads/pdf/survey/uhf_map_100604.pdf"&gt;as defined by the United Hospital Fund&lt;/a&gt;, as well as their names and unique ID codes.&lt;/p&gt;
&lt;p&gt;A neat trick to know about GeoPandas is that calling .plot() on a GeoDataFrame will automatically plot the geometries of the shapes contained:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ngbhoods&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Coiled blog spatial join 2" src="https://docs.coiled.io/_images/6372bb32429e2e263529d1da_Coiled%2520blog%2520-%2520spatial%2520join%25202.png" /&gt;
&lt;p&gt;When joining spatial datasets, it’s important to standardize the Coordinate Reference System (CRS). A CRS defines how coordinates (numbers like 42.01 or 180000) map to points on the surface of the Earth. Discussing why you’d pick a particular CRS is out of scope for this post, but you visit &lt;a class="reference external" href="https://www.earthdatascience.org/courses/use-data-open-source-python/intro-vector-data-python/spatial-data-vector-shapefiles/intro-to-coordinate-reference-systems-python/"&gt;these&lt;/a&gt; &lt;a class="reference external" href="https://kartoweb.itc.nl/geometrics/Map%20projections/mappro.html"&gt;links&lt;/a&gt; to learn more. What is important is that the two datasets you want to join together have &lt;em&gt;the same CRS&lt;/em&gt;. Joining datasets with mismatched CRSs will lead to incorrect results downstream.&lt;/p&gt;
&lt;p&gt;The NYC TLC data uses latitude and longitude, with the WGS84 datum, which is the EPSG 4326 coordinate reference system. Let’s inspect the CRS of our ngbhoods GeoDataFrame:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ngbhoods&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crs&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Derived&lt;/span&gt; &lt;span class="n"&gt;Projected&lt;/span&gt; &lt;span class="n"&gt;CRS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EPSG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2263&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The CRSs don’t match, so we’ll have to transform one of them. It’s more efficient to transform a smaller rather than a larger dataset, so let’s project ngbhoods to EPSG 4326:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ngbhoods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngbhoods&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_crs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epsg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Sidenote: if you are working with multiple GeoDataFrames you can set the CRS of one gdf to that of another using gdf1.to_crs(gdf2.crs) without worrying about the specific CRS numbers.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Calling head() on ngbhoods now will reveal that the values for the geometry column have changed:&lt;/p&gt;
&lt;img alt="Coiled blog spatial join 3" src="https://docs.coiled.io/_images/6372bb8a64ed3505cccaa47f_Coiled%2520blog%2520-%2520spatial%2520join%25203.png" /&gt;
&lt;p&gt;Plotting the GeoDataFrame confirms that our spatial information is still intact. But if you look at the axes, you’ll notice the coordinates are now in degrees latitude and longitude instead of meters northing and easting:&lt;/p&gt;
&lt;img alt="Coiled blog spatial join 4" src="https://docs.coiled.io/_images/6372bb9bbdf82efc82d77fbe_Coiled%2520blog%2520-%2520spatial%2520join%25204.png" /&gt;
&lt;p&gt;Next, let’s take a look at the regular pandas DataFrame containing our taxi trip data. You’ll notice that this DataFrame also contains geographic information in the pickup_longitude/latitude and dropoff_longitude/latitude columns.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Coiled blog spatial join 5" src="https://docs.coiled.io/_images/6372bbb0d1545aa90b643264_Coiled%2520blog%2520-%2520spatial%2520join%25205.png" /&gt;
&lt;p&gt;Looking at the dtypes, however, reveals them to be regular numpy floats:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt;

&lt;span class="n"&gt;vendor_id&lt;/span&gt;           &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;pickup_datetime&lt;/span&gt;      &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;dropoff_datetime&lt;/span&gt;     &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;passenger_count&lt;/span&gt;       &lt;span class="n"&gt;int64&lt;/span&gt;
&lt;span class="n"&gt;trip_distance&lt;/span&gt;       &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;pickup_longitude&lt;/span&gt;    &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;pickup_latitude&lt;/span&gt;     &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;rate_code&lt;/span&gt;             &lt;span class="n"&gt;int64&lt;/span&gt;
&lt;span class="n"&gt;store_and_fwd_flag&lt;/span&gt;   &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;dropoff_longitude&lt;/span&gt;   &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;dropoff_latitude&lt;/span&gt;    &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;payment_type&lt;/span&gt;         &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;fare_amount&lt;/span&gt;         &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;surcharge&lt;/span&gt;           &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;mta_tax&lt;/span&gt;             &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;tip_amount&lt;/span&gt;          &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;tolls_amount&lt;/span&gt;        &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;total_amount&lt;/span&gt;        &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Calling plot() on the latitude and longitude columns does not give us a meaningful spatial representation. Let’s see how latitude / longitude values are difficult to interpret without a geospatial library.&lt;/p&gt;
&lt;p&gt;Start by plotting the latitude / longitude values:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;scatter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_latitude&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Coiled blog spatial join 6" src="https://docs.coiled.io/_images/6372bbc664ed359605cafef9_Coiled%2520blog%2520-%2520spatial%2520join%25206.png" /&gt;
&lt;p&gt;This result is difficult to interpret, partly because there are clearly some data points with incorrect coordinate values in the dataset. This won’t be a problem for our particular use case, but may be worth paying attention to if you’re using this data for other analyses.&lt;/p&gt;
&lt;p&gt;Let’s turn df into a GeoPandas GeoDataFrame, so we can leverage the built-in geospatial functions that’ll make our lives a lot easier. Convert the separate pickup_longitude and pickup_latitude columns into a single geometry column containing shapely Point data:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;geopandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;points_from_xy&lt;/span&gt;
&lt;span class="c1"&gt;# turn taxi_df into geodataframe&lt;/span&gt;
&lt;span class="n"&gt;taxi_gdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gpd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GeoDataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;crs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;EPSG:4326&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;points_from_xy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_latitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Take a look at first 3 rows:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;taxi_gdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Coiled blog spatial join 7" src="https://docs.coiled.io/_images/6372bbd42f40085a24523437_Coiled%2520blog%2520-%2520spatial%2520join%25207.png" /&gt;
&lt;p&gt;We now have a GeoDataFrame with a dedicated geometry column.&lt;/p&gt;
&lt;p&gt;Calling .explore() on a GeoDataFrame allows you to explore the dataset on a map. Run &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/main/blogs/spatial-join-dask-geopandas-sjoin.ipynb"&gt;this notebook&lt;/a&gt; to interactively explore the map yourself:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;taxi_gdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;explore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tiles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CartoDB positron&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# use &amp;quot;CartoDB positron&amp;quot; tiles&lt;/span&gt;
    &lt;span class="n"&gt;cmap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Set1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# use &amp;quot;Set1&amp;quot; matplotlib colormap&lt;/span&gt;
    &lt;span class="n"&gt;style_kwds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# use black outline&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Coiled blog spatial join 8" src="https://docs.coiled.io/_images/6372bbf27c694b1343873e5e_Coiled%2520blog%2520-%2520spatial%2520join%25208.png" /&gt;
&lt;p&gt;Now that both of your datasets have geometry columns, you can perform a spatial join.&lt;/p&gt;
&lt;p&gt;Perform a left spatial join with taxi_gdf as the left_df and ngbhoods as the right_df. Setting the predicate keyword to ‘within’ will join points in the left_df to polygons from the right_df they are located within:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;
&lt;span class="n"&gt;joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gpd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sjoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;taxi_gdf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;ngbhoods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;left&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;within&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;1.13 s ± 33.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Clean up and clarify some column names:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# drop index column&lt;/span&gt;
&lt;span class="n"&gt;joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;index_right&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# rename columns&lt;/span&gt;
&lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FIRST_UHF_&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pickup_ngbhood_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;UHF_CODE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pickup_ngbhood_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now that you’ve performed the spatial join, you can analyze spatial patterns in the data.&lt;/p&gt;
&lt;p&gt;For example, you can run a groupby that aggregates the mean trip_distance per pickup neighborhood:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pickup_ngbhood_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trip_distance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;res&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pickup_ngbhood_name&lt;/span&gt;
&lt;span class="n"&gt;Rockaway&lt;/span&gt;                              &lt;span class="mf"&gt;18.640000&lt;/span&gt;
&lt;span class="n"&gt;Southwest&lt;/span&gt; &lt;span class="n"&gt;Queens&lt;/span&gt;                       &lt;span class="mf"&gt;7.583590&lt;/span&gt;
&lt;span class="n"&gt;Bayside&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Meadows&lt;/span&gt;                      &lt;span class="mf"&gt;5.461818&lt;/span&gt;
&lt;span class="n"&gt;East&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;York&lt;/span&gt;                          &lt;span class="mf"&gt;5.195714&lt;/span&gt;
&lt;span class="n"&gt;Jamaica&lt;/span&gt;                                &lt;span class="mf"&gt;4.933902&lt;/span&gt;
&lt;span class="n"&gt;Washington&lt;/span&gt; &lt;span class="n"&gt;Heights&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Inwood&lt;/span&gt;            &lt;span class="mf"&gt;4.746627&lt;/span&gt;
&lt;span class="n"&gt;Pelham&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Throgs&lt;/span&gt; &lt;span class="n"&gt;Neck&lt;/span&gt;                   &lt;span class="mf"&gt;4.478235&lt;/span&gt;
&lt;span class="n"&gt;Southeast&lt;/span&gt; &lt;span class="n"&gt;Queens&lt;/span&gt;                       &lt;span class="mf"&gt;4.322500&lt;/span&gt;
&lt;span class="n"&gt;Kingsbridge&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Riverdale&lt;/span&gt;                &lt;span class="mf"&gt;4.140000&lt;/span&gt;
&lt;span class="n"&gt;Downtown&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Heights&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Slope&lt;/span&gt;            &lt;span class="mf"&gt;3.833111&lt;/span&gt;
&lt;span class="n"&gt;Flushing&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Clearview&lt;/span&gt;                   &lt;span class="mf"&gt;3.761765&lt;/span&gt;
&lt;span class="n"&gt;Sunset&lt;/span&gt; &lt;span class="n"&gt;Park&lt;/span&gt;                            &lt;span class="mf"&gt;3.612857&lt;/span&gt;
&lt;span class="n"&gt;Ridgewood&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Forest&lt;/span&gt; &lt;span class="n"&gt;Hills&lt;/span&gt;               &lt;span class="mf"&gt;3.311461&lt;/span&gt;
&lt;span class="n"&gt;Canarsie&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Flatlands&lt;/span&gt;                   &lt;span class="mf"&gt;3.290000&lt;/span&gt;
&lt;span class="n"&gt;Greenpoint&lt;/span&gt;                             &lt;span class="mf"&gt;3.153171&lt;/span&gt;
&lt;span class="n"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;Island&lt;/span&gt; &lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Astoria&lt;/span&gt;             &lt;span class="mf"&gt;3.055458&lt;/span&gt;
&lt;span class="n"&gt;Coney&lt;/span&gt; &lt;span class="n"&gt;Island&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Sheepshead&lt;/span&gt; &lt;span class="n"&gt;Bay&lt;/span&gt;          &lt;span class="mf"&gt;3.036667&lt;/span&gt;
&lt;span class="n"&gt;West&lt;/span&gt; &lt;span class="n"&gt;Queens&lt;/span&gt;                            &lt;span class="mf"&gt;3.034350&lt;/span&gt;
&lt;span class="n"&gt;Central&lt;/span&gt; &lt;span class="n"&gt;Harlem&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Morningside&lt;/span&gt; &lt;span class="n"&gt;Heights&lt;/span&gt;   &lt;span class="mf"&gt;2.866855&lt;/span&gt;
&lt;span class="n"&gt;Union&lt;/span&gt; &lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Lower&lt;/span&gt; &lt;span class="n"&gt;Manhattan&lt;/span&gt;          &lt;span class="mf"&gt;2.721932&lt;/span&gt;
&lt;span class="n"&gt;Bedford&lt;/span&gt; &lt;span class="n"&gt;Stuyvesant&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Crown&lt;/span&gt; &lt;span class="n"&gt;Heights&lt;/span&gt;     &lt;span class="mf"&gt;2.643019&lt;/span&gt;
&lt;span class="n"&gt;East&lt;/span&gt; &lt;span class="n"&gt;Harlem&lt;/span&gt;                            &lt;span class="mf"&gt;2.638558&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="spatial-join-dask-geopandas"&gt;
&lt;h2&gt;Spatial Join: Dask-GeoPandas&lt;/h2&gt;
&lt;p&gt;Now that we’ve seen how to perform a spatial join with GeoPandas, let’s look at how to do the same thing with Dask-GeoPandas. Dask-GeoPandas is particularly useful when you’re working with datasets that are too large to fit into memory.&lt;/p&gt;
&lt;p&gt;Let’s load the full NYC TLC dataset for January 2009 (about 120GB of data) into a Dask DataFrame:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://nyc-tlc/trip data/yellow_tripdata_2009-01.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s perform some basic data cleaning and filtering:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# convert datetime columns&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_datetime&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_datetime&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dropoff_datetime&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dropoff_datetime&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# filter out invalid coordinates&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_latitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;73.7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pickup_latitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;40.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.9&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s also load some public health data at the neighborhood level:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gpd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;../data/CHS_2009_DOHMH_2010B.shp&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)[[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;geometry&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;FIRST_UHF_&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;UHF_CODE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FIRST_UHF_&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;nhbd_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;UHF_CODE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;nhbd_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_crs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epsg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now let’s convert our Dask DataFrame into a Dask-GeoPandas GeoDataFrame:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask_geopandas&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dask_geopandas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_dask_dataframe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dask_geopandas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points_from_xy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pickup_longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pickup_latitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_crs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Before we perform the spatial join, we need to make sure all workers have access to the health data:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# broadcast health data to all workers&lt;/span&gt;
&lt;span class="n"&gt;health_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now we can perform the spatial join:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="n"&gt;joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sjoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health_future&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;within&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The spatial join is now distributed across all workers in the cluster. Let’s compute some statistics:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nhbd_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;trip_distance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;fare_amount&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;tip_amount&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;passenger_count&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This will give us average trip statistics per neighborhood, which we can use to analyze patterns in the data.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="spatial-join-the-final-verdict"&gt;
&lt;h2&gt;Spatial Join: the Final Verdict&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Spatial joins are powerful because they allow you to join datasets based on geographic data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GeoPandas is great for performing a spatial join on datasets that fit into memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dask-GeoPandas scales GeoPandas for larger-than-memory datasets using the parallel processing power of Dask.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can speed up a spatial join by running your computations in the cloud using a Coiled Cluster.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table class="tg" align="center"&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th class="tg-bc4c"&gt;&lt;/th&gt;
    &lt;th class="tg-bc4c"&gt;GeoPandas&lt;/th&gt;
    &lt;th class="tg-bc4c"&gt;Dask-GeoPandas&lt;/th&gt;
    &lt;th class="tg-bc4c"&gt;on Coiled&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
  &lt;tr&gt;
    &lt;td class="tg-ohrm"&gt;Perform Spatial Join&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class="tg-rnf2"&gt;Support Geographic Data&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class="tg-ohrm"&gt;Parallel Processing&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/274c.svg" alt="❌" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class="tg-rnf2"&gt;Larger-than-memory Datasets&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/274c.svg" alt="❌" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-bc4c"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class="tg-ohrm"&gt;Scale to the cloud&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/274c.svg" alt="❌" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/274c.svg" alt="❌" width="20" height="20"&gt;&lt;/td&gt;
    &lt;td class="tg-aiw4"&gt;&lt;img src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg" alt="✅" width="20" height="20"&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/spatial-join-dask-geopandas-sjoin.html"/>
    <summary>This blog explains how to perform a spatial join in Python. Knowing how to perform a spatial join is an important asset in your data-processing toolkit: it enables you to join two datasets based on spatial predicates. For example, you can join a point-based dataset with a polygon-based dataset based on whether the points fall within the polygon.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/introducing-the-dask-active-memory-manager.html</id>
    <title>Introducing the Dask Active Memory Manager</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="introducing-the-dask-active-memory-manager"&gt;

&lt;p&gt;Historically, out-of-memory errors and excessive memory requirements have frequently been a pain point for Dask users. Two of the main causes of memory-related headaches are data duplication and imbalance between workers.&lt;/p&gt;
&lt;p&gt;Dask release 2021.10.0 introduces the first piece of a new modular system called &lt;em&gt;Active Memory Manager&lt;/em&gt;, which aims to alleviate memory issues. The examples in this article use Dask release 2021.11.2, which features improved API and tools.&lt;/p&gt;
&lt;section id="data-duplication"&gt;
&lt;h2&gt;Data duplication&lt;/h2&gt;
&lt;p&gt;Whenever a Dask task returns data, it is stored on the worker that executed the task for as long as it’s referenced by a &lt;em&gt;Future&lt;/em&gt; or another task:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;has_what&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Output: 2&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;has_what&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Output: {&amp;#39;tcp://127.0.0.1:37465&amp;#39;: (&amp;#39;x&amp;#39;,), &amp;#39;tcp://127.0.0.1:44715&amp;#39;: ()}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When a task runs on a worker and requires as its input data that was returned by another task on a different worker, Dask will transparently transfer the data between workers, ending up with multiple copies of the same data on different workers. This is generally desirable, as it avoids re-transferring the data if it’s required again later on. However, it also causes increased overall memory usage across the cluster:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Output: 3&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;has_what&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Output: {&amp;#39;tcp://127.0.0.1:37465&amp;#39;: (&amp;#39;x&amp;#39;,), &amp;#39;tcp://127.0.0.1:44715&amp;#39;: (&amp;#39;x&amp;#39;, &amp;#39;y&amp;#39;)}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="memory-imbalances"&gt;
&lt;h2&gt;Memory imbalances&lt;/h2&gt;
&lt;p&gt;Dask assigns tasks to workers following criteria of CPU occupancy, locality, and worker resources. This design aims to optimize the overall computation time; as a side effect, however, it can lead to some workers ending up with substantially higher memory usage than others.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-active-memory-manager"&gt;
&lt;h2&gt;The Active Memory Manager&lt;/h2&gt;
&lt;p&gt;Active Memory Manager, or &lt;em&gt;AMM&lt;/em&gt; for short, is a background routine that periodically runs in the scheduler and that copies, moves, and removes data on the Dask cluster. It is a plugin-based design where the core component listens to and then enacts &lt;em&gt;suggestions&lt;/em&gt; from a list of policies. In other words, the policies implement arbitrarily sophisticated decisions regarding where data should be, while the AMM core component makes sure that these decisions are put in practice without compromising data integrity.&lt;/p&gt;
&lt;p&gt;At the moment of writing, the Active Memory Manager is shipped with a single built-in policy, &lt;strong&gt;ReduceReplicas&lt;/strong&gt;, which cleans duplicated data on the workers when it’s no longer necessary.&lt;/p&gt;
&lt;p&gt;When it comes to choosing which copies to preserve or create and which to delete, the AMM always prefers preserving/creating on the workers with the lowest memory usage and deleting on those with the highest - thus rebalancing the cluster as it runs.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="enabling-the-amm"&gt;
&lt;h2&gt;Enabling the AMM&lt;/h2&gt;
&lt;p&gt;Since it is an experimental feature, the Active Memory Manager is disabled by default.&lt;/p&gt;
&lt;p&gt;The simplest way to turn it on is through the dask config:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;distributed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can do the same with an environment variable before you start the scheduler:&lt;/p&gt;
&lt;div class="highlight-shell notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DASK_DISTRIBUTED__SCHEDULER__ACTIVE_MEMORY_MANAGER__START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True
dask-scheduler
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Or if you’re using &lt;a class="reference external" href="https://coiled.io"&gt;Coiled&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;DASK_DISTRIBUTED__SCHEDULER__ACTIVE_MEMORY_MANAGER__START&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;True&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can also enable/disable/test it on the fly from the dask client:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Output: True&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Output: False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With any of the above methods, the AMM will run all default policies (at the moment, just ReduceReplicas) every 2 seconds. If you’re a power user, you can further alter the Dask configuration to change the run interval, cherry-pick and configure individual policies, or write your own custom policies.&lt;/p&gt;
&lt;p&gt;You can read the full documentation at &lt;a class="reference external" href="https://distributed.dask.org/en/latest/active_memory_manager.html"&gt;https://distributed.dask.org/en/latest/active_memory_manager.html&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="a-demonstration-with-dot-product"&gt;
&lt;h2&gt;A demonstration with dot product&lt;/h2&gt;
&lt;p&gt;The dot product between two matrices is a O(n²) problem where each point of the matrices needs to be paired with each other:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;da&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;40_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40_000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A is a square matrix worth 12 GiB in total, split across 122 MiB chunks:&lt;/p&gt;
&lt;img alt="Image" src="https://docs.coiled.io/_images/633f7b5e3269c1bf809fe758_image-700x285.png" /&gt;
&lt;p&gt;If you run b.compute() on either the local threaded scheduler or on a single distributed threaded worker, the peak memory usage will be roughly 19 GiB; this is because no copies of the data need to be moved across workers. If you run the same problem on a cluster with multiple workers, however, you will necessarily incur data duplication. While the individual workers can mount a lot less than in the local use case, you should expect the cluster-wide occupation (the sum of the occupation on all the workers) to be substantially higher.&lt;/p&gt;
&lt;p&gt;Let’s use a free trial account on Coiled to quickly set up a cluster with 48 workers and 96 CPUs:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;DASK_DISTRIBUTED__SCHEDULER__ACTIVE_MEMORY_MANAGER__START&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;True&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s test that the Active Memory Manager is running:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Output: True&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’re going to use &lt;a class="reference external" href="https://distributed.dask.org/en/latest/diagnosing-performance.html#analysing-memory-usage-over-time"&gt;MemorySampler&lt;/a&gt; (new in Dask 2021.11.2) to record memory usage on the cluster:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;distributed.diagnostics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySampler&lt;/span&gt;
&lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MemorySampler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then we’re going to run our computation on the cluster and fetch the history of our memory usage:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;AMM on&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now let’s do it again, but this time without the Active Memory Manager:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;AMM off&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Finally, let’s plot the data to see the difference:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Image 1" src="https://docs.coiled.io/_images/633f7b5e3269c133bc9fe757_image-1-700x556.png" /&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_pandas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;

&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Output:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# With AMM     135.177975&lt;/span&gt;
&lt;span class="c1"&gt;# No AMM       165.925610&lt;/span&gt;
&lt;span class="c1"&gt;# dtype: float64&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The Active Memory Manager reduced the peak cluster-wide memory usage from 166 GiB to 135 GiB, with no runtime degradation!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="next-steps"&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;ReduceReplicas is just the first of a series of AMM policies that will be released in the future:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Worker retirement&lt;/strong&gt; is being reimplemented to run on top of the AMM. The key benefit to this is that it will become possible to gracefully retire a worker on a busy cluster, while jobs are running on it. When a worker runs out of memory, it is automatically retired and restarted; this change will prevent random crashes in the jobs when that happens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Graceful worker retirement will also let you &lt;strong&gt;partially downscale an adaptive cluster&lt;/strong&gt; before it’s completely idle; this will potentially result in substantial monetary savings whenever a job features an initial burst of parallelism followed by a long “tail” of serial tasks, or when small jobs are continuously pushed to the cluster and there’s a spike in usage (more jobs, or a much larger job) at a certain time of the day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Worker pause&lt;/strong&gt; (which, by default, is triggered when a worker reaches 80% memory usage) is being redesigned to gracefully transition into retirement after a timeout expires.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rebalance&lt;/strong&gt; is being reimplemented as an AMM policy and will automatically run every two seconds, while computations are running and without any need for user intervention. The manual method &lt;a class="reference external" href="https://distributed.dask.org/en/latest/api.html?highlight=rebalance#distributed.Client.rebalance"&gt;Client.rebalance&lt;/a&gt; is going to be phased out.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Replicate&lt;/strong&gt; is also becoming an AMM policy. Today, &lt;a class="reference external" href="https://distributed.dask.org/en/latest/api.html?highlight=replicate#distributed.Client.replicate"&gt;Client.replicate&lt;/a&gt; synchronously creates replicas, but does not track them after it returns; if a replica is lost later on, nothing will regenerate it unless the user manually invokes replicate() again. In the future, Client.replicate will inform the AMM of the desired number of replicas and immediately return; the AMM will start tracking them and ensure that, whenever a replica is lost, it is recreated somewhere else (as long as at least one copy of the data survives).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’ll also be able to ask for a key to be replicated on all workers, including those that will join the cluster in the future.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="thanks-for-reading"&gt;
&lt;h2&gt;Thanks for reading!&lt;/h2&gt;
&lt;p&gt;If you’d like to scale your Dask work to the cloud, check out Coiled — Coiled provides quick and on-demand Dask clusters along with tools to manage environments, teams, and costs.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/introducing-the-dask-active-memory-manager.html"/>
    <summary>Historically, out-of-memory errors and excessive memory requirements have frequently been a pain point for Dask users. Two of the main causes of memory-related headaches are data duplication and imbalance between workers.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-dataframe-merge-join.html</id>
    <title>How to Merge Dask DataFrames</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="how-to-merge-dask-dataframes"&gt;

&lt;iframe src="https://www.loom.com/embed/f30901c501ee442280b50f3d279b2e4f" allowfullscreen frameborder="0" style="width: 100%; height: 400px;"&gt;&lt;/iframe&gt;
&lt;p&gt;This post demonstrates how to merge Dask DataFrames and discusses important considerations when making large joins.&lt;/p&gt;
&lt;p&gt;You’ll learn:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;how to join a large Dask DataFrame to a small pandas DataFrame&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how to join two large Dask DataFrames&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how to structure your joins for optimal performance&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lessons in this post will help you execute your data pipelines faster and more reliably, enabling you to deliver value to your clients in shorter cycles.&lt;/p&gt;
&lt;p&gt;Let’s start by diving right into the Python syntax and then build reproducible data science examples you can run on your machine.&lt;/p&gt;
&lt;section id="dask-dataframe-merge"&gt;
&lt;h2&gt;Dask Dataframe Merge&lt;/h2&gt;
&lt;p&gt;You can join a Dask DataFrame to a small pandas DataFrame by using the dask.dataframe.merge() method, similar to the pandas api.&lt;/p&gt;
&lt;p&gt;Below we create a Dask DataFrame with multiple partitions and execute a left join with a small pandas DataFrame:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="c1"&gt;# create sample large pandas dataframe&lt;/span&gt;
&lt;span class="n"&gt;df_large&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Azza&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Brandon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Cedric&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Devonte&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Eli&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fabio&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
        &lt;span class="s2"&gt;&amp;quot;Age&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# create multi-partition dask dataframe from pandas&lt;/span&gt;
&lt;span class="n"&gt;large&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df_large&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# create sample small pandas dataframe&lt;/span&gt;
&lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Azza&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Cedric&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Fabio&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
        &lt;span class="s2"&gt;&amp;quot;City&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Beirut&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Dublin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Rosario&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# merge dask dataframe to pandas dataframe&lt;/span&gt;
&lt;span class="n"&gt;join&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# inspect results&lt;/span&gt;
&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/633f7b60fbdbe1b42ece6904_9761-pasted-image-0.png" /&gt;&lt;/p&gt;
&lt;p&gt;To join two large Dask DataFrames, you can use the exact same Python syntax. If you are planning to run repeated joins against a large Dask DataFrame, it’s best to sort the Dask DataFrame using the .set_index() method first to improve performance.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;large_join&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;also_large&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="dask-dataframe-merge-to-a-small-pandas-dataframe"&gt;
&lt;h2&gt;Dask DataFrame merge to a small pandas DataFrame&lt;/h2&gt;
&lt;p&gt;Dask DataFrames are divided into multiple partitions. Each partition is a pandas DataFrame with its own index. Merging a Dask DataFrame to a pandas DataFrame is therefore an embarrassingly parallel problem. Each partition in the Dask DataFrame can be joined against the single small pandas DataFrame without incurring overhead relative to normal pandas joins.&lt;/p&gt;
&lt;p&gt;Let’s demonstrate with a reproducible Python code example. Import dask.dataframe and pandas and then load in the datasets from the public Coiled Datasets S3 bucket. This time the Dask DataFrame is &lt;em&gt;actually&lt;/em&gt; large and not just a placeholder: it contains a 35GB dataset of time series data. This means the data is too large to run with pandas on almost all machines.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt; 

&lt;span class="c1"&gt;# create dataframes&lt;/span&gt;
&lt;span class="n"&gt;large&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/dask-merge/large.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/dask-merge/small.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# inspect large dataframe&lt;/span&gt;
&lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/633f7b60fbdbe14c55ce6905_9761-pasted-image-2.png" /&gt;&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# check number of partitions&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;
&lt;span class="mi"&gt;359&lt;/span&gt;

&lt;span class="c1"&gt;# inspect small dataframe&lt;/span&gt;
&lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/633f7b60fbdbe14f37ce68fc_9761-pasted-image-3.png" /&gt;&lt;/p&gt;
&lt;p&gt;The large dataframe contains a large dataset of synthetic time series data with entries at a frequency of 1 second. The small dataframe contains synthetic data over the same time interval but at a frequency of one entry per day.&lt;/p&gt;
&lt;p&gt;Let’s execute a left join on the timestamp column by calling dask.dataframe.merge(). We’ll use large as the left dataframe.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;timestamp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/633f7b60fbdbe146cece6903_9761-pasted-image-4.png" /&gt;&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;As expected, the column z is filled with NaN for all entries except the first per-second entry of every day.&lt;/p&gt;
&lt;p&gt;If you’re working with a small Dask DataFrame instead of a pandas DataFrame, you have two options. You can &lt;a class="reference external" href="https://coiled.io/blog/converting-a-dask-dataframe-to-a-pandas-dataframe/"&gt;convert it into a pandas DataFrame&lt;/a&gt; using .compute(). This will load the DataFrame into memory. Alternatively, if you can’t or don’t want to load it into your single machine memory, you can turn the small Dask DataFrame into a single partition by &lt;a class="reference external" href="https://coiled.io/blog/repartition-dataframe/"&gt;using the .repartition() method&lt;/a&gt; instead. These two operations are programmatically equivalent which means there’s no meaningful difference in performance between them. Rule of thumb here is to keep your Dask partitions under 100MB each.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# turn dask dataframe into pandas dataframe&lt;/span&gt;
&lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# OR turn dask dataframe into one partition&lt;/span&gt;
&lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="merge-two-large-dask-dataframes"&gt;
&lt;h2&gt;Merge two large Dask DataFrames&lt;/h2&gt;
&lt;p&gt;You can merge two large Dask DataFrames with the same .merge() API syntax.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;large_joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;also_large&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;timestamp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;However, merging two large Dask DataFrames requires careful consideration of your data structure and the final result you’re interested in. Joins are expensive operations, especially in a distributed computing context. Understanding both your data and your desired end result can help you set up your computations efficiently to optimize performance. The most important consideration is whether and how to set your DataFrame’s index before executing the join. Note that in the previous example, the timestamp column is the index for both dataframes.&lt;/p&gt;
&lt;section id="unsorted-vs-sorted-joins"&gt;
&lt;h3&gt;Unsorted vs Sorted Joins&lt;/h3&gt;
&lt;p&gt;As explained above, Dask DataFrames are divided into partitions, where each single partition is a pandas DataFrame. Dask can track how the data is partitioned (i.e. where one partition starts and the next begins) using a DataFrame’s divisions.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://docs.coiled.io/_images/633f7b60fbdbe1941ece6906_9761-pasted-image-5.png" /&gt;&lt;/p&gt;
&lt;p&gt;If a Dask DataFrame’s divisions are known, then Dask knows the minimum value of every partition’s index and the maximum value of the last partition’s index. This enables Dask to take efficient shortcuts when looking up specific values. Instead of searching the entire dataset, it can find out &lt;em&gt;which partition&lt;/em&gt; the value is in by looking at the divisions and then limit its search to only that &lt;em&gt;specific partition&lt;/em&gt;. This is called a sorted join. The join stored in large_joined we executed above is an example of a sorted join since the timestamp column is the index for both of the dataframes in that join.&lt;/p&gt;
&lt;p&gt;Let’s look at another example. The divisions of the DataFrame df below are known: it has 4 divisions. This means that if we look up the row with index 2015-02-12, Dask will only search the 2nd partition and won’t bother with the other three. In reality, Dask Dataframes often have hundreds or even thousands of partitions, which means the benefit of knowing where to look for a specific value becomes even greater.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;known_divisions&lt;/span&gt;
&lt;span class="c1"&gt;# True&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;
&lt;span class="c1"&gt;# 4&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt;
&lt;span class="c1"&gt;# [&amp;#39;2015-01-01&amp;#39;, &amp;#39;2015-02-01&amp;#39;, &amp;#39;2015-03-01&amp;#39;, &amp;#39;2015-04-01&amp;#39;, &amp;#39;2015-04-31&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If divisions are not known, then Dask will need to move all of your data around so that rows with matching values in the joining columns end up in the same partition. This is called an &lt;em&gt;unsorted&lt;/em&gt; &lt;em&gt;join&lt;/em&gt; and it’s an extremely memory-intensive process, especially if your machine runs out of memory and Dask will have to read and write data to disk instead. This is a situation you want to avoid. Read more about unsorted large to large joins &lt;a class="reference external" href="https://docs.dask.org/en/latest/dataframe-joins.html#large-to-large-unsorted-joins"&gt;in the Dask documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="sorted-join-with-set-index"&gt;
&lt;h3&gt;Sorted Join with set index&lt;/h3&gt;
&lt;p&gt;To perform a sorted join of two large Dask DataFrames, you will need to ensure that the DataFrame’s divisions are known by setting the DataFrame’s index. You can set a Dask DataFrame’s index and pass it the known divisions using:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# use set index to get divisions&lt;/span&gt;
&lt;span class="n"&gt;dask_divisions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt;
&lt;span class="n"&gt;unique_divisions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromkeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dask_divisions&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="c1"&gt;# apply set index to both dataframes&lt;/span&gt;
&lt;span class="n"&gt;large_sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;divisions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;unique_divisions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;also_large_sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;also_large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;divisions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;unique_divisions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;large_join&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large_sorted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;also_large_sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;left_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;right_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Note that setting the index is itself also an expensive operation. The rule of thumb here is that if you’re going to be joining against a large Dask DataFrame &lt;em&gt;more than once&lt;/em&gt;, it’s a good idea to set that DataFrame’s index first. Read our blog about &lt;a class="reference external" href="https://coiled.io/blog/dask-set-index-dataframe/"&gt;setting a Dask DataFrame index&lt;/a&gt; to learn more about how and when to do this.&lt;/p&gt;
&lt;p&gt;It’s good practice to write sorted DataFrames to the Apache Parquet file format in order to preserve the index. If you’re not familiar with Parquet, then you might want to check out our blog about &lt;a class="reference external" href="https://coiled.io/blog/parquet-file-column-pruning-predicate-pushdown/"&gt;the advantages of Parquet for Dask analyses&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="joining-along-a-non-index-column"&gt;
&lt;h3&gt;Joining along a non-Index column&lt;/h3&gt;
&lt;p&gt;You may find yourself in the situation of wanting to perform a join between two large Dask DataFrames along a column that is &lt;em&gt;not the index&lt;/em&gt;. This is basically the same situation as not having set the index (i.e. an unsorted join) and will require a complete data shuffle, which is an expensive operation. Ideally you’ll want to think about your computation in advance and set the index right from the start. The Open Source Engineering team at Coiled is working actively to improve shuffling. Read the &lt;a class="reference external" href="https://coiled.io/blog/better-shuffling-in-dask-a-proof-of-concept/"&gt;Proof of Concept for better shuffling in Dask if you’d like to learn more.&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="sorted-join-fails-locally-memoryerror"&gt;
&lt;h3&gt;Sorted Join Fails Locally (MemoryError)&lt;/h3&gt;
&lt;p&gt;Even a sorted join may fail locally if the datasets are simply too large for your local machine. For example, this join:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;large&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/dask-merge/large.parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;also_large&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datasets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeseries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1990-01-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2020-01-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partition_freq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1M&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtypes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;large_join&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;also_large&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;left_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;right_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;large_join&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Will throw an error when run on a laptop with 32GB of RAM or less.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note that we didn’t set the index explicitly here because the index of large is preserved in the Parquet file format and dask.datasets.timeseries() automatically sets the index when creating the synthetic data.&lt;/em&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="run-massive-joins-on-dask-cluster"&gt;
&lt;h3&gt;Run Massive Joins on Dask Cluster&lt;/h3&gt;
&lt;p&gt;When this happens, you can scale out to a Dask cluster in the cloud with Coiled and run the join there in 3 steps:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Spin up a Coiled cluster:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;worker_memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;16Gib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;&lt;p&gt;Connect Dask to the running cluster:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;&lt;p&gt;Run the massive join on 50 workers’ multiple cores in parallel:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;large_join&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;also_large&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;left_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
     &lt;span class="n"&gt;right_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="n"&gt;joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;large_join&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;distributed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# CPU times: user 385 ms, sys: 38.5 ms, total: 424 ms&lt;/span&gt;
&lt;span class="c1"&gt;# Wall time: 14.6 s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This Coiled cluster with 50 workers can run a join of two 35GB DataFrames in 14.6s.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="dask-dataframe-merge-summary"&gt;
&lt;h2&gt;Dask Dataframe Merge Summary&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;You can merge a Dask DataFrame to a small pandas DataFrame using the merge method. This is an embarrassingly parallel problem that requires little to no extra overhead compared to a regular pandas join.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can merge two large Dask DataFrames using the same merge method. Think carefully about whether to run an unsorted join or a sorted join using set_index first to speed up the join.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For very large joins, delegate computations to a Dask cluster in the cloud with Coiled for high-performance joins using parallel computing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-dataframe-merge-join.html"/>
    <summary>This post demonstrates how to merge Dask DataFrames and discusses important considerations when making large joins.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/how-to-convert-a-pandas-dataframe-into-a-dask-dataframe.html</id>
    <title>How to Convert a pandas Dataframe into a Dask Dataframe</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="how-to-convert-a-pandas-dataframe-into-a-dask-dataframe"&gt;

&lt;p&gt;In this post, we will cover:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;How (and when) to convert a pandas DataFrame into a Dask DataFrame;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Demonstrating 2x (or more!) speedup with an example;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Discuss some best practices.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;pandas is a very powerful Python library for manipulating and analyzing structured data, but it has some limitations. pandas does not leverage parallel computing capabilities of modern systems and throws a MemoryError when you try to read larger-than-memory data. Dask can help solve these limitations. &lt;a class="reference external" href="https://coiled.io/blog/what-is-dask/"&gt;Dask&lt;/a&gt; is a library for parallel and distributed computing in Python. It provides a collection called Dask DataFrame that helps parallelize the pandas API.&lt;/p&gt;
&lt;p&gt;This data is stored in a public bucket and you can use Coiled for free if you’d like to run these computations yourself.&lt;/p&gt;
&lt;p&gt;You can follow along with the code in &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/master/pandas-to-dask-dataframe/pandas-to-dask-dataframe.ipynb"&gt;this notebook&lt;/a&gt;!&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/l9c08OAT7jY" title="How to Convert a pandas Dataframe into a Dask Dataframe | Pavithra Eswaramoorthy" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;section id="analyzing-library-data-with-pandas"&gt;
&lt;h2&gt;Analyzing library data with pandas&lt;/h2&gt;
&lt;p&gt;Let’s start with a sample pandas workflow of reading data into a pandas DataFrame and doing a groupby operation. We read a subset of the &lt;a class="reference external" href="https://www.kaggle.com/city-of-seattle/seattle-checkouts-by-title?select=checkouts-by-title.csv"&gt;Seattle Public Library checkouts dataset&lt;/a&gt; (~4GB) and find the total number of items checked out based on the ‘Usage Class’ (i.e., physical vs. digital).&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;checkouts-subset.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UsageClass&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Checkouts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# ~1.2 seconds&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The computation takes about 1.2 seconds on my computer. Can we speed this up using Dask DataFrame?&lt;/p&gt;
&lt;/section&gt;
&lt;section id="parallelizing-using-dask-dataframe"&gt;
&lt;h2&gt;Parallelizing using Dask DataFrame&lt;/h2&gt;
&lt;p&gt;To leverage single-machine parallelism for this analysis, we can convert the pandas DataFrame into a Dask DataFrame. A Dask DataFrame consists of multiple pandas DataFrames split across an index as shown in the following diagram:&lt;/p&gt;
&lt;p&gt;![](/blog/images/webflow-images/638657ff104d4050037d09ec_DataFrame Image - Blue 2&amp;#64;3x.png)&lt;/p&gt;
&lt;p&gt;We can use Dask’s from_pandas function for this conversion. This function splits the in-memory pandas DataFrame into multiple sections and creates a Dask DataFrame. We can then operate on the Dask DataFrame in parallel using its pandas-like interface. Note that we need to specify the number of partitions or the size of each chunk while converting.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This conversion might take a minute, but it’s a one-time cost. We can now perform the same computation as earlier:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UsageClass&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Checkouts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# ~679 ms&lt;/span&gt;
 &lt;span class="n"&gt;seconds&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This took ~680 milliseconds on my machine. That’s more than twice as fast as pandas!&lt;/p&gt;
&lt;p&gt;It’s important to note here that Dask DataFrame provides parallelism, but it may not always be faster than pandas. For example, operations that require sorting take more time because data needs to be moved between different sections. We suggest sticking with pandas if your dataset fits in memory.&lt;/p&gt;
&lt;p&gt;If you work with sizable data, it is likely stored on a remote system across multiple files, and not on your local machine. Dask allows you to read this data in parallel, which can be much faster than reading with pandas.&lt;/p&gt;
&lt;p&gt;You can read data into a Dask DataFrame directly using Dask’s read_csv function:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/checkouts-subset.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Both pandas and Dask also support several file-formats, including Parquet and HDF5, data formats optimized for scalable computing. You can check out the list of formats in the &lt;a class="reference external" href="https://pandas.pydata.org/docs/user_guide/io.html#io"&gt;pandas documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="using-dask-distributed-scheduler"&gt;
&lt;h2&gt;Using Dask  Distributed Scheduler&lt;/h2&gt;
&lt;p&gt;Dask DataFrame uses&lt;a class="reference external" href="https://docs.dask.org/en/latest/scheduling.html#local-threads"&gt; the single-machine threaded scheduler&lt;/a&gt; by default, which works well for local workflows. Dask also provides a &lt;a class="reference external" href="https://docs.dask.org/en/latest/scheduling.html#dask-distributed-local"&gt;distributed scheduler&lt;/a&gt; with a lot more features and optimizations. It can be useful even for local development. For instance, you can take advantage of Dask’s diagnostic dashboards. To use the distributed scheduler, use:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then, perform your Dask computations as usual:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://coiled-datasets.s3.us-east-2.amazonaws.com/seattle-library-checkouts/checkouts-subset.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UsageClass&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Checkouts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask is also capable of distributed computing in the cloud and we’re building a service, Coiled, that allows you to deploy Dask clusters on the cloud effortlessly. Coiled offers a free tier with up to 100 CPU cores and 1000 CPU hours of computing time per month. Try it out today, and let us know how it goes!&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/how-to-convert-a-pandas-dataframe-into-a-dask-dataframe.html"/>
    <summary>In this post, we will cover:</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/how-coiled-sets-memory-limit-for-dask-workers.html</id>
    <title>How Coiled sets memory limit for Dask workers</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="how-coiled-sets-memory-limit-for-dask-workers"&gt;

&lt;section id="problem"&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;While running workloads to test Dask reliability, we noticed that some workers were freezing or dying when the OS stepped in and started killing processes when the system ran out of memory.&lt;/p&gt;
&lt;p&gt;Having Dask workers die from memory overuse is common, so we thought that we’d investigate this further.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Dask manages memory, so it needs to know how much memory it has available.&lt;/p&gt;
&lt;p&gt;Setting the memory limit correctly is important to the performance of your Dask cluster. This memory limit affects when Dask spills data in memory to disk, and when it restarts workers that are out of memory.&lt;/p&gt;
&lt;p&gt;When the memory limit is set &lt;strong&gt;too low&lt;/strong&gt;, Dask won’t use as much memory as it could. That’s not the worst thing, but it means your work will potentially take longer than it needs to.&lt;/p&gt;
&lt;p&gt;When the memory limit is set &lt;strong&gt;too high&lt;/strong&gt;, Dask can fail to spill to disk when needed and fail to restart workers when they’re out of memory. This is bad because if a worker isn’t killed when it’s out of memory, then it can freeze and potentially your cluster will be stuck waiting for the work from this worker. If the worker is restarted, then Dask will retry the work.&lt;/p&gt;
&lt;p&gt;Dask attempts to determine the correct memory limit to use. It does this by looking at the &lt;strong&gt;total system memory&lt;/strong&gt; (including virtual memory) and the &lt;strong&gt;cgroup memory limit&lt;/strong&gt; if set. (Dask also looks at &lt;a class="reference external" href="https://docs.python.org/3/library/resource.html#resource.RLIMIT_RSS"&gt;RSS rlimit&lt;/a&gt; if set). You can also explicitly set the memory limit. Dask then uses the minimum of these values.&lt;/p&gt;
&lt;p&gt;The upshot is that in many deployments—including how we had been deploying Dask with Coiled—the memory limit will be set to the total system memory. That’s not ideal because that limit is &lt;strong&gt;too high&lt;/strong&gt;: we’re also running an OS and logging agents.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="solution"&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;To address this, Coiled now sets the &lt;strong&gt;cgroup memory limit&lt;/strong&gt; on the containers running dask. We set this to the available memory on the system &lt;strong&gt;&lt;em&gt;before&lt;/em&gt;&lt;/strong&gt; Dask starts running.&lt;/p&gt;
&lt;p&gt;Here’s how we’ve implemented this.&lt;/p&gt;
&lt;p&gt;First, our script that starts Dask on each VM now sets an environment variable with the available memory:&lt;/p&gt;
&lt;p&gt;&lt;a href="#" class="copy-button button-2 w-button" fs-copyclip-element="click" fs-copyclip-message="Copied" fs-copyclip-duration="3000"&gt;Copy&lt;/a&gt;&lt;/p&gt;
&lt;pre class="language-python"&gt;&lt;code class="language-python hljs" fs-copyclip-element="copy-this"&gt;export AVAILABLE_MEMORY_FOR_DASK=$(grep -i memavailable /proc/meminfo | sed 's/[a-zA-Z]+:\s+//')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we’ve modified the Docker compose file we use to start Dask (we run Dask in containers, and configure the containers using Docker compose). We’ve added a line to explicitly set &lt;strong&gt;mem_limit&lt;/strong&gt; for the container to ${AVAILABLE_MEMORY_FOR_DASK:-0}.&lt;/p&gt;
&lt;p&gt;We could have just used $AVAILABLE_MEMORY_FOR_DASK, but instead we’re setting a default value of 0 (i.e., no limit) just in case the environment variable isn’t set. Mostly that’s unnecessary, but it makes deployment safer because the script that sets the environment variable is part of our custom AMI while the Docker compose file is created for each cluster by our control-plane.&lt;/p&gt;
&lt;p&gt;With a Dask client connected to your Coiled (or other Dask) cluster, you can check the value of the memory limit Dask is using by running:&lt;/p&gt;
&lt;p&gt;&lt;a href="#" class="copy-button button-2 w-button" fs-copyclip-element="click" fs-copyclip-message="Copied" fs-copyclip-duration="3000"&gt;Copy&lt;/a&gt;&lt;/p&gt;
&lt;pre class="language-python"&gt;&lt;code class="language-python hljs" fs-copyclip-element="copy-this"&gt;client.run(lambda dask_worker: dask_worker.memory_manager.memory_limit)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The upshot is that since we’ve made this change, Dask running on Coiled now does a better job spilling to disk and restarting workers when appropriate, and we’re seeing increased reliability from our testing.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/how-coiled-sets-memory-limit-for-dask-workers.html"/>
    <summary>While running workloads to test Dask reliability, we noticed that some workers were freezing or dying when the OS stepped in and started killing processes when the system ran out of memory.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-filter-dataframes-loc.html</id>
    <title>Filtering Dask DataFrames with loc</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="filtering-dask-dataframes-with-loc"&gt;

&lt;p&gt;This post explains how to filter Dask DataFrames based on the DataFrame index and on column values using loc.&lt;/p&gt;
&lt;p&gt;Filtering Dask DataFrames can cause data to be unbalanced across partitions which isn’t desirable from a performance perspective. This post illustrates how filtering can cause the “empty partition problem” and how to eliminate empty partitions with repartitioning.&lt;/p&gt;
&lt;p&gt;The blog starts with minimal complete verifiable examples (&lt;a class="reference external" href="https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports"&gt;MVCEs)&lt;/a&gt; filtering examples on small datasets and then shows how to filter DataFrames with millions of rows of data on a cluster.&lt;/p&gt;
&lt;p&gt;This data is stored in a public bucket and you can use Coiled for free if you’d like to run these computations yourself.&lt;/p&gt;
&lt;p&gt;Here’s the &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/master/filter-dataframes/filter-examples.ipynb"&gt;link to the notebook&lt;/a&gt; with all the code snippets used in this post.&lt;/p&gt;
&lt;section id="dask-filter-index-filtering"&gt;
&lt;h2&gt;Dask Filter: Index filtering&lt;/h2&gt;
&lt;p&gt;Dask DataFrames consist of multiple partitions, each of which is a pandas DataFrame. Each pandas DataFrame has an index. Dask allows you to filter multiple pandas DataFrames on their index in parallel, which is quite fast.&lt;/p&gt;
&lt;p&gt;Let’s create a Dask DataFrame with 6 rows of data organized in two partitions.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.data&lt;/span&gt;
&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;letters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s visualize the data in each partition.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
	&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
	&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;     &lt;span class="mi"&gt;1&lt;/span&gt;       &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;     &lt;span class="mi"&gt;2&lt;/span&gt;       &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;     &lt;span class="mi"&gt;3&lt;/span&gt;       &lt;span class="n"&gt;c&lt;/span&gt;   
	&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;     &lt;span class="mi"&gt;4&lt;/span&gt;       &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;     &lt;span class="mi"&gt;5&lt;/span&gt;       &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="mi"&gt;6&lt;/span&gt;       &lt;span class="n"&gt;f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask automatically added an integer index column to our data.&lt;/p&gt;
&lt;p&gt;Grab rows 2 and 5 from the DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
	&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;     &lt;span class="mi"&gt;3&lt;/span&gt;       &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="mi"&gt;6&lt;/span&gt;       &lt;span class="n"&gt;f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Grab rows 3, 4, and 5 from the DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
	&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;     &lt;span class="mi"&gt;4&lt;/span&gt;       &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;     &lt;span class="mi"&gt;5&lt;/span&gt;       &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="mi"&gt;6&lt;/span&gt;       &lt;span class="n"&gt;f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s learn more about how Dask tracks information about divisions in sorted DataFrames to perform loc filtering efficiently.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-filter-divisions"&gt;
&lt;h2&gt;Dask Filter: Divisions&lt;/h2&gt;
&lt;p&gt;Dask is aware of the starting and ending index value for each partition in the DataFrame and stores this division’s metadata to perform quick filtering.&lt;/p&gt;
&lt;p&gt;You can verify that Dask is aware of the divisions for this particular DataFrame by running ddf.known_divisions and seeing it returns True. Dask isn’t always aware of the DataFrame divisions.&lt;/p&gt;
&lt;p&gt;Print all the divisions of the DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;divisions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Take a look at the values in each partition of the DataFrame to better understand this division’s output.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
	&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partitions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  
	&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;     &lt;span class="mi"&gt;1&lt;/span&gt;       &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;     &lt;span class="mi"&gt;2&lt;/span&gt;       &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;     &lt;span class="mi"&gt;3&lt;/span&gt;       &lt;span class="n"&gt;c&lt;/span&gt;   
	&lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;     &lt;span class="mi"&gt;4&lt;/span&gt;       &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;     &lt;span class="mi"&gt;5&lt;/span&gt;       &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="mi"&gt;6&lt;/span&gt;       &lt;span class="n"&gt;f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The first division is from 0-3, and the second division is from 3-5. This means the first division contains rows 0 to 2, and the last division contains rows 3 to 5.&lt;/p&gt;
&lt;p&gt;Dask’s division awareness in this example lets it know exactly what partitions it needs to fetch from when filtering.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-filter-column-value-filtering"&gt;
&lt;h2&gt;Dask Filter: Column value filtering&lt;/h2&gt;
&lt;p&gt;You won’t always be able to filter based on index values. Sometimes you need to filter based on actual column values.&lt;/p&gt;
&lt;p&gt;Fetch all rows in the DataFrame where nums is even.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="mi"&gt;3&lt;/span&gt;   &lt;span class="mi"&gt;4&lt;/span&gt;   &lt;span class="n"&gt;d&lt;/span&gt;
    &lt;span class="mi"&gt;5&lt;/span&gt;   &lt;span class="mi"&gt;6&lt;/span&gt;   &lt;span class="n"&gt;f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Find all rows where nums is even, and letters contains either b or f.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;letters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]))]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;nums&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="mi"&gt;5&lt;/span&gt;   &lt;span class="mi"&gt;6&lt;/span&gt;   &lt;span class="n"&gt;f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask makes it easy to apply multiple logic conditions when filtering.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-filter-empty-partition-problem"&gt;
&lt;h2&gt;Dask Filter: Empty partition problem&lt;/h2&gt;
&lt;p&gt;Let’s read a dataset with 662 million rows into a Dask DataFrame and perform a filtering operation to illustrate the empty partition problem.&lt;/p&gt;
&lt;p&gt;Read in the data and create the Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;use_ssl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;ddf.npartitions shows that the DataFrame has 1,095 partitions.&lt;/p&gt;
&lt;p&gt;Run ddf.head() to take a look at the data.&lt;/p&gt;
&lt;p&gt;Here’s how to filter the DataFrame to only include rows with an id greater than 1150.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1150&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Run len(res) to see that the DataFrame only has 1,103 rows after this filtering operation. This was a big filter, and only a small fraction of the original 662 million rows remain.&lt;/p&gt;
&lt;p&gt;We can run res.npartitions to see that the DataFrame still has 1,095 partitions. The filtering operation didn’t change the number of partitions.&lt;/p&gt;
&lt;p&gt;Run res.map_partitions(len).compute() to visually inspect how many rows of data are in each partition.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;   &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;   &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;
&lt;span class="mi"&gt;1090&lt;/span&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="mi"&gt;1091&lt;/span&gt;    &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="mi"&gt;1092&lt;/span&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="mi"&gt;1093&lt;/span&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="mi"&gt;1094&lt;/span&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1095&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;int64&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A lot of the partitions are empty and others only have a few rows of data.&lt;/p&gt;
&lt;p&gt;Dask often works best with partition sizes of at least 100MB. Let’s repartition our data to two partitions and persist it in memory.&lt;/p&gt;
&lt;p&gt;res2 = res.repartition(2).persist()&lt;/p&gt;
&lt;p&gt;Subsequent operations on res2 will be really fast because the data is stored in memory. len(res) takes 57 seconds whereas len(res2) only takes 0.3 seconds.&lt;/p&gt;
&lt;p&gt;The filtered dataset is so small in this example that you could even convert it to a pandas DataFrame with res3 = res.compute(). It only takes 0.000011 seconds to execute len(res3).&lt;/p&gt;
&lt;p&gt;You don’t have to filter datasets at the computation engine level. You can also filter at the database level and only send a fraction of the data to the computation engine.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-filter-query-pushdown"&gt;
&lt;h2&gt;Dask Filter: Query pushdown&lt;/h2&gt;
&lt;p&gt;Query pushdown is when you perform data operations before sending the data to the Dask cluster. Part of the work is “pushed down” to the database level.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://coiled.io/blog/parquet-file-column-pruning-predicate-pushdown/"&gt;See the Advantages of Parquet blog post&lt;/a&gt; for information on column pruning and predicate pushdown filtering, query pushdown for the Parquet file format.&lt;/p&gt;
&lt;p&gt;Here’s the high-level process for filtering with a Dask cluster:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Read all the data from disk into the cluster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Perform the filtering operation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repartition the filtered DataFrame&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Possibly write the result to disk (ETL style workflow) or persist in memory&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Organizations often need to optimize data storage and leverage query pushdown in a manner that’s optimized for their query patterns and latency needs.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-filter-conclusion"&gt;
&lt;h2&gt;Dask Filter: Conclusion&lt;/h2&gt;
&lt;p&gt;Dask makes it easy to filter DataFrames, but you need to be cognizant of the implications of big filters.&lt;/p&gt;
&lt;p&gt;After filtering a lot of data, you should consider repartitioning and persisting the data in memory.&lt;/p&gt;
&lt;p&gt;You should also consider filtering at the database level and bypassing cluster filtering altogether. Lots of Dask analyses run slower than they should because a large filtering operation was performed, and the analyst is running operations on a DataFrame with tons of empty partitions.&lt;/p&gt;
&lt;p&gt;Thanks for reading. If you’re interested in trying out Coiled, which provides hosted Dask clusters, docker-less managed software, and one-click deployments, you can do so for free today when you click below.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-filter-dataframes-loc.html"/>
    <summary>This post explains how to filter Dask DataFrames based on the DataFrame index and on column values using loc.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/enterprise-dask-support.html</id>
    <title>Enterprise Dask Support</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="enterprise-dask-support"&gt;

&lt;p&gt;Along with the Cloud SaaS product, Coiled sells enterprise support for Dask. Mostly people buy this for these three things:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Office hours with Dask experts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Private GitHub issue tracker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Commitment to quickly fix Dask bugs that affect critical workloads should they arise&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Support contracts also come with a variety of other things, like indemnity insurance, response time SLAs, and so on, but these are usually mostly to satisfy procurement checklists rather than drivers on the business side.&lt;/p&gt;
&lt;p&gt;Our contracts list everything out in detail, but I often find myself explaining to customers how things work and why we structure things the way we do. This article will be a more informal description of how we operate and why.&lt;/p&gt;
&lt;section id="who-needs-enterprise-dask-support"&gt;
&lt;h2&gt;Who needs Enterprise Dask Support?&lt;/h2&gt;
&lt;p&gt;This tends to be larger companies that are moderately far along in their Dask journey. They usually have a few groups using Dask and it’s found its way into critical workloads. Often Dask has spread throughout the organization faster than their Dask expertise has grown, and so there are a handful of Dask experts who are somewhat overburdened trying to keep everyone happy and all critical workloads humming nicely.&lt;/p&gt;
&lt;p&gt;We charge mid-six-figure annual deal sizes for our support contracts, so these are usually larger companies.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="how-deep-do-you-get"&gt;
&lt;h2&gt;How deep do you get?&lt;/h2&gt;
&lt;p&gt;We need to strike a balance between getting deep enough with your problem to understand how to help, while staying distant enough that we focus on Dask (what we’re good at) rather than getting sucked into internal customer systems. Usually the customers who want enterprise support contracts are pretty sensitive about having us deeply in their systems, so everyone is aligned with finding good boundaries of communication.&lt;/p&gt;
&lt;p&gt;As a result, we do not offer “hands-on-keyboard” support, where we would go into your company, look at your data, and build ML models / data engineering pipelines for you. Honestly, we’d be pretty bad at that anyway. It’s not our skillset. When folks do need this level of support we bring in consultants that we know and trust. Most often we recommend Quansight who are great at general purpose open source Python work.&lt;/p&gt;
&lt;p&gt;Instead, we find that a combination of a few office hours a month, an issue tracker, and all-you-can-eat OSS bugfixing works well in combination. Let’s talk through these three below:&lt;/p&gt;
&lt;/section&gt;
&lt;section id="office-hours"&gt;
&lt;h2&gt;1 – Office Hours&lt;/h2&gt;
&lt;p&gt;It’s useful to talk to an expert. Many of the problems that our users face can be resolved by pointing them to a project or API of which they weren’t aware. It’s common that the right 30 minute meeting can accelerate weeks of work.&lt;/p&gt;
&lt;p&gt;Contracts typically include a finite number of office hours per month, somewhere between two and ten. Folks can use these however they like, but a typical allocation of ten hours looks like the following:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;One open office hour every week (4hrs)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A lunch and learn style lecture on a topic facing the group every month (1hr + 1hr prep)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A deeper dive on a harder problem encountered by some internal team, often including screensharing going through performance dashboards, logs, etc. (2hr)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These hours are designed to uncover issues and transfer knowledge. Often they result in issues raised for work upstream. This is where the bulk of the value comes.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-critical-bugfixing"&gt;
&lt;h2&gt;2 – Dask Critical BugFixing&lt;/h2&gt;
&lt;p&gt;While office hours are finite and precious, Dask bugfixing is managed in more of an “all-you-can-eat” fashion. Often in office hours we’ll find that Dask was underperforming in some way (Dask is far from perfect) and we can raise this as an issue to be resolved upstream.&lt;/p&gt;
&lt;p&gt;Once we’ve found work to do on the public open source project we get access to a much larger team to work on your problems. As a company we’re mostly focused around discovering high impact work, prioritizing that work, and executing it swiftly. If we can solve your problem by enhancing Dask then all of our incentives are very well aligned.&lt;/p&gt;
&lt;p&gt;If you think in terms of hourly rates, the office hours are expensive (thousands of dollars per hour) while the Dask OSS work is very cheap (often $100 per hour or less). This reflects our perception of costs and our priorities. We spend the office hour time figuring out how to align our company and yours, and then we operate more as partners with a shared goal of improving the open source software.&lt;/p&gt;
&lt;p&gt;Of course, even this isn’t entirely free. Our OSS team is large and skilled, but still finite. We also prioritize based on importance across users. Financially, we’re incentivized to provide enough value to customers so that they want to renew at the end of the year. The larger the contract, the more value we know we need to provide.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="private-github-issue-tracker"&gt;
&lt;h2&gt;3 – Private Github Issue Tracker&lt;/h2&gt;
&lt;p&gt;We need a way to communicate on the ongoing technical work. We find that Github is the easiest and least distracting way to do this. Being outside of the open source tracker provides some privacy for sensitive communication, and also makes it easier for us to prioritize and track things without them getting lost.&lt;/p&gt;
&lt;p&gt;We also jump on video calls on an as-needed basis and, very rarely, have slack channels open with customers. We’ve found that Github works best though to keep things private and also well-tracked.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-we-don-t-do"&gt;
&lt;h2&gt;What we don’t do&lt;/h2&gt;
&lt;p&gt;We’re very efficient at diagnosing Dask issues, but inefficient at diagnosing issues related to customers’ internal systems. This comes up in a few scenarios:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p&gt;“Pods in our in-house Kubernetes system aren’t coming up. It says that it can’t load the image. Why?”&lt;/p&gt;
&lt;p&gt;If we don’t manage your Dask deployment solution then we really can’t do much to help you here. Probably we don’t even have the permissions to diagnose this. We encourage you to try out Coiled’s deployment solution.  It’s pretty slick.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“Our data scientist ran this giant notebook and it’s slow. Can you make it faster?”&lt;/p&gt;
&lt;p&gt;This may have more to do with your code than ours. Your team is likely more efficient at figuring out where the issue is than we are, and they’re a lot cheaper.&lt;/p&gt;
&lt;p&gt;However, we could help here in a few ways:&lt;/p&gt;
&lt;p&gt;a. Getting on a call and looking at Dask diagnostics together, helping to pinpoint what might be going wrong.
b. Teaching your team more about Dask diagnostics and how to use them to identify performance issues.
c. Providing general Python performance advice during office hours.&lt;/p&gt;
&lt;p&gt;But we need to be clear that custom workload performance is out-of-scope for the all-you-can-eat model. We can provide advice and training, but ultimately custom workloads are the customer’s responsibility. If the customer is able to provide a minimal reproducer of the issue or benchmark then we can more effectively take on ownership.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“We’ve made an in-house library around Dask to train models. Can you help us to maintain it?”&lt;/p&gt;
&lt;p&gt;We would likely try to direct you to open source technologies that might do similar things. Alternatively, we could connect you with excellent consultants who can be more hands-on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“We’ve found an issue in dask-kubernetes/dask-gateway/dask-cloudprovider. Can you fix it?”&lt;/p&gt;
&lt;p&gt;This invariably leads us into Problem #1.  We are happy to go over configuration on these tools during office hours, but don’t include them in our all-you-can-eat structure. We’ve found that they’re hard to support in deep enterprise cases. This is why we built the Coiled deployment platform.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“We have many new people to Dask, can you answer all of their questions on Slack?”&lt;/p&gt;
&lt;p&gt;Sure, let’s use an office hour or two and give some broad training. However, we can’t be available for lots of novice Q&amp;amp;A. Instead, we ask that your company selects a small number of folks to route these questions through to us. We find that those folks are usually pretty Dask savvy and are able to provide basic level-1 support. Please think of us more as L2/L3 support.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section id="limited-seats"&gt;
&lt;h2&gt;Limited Seats&lt;/h2&gt;
&lt;p&gt;As the last question implies, we typically limit the number of folks that we talk to in an organization. This tends to be 1-2 people per project (companies paying several hundred thousand annually typically have a few groups using Dask). These people become deep experts and help to buffer us from lots of small problems. As Dask expands in the organization and we add more projects we also add more contact seats and typically start to ask for more money (which is often fine, because the new projects often come in with new budgets).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="sometimes-we-also-do-new-development"&gt;
&lt;h2&gt;Sometimes, we also do new development&lt;/h2&gt;
&lt;p&gt;Sometimes companies come to us with paid feature development requests like “Hey, can you integrate Dask with Library X?”. We consider these, but reject more than we accept. We become much more excited if these are already on our roadmap, and the company wants to help us with design and testing as more of a partner.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="aligned-incentives"&gt;
&lt;h2&gt;Aligned Incentives&lt;/h2&gt;
&lt;p&gt;Over the years Dask has been funded by a wide variety of mechanisms. I really like the support model laid out above. It does a good job of delivering solid value to customers, while also aligning incentives to help make the project better. Everyone focuses on their core skillset and generally has a good time.&lt;/p&gt;
&lt;p&gt;Dask has been developed in this manner for many years. It would not be nearly as pragmatic without the valuable support and technical insight of its support partners throughout the years.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/enterprise-dask-support.html"/>
    <summary>Along with the Cloud SaaS product, Coiled sells enterprise support for Dask. Mostly people buy this for these three things:</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/parallel-python.html</id>
    <title>Easily Run Python Functions in Parallel</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="easily-run-python-functions-in-parallel"&gt;

&lt;p&gt;When you search for how to run a Python function in parallel, one of the first things that comes up is the multiprocessing module. The &lt;a class="reference external" href="https://docs.python.org/3/library/multiprocessing.html"&gt;documentation&lt;/a&gt; describes parallelism in terms of processes versus threads and mentions it can side-step the infamous Python GIL (Global Interpreter Lock).&lt;/p&gt;
&lt;p&gt;This is all great if you’re a Python developer comfortable navigating the intricacies of processes and threads. But what if you don’t want to care about any of that? What if all you want is a straightforward way to run a Python function in parallel?&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Parallel Python" src="https://docs.coiled.io/_images/63587d9db3c62b5abacab6a1_image1-1-700x632.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Different models of concurrency, adapted from &lt;a class="reference external" href="https://realpython.com/python-concurrency"&gt;https://realpython.com/python-concurrency&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Imagine you’re estimating the global burden of malaria. You don’t want to dive into the technicalities of parallelism—you just want to answer a question like, “How many people died of malaria in 1984?”&lt;/p&gt;
&lt;p&gt;That’s where &lt;strong&gt;Dask&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;section id="why-choose-dask-over-multiprocessing"&gt;
&lt;h2&gt;Why Choose Dask Over Multiprocessing?&lt;/h2&gt;
&lt;p&gt;Dask is designed to make parallel computing in Python as seamless as possible. With Dask, you can:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Run Python functions in parallel with &lt;strong&gt;minimal code changes&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easily scale from a single machine to a distributed cluster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focus on your &lt;strong&gt;data and analysis&lt;/strong&gt; instead of debugging processes and threads.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whether you’re analyzing global health data, processing large datasets, or running computational models, Dask lets you spend more time on your problem and less time wrestling with low-level parallelism.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="run-a-python-function-in-parallel"&gt;
&lt;h2&gt;Run a Python Function in Parallel&lt;/h2&gt;
&lt;p&gt;You can use Dask to run any Python function in parallel. Let’s say you have a function called &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;costly_simulation&lt;/span&gt;&lt;/code&gt; that defines a long-running simulation over a set of parameters. With Dask, we can run this in parallel locally on all cores available on your laptop:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                &lt;span class="c1"&gt;# Use all cores on local machine&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;costly_simulation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;costly_simulation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# Run simulation in parallel&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Or scale out to a cluster of many machines on the cloud using Coiled:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;# Start 50 machines on AWS        &lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;costly_simulation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;costly_simulation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# Run simulation in parallel&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled runs from your AWS, Google Cloud, or Azure account and you’ll get 500 free CPU hours each month. It’s free to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;sign up&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="parallel-for-loop-with-dask"&gt;
&lt;h2&gt;Parallel For Loop with Dask&lt;/h2&gt;
&lt;p&gt;Here’s an illustrative example doing a nested for loop over parameters and data files. In this example, we work sequentially, loading the data in the outer loop and scoring in the inner loop for all pairs.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;             &lt;span class="c1"&gt;# Nested for loop&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# Load data in outer loop&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;               &lt;span class="c1"&gt;# Score in inner loop over all pairs&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="c1"&gt;# Get best score&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;Working sequentially can be quite slow, especially if you’re working through hundreds of files. We can use Dask to run this in parallel, using all cores available on your laptop:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                 &lt;span class="c1"&gt;# Use all cores on local machine&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;               &lt;span class="c1"&gt;# Nested for loop&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load data in outer loop&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                 &lt;span class="c1"&gt;# Score in inner loop over all pairs&lt;/span&gt;
        &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      
        &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;# Get best score&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;If your data is stored on the cloud or you need access to hardware you don’t have available locally (like more memory or GPUs) you can use Coiled to run the same workflow across a cluster of VMs:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# Scale out to 100 machines&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s3_filenames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;            &lt;span class="c1"&gt;# Nested for loop&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Load data in outer loop&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                 &lt;span class="c1"&gt;# Score in inner loop over all pairs&lt;/span&gt;
        &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      
        &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;# Get best score&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled handles things like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/index.html"&gt;Python environment management&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/costs/index.html"&gt;Controlling cloud costs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Combining hardware and Python-specific metrics into a single UI&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/554gEk_qHFk" title="Dask Futures for General Parallelism" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="when-to-use-multiprocessing-instead-of-dask"&gt;
&lt;h2&gt;When to Use Multiprocessing Instead of Dask?&lt;/h2&gt;
&lt;p&gt;While Dask is powerful and flexible, there are cases where multiprocessing might be a better choice:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Standard Library: As part of Python’s standard library, multiprocessing requires no additional installation or setup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Low Overhead: For smaller tasks, the overhead of Dask’s scheduler might outweigh its benefits, making multiprocessing a more efficient option. Dask also has a &lt;a class="reference external" href="https://docs.dask.org/en/stable/scheduling.html#local-processes"&gt;multiprocessing scheduler&lt;/a&gt; that is very lightweight for these situations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fine-Grained Control: If you need detailed control over processes, shared memory, or inter-process communication, multiprocessing provides the tools you need. Though it’s worth mentioning Dask is also flexible enough for this level of fine-grained tuning with tools like asynchronous futures, distributed locks, queues, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="examples"&gt;
&lt;h2&gt;Examples&lt;/h2&gt;
&lt;p&gt;If you’re tired of fighting with multiprocessing or just want an easier way to run Python functions in parallel, Dask could be a good option. It’s powerful, intuitive, and designed to help you focus on your work—not the underlying mechanics of parallel computing. For more examples using Dask for parallel Python you might consider the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Process &lt;a class="reference external" href="https://docs.coiled.io/user_guide/arxiv-matplotlib.html"&gt;1 TiB of arXiv S3 data&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get a &lt;em&gt;really&lt;/em&gt; good &lt;a class="reference external" href="https://docs.coiled.io/user_guide/pi.html"&gt;estimation of Pi&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Train an &lt;a class="reference external" href="https://docs.coiled.io/user_guide/xgboost.html"&gt;XGBoost model in parallel&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/parallel-python.html"/>
    <summary>When you search for how to run a Python function in parallel, one of the first things that comes up is the multiprocessing module. The documentation describes parallelism in terms of processes versus threads and mentions it can side-step the infamous Python GIL (Global Interpreter Lock).</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-gcp.html</id>
    <title>Dask on GCP</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="dask-on-gcp"&gt;

&lt;p&gt;‍&lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; is a flexible Python library for parallel and distributed computing. There are a number of ways you can create Dask clusters, each with their own benefits. In this article, we explore how Coiled provides a managed cloud infrastructure solution for deploying Dask on Google Cloud, addressing:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Deployment challenges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Software environment and credential management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hardware flexibility&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Historical tracking and observability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost optimization&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/eXP-YuERvi4" title="Six Coiled features for Dask users" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;section id="how-to-deploy-a-dask-cluster"&gt;
&lt;h2&gt;How to Deploy a Dask Cluster&lt;/h2&gt;
&lt;p&gt;There are a number of tools you can use to manage Dask clusters, including:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html#dask-kubernetes"&gt;Dask Kubernetes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html#dask-gateway"&gt;Dask Gateway&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html#dask-cloudprovider"&gt;Dask Cloud Provider&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In principle, Coiled is just like these other Dask as a service products, in that you can also use Coiled to create a Dask cluster. While this might sound straightforward, there’s significant nuance to deploying a Dask cluster effectively.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="install-packages-and-sync-credentials"&gt;
&lt;h2&gt;Install Packages and Sync Credentials&lt;/h2&gt;
&lt;p&gt;On cluster start, Coiled examines your local machine, identifying pip and conda packages and any development packages you have locally. It packages these libraries and deploys them dynamically on cloud-based Linux machines in your cluster. This approach solves the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/index.html"&gt;software environment matching problem&lt;/a&gt; between your local and remote software environments. Additionally, Coiled securely transfers your local cloud credentials to the workers, ensuring they can access your cloud resources, like S3 buckets, safely and securely.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="use-gpus-arm-and-other-hardware"&gt;
&lt;h2&gt;Use GPUs, ARM, and Other Hardware&lt;/h2&gt;
&lt;p&gt;Coiled provides a range of options for configuring your Dask cluster, making it highly flexible. You can specify the number of virtual machines with n_workers and choose a region close to your remote data with the region option. Set the number of CPU cores with worker_cpu, &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/gpu.html"&gt;access GPUs&lt;/a&gt; with worker_gpu, or opt for more efficient &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/arm.html"&gt;ARM instances&lt;/a&gt; using arm=True.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_cpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;By contrast, if you’re using Kubernetes, you’d have to deploy a new Kubernetes cluster in this region with the node pool attached to a certain node type that has these kinds of machines in it. It can be kind of daunting.&lt;/p&gt;
&lt;p&gt;Coiled’s cluster configuration flexibility extends to AWS, GCP, and Azure allowing you to dynamically start machines of any type in any region, a powerful feature for hardware experimentation.&lt;/p&gt;
&lt;section id="getting-started-with-dask-on-gcp"&gt;
&lt;h3&gt;Getting Started with Dask on GCP&lt;/h3&gt;
&lt;p&gt;Create Dask clusters on Google Cloud by connecting &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;Coiled&lt;/a&gt; to your GCP account. Coiled starts virtual machines when you need them and turns them off when you’re done. It’s easy to set up Coiled to run from your GCP account:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;login
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;gcp
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Or you can skip the CLI and get started at &lt;a class="reference external" href="https://cloud.coiled.io/"&gt;cloud.coiled.io&lt;/a&gt;:&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/d6XouzFP_AY" title="How do I Set Up Coiled?" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="observability-into-your-dask-cluster"&gt;
&lt;h2&gt;Observability Into Your Dask Cluster&lt;/h2&gt;
&lt;p&gt;Coiled provides &lt;a class="reference external" href="https://www.youtube.com/watch?v=4PiZf6UvCv0"&gt;visibility and historical tracking&lt;/a&gt;, offering insights into all activities for your team. You can monitor currently running and past clusters, with flags marking specific workload characteristics. For each cluster, you gain access to information about executed code, errors, and metrics. This visibility lets you optimize work across your team, even for members who are less familiar with the Dask codebase.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="managing-cloud-costs"&gt;
&lt;h2&gt;Managing Cloud Costs&lt;/h2&gt;
&lt;p&gt;Effective cost management is crucial for organizations using cloud resources. Coiled offers team management features that enable you to limit the number of CPU cores running concurrently and on a monthly basis. You can also control team spending by monitoring usage. By default, all Coiled clusters will shutdown after 20 minutes of inactivity. These features are especially valuable for teams supporting users with varying skill levels, ensuring costs remain within budgetary constraints.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="optimizing-cloud-costs"&gt;
&lt;h2&gt;Optimizing Cloud Costs&lt;/h2&gt;
&lt;p&gt;Coiled goes beyond cost management and helps optimize expenses in several ways, including minimizing network costs, using &lt;a class="reference external" href="https://docs.coiled.io/blog/burstable-vs-nonburstable.html"&gt;cost-effective non-burstable instance types as the default&lt;/a&gt;, and making it easy to use &lt;a class="reference external" href="https://docs.coiled.io/blog/dask-graviton.html"&gt;efficient hardware like ARM instances&lt;/a&gt;. There’s nuance to offering this level of configurability and also creating a seamless user experience, though. One example of this is how Coiled helps you take advantage of preemptible VM instances.&lt;/p&gt;
&lt;p&gt;Though preemptible instances are heavily discounted, most users are willing to pay more to avoid interruptions to their workflows as preemptible instances become unavailable. With Coiled, when you request preemptible instances, we look at your region and select the data center in that region with the best availability. Once we get the warning that these preemptible instances are being deprovisioned, we move your tasks onto new preemptible or standard instances. This ensures the continuity of your workloads without interruption. Learn more in &lt;a class="reference internal" href="blog/save-money-with-spot.html"&gt;&lt;span class="doc std std-doc"&gt;our blog post&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Coiled is a seamless managed Dask solution. It offers a smooth and secure cloud infrastructure experience for both individual users and organizations. Notably, you can use Coiled for free for the first 500 CPU hours per month, making it accessible to many users (you’ll still be responsible for your GCP costs).&lt;/p&gt;
&lt;p&gt;If you are currently using other Dask as a service products, like Dask Cloud Provider or Dask Kubernetes, Coiled offers cost savings and an improved user experience, making it worth considering to manage your Dask workflows in the cloud.&lt;/p&gt;
&lt;p&gt;It’s easy to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;get started&lt;/a&gt; with Dask on GCP, AWS, or Azure.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-gcp.html"/>
    <summary>‍Dask is a flexible Python library for parallel and distributed computing. There are a number of ways you can create Dask clusters, each with their own benefits. In this article, we explore how Coiled provides a managed cloud infrastructure solution for deploying Dask on Google Cloud, addressing:</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-azure.html</id>
    <title>Dask on Azure</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="dask-on-azure"&gt;

&lt;p&gt;‍&lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; is a flexible Python library for parallel and distributed computing. There are a number of ways you can create Dask clusters, each with their own benefits. In this article, we explore how Coiled provides a managed cloud infrastructure solution for deploying Dask on Azure, addressing:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Deployment challenges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Software environment and credential management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hardware flexibility&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Historical tracking and observability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost optimization&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/eXP-YuERvi4" title="Six Coiled features for Dask users" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;section id="how-to-deploy-a-dask-cluster"&gt;
&lt;h2&gt;How to Deploy a Dask Cluster&lt;/h2&gt;
&lt;p&gt;There are a number of tools you can use to manage Dask clusters, including:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html#dask-kubernetes"&gt;Dask Kubernetes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html#dask-gateway"&gt;Dask Gateway&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html#dask-cloudprovider"&gt;Dask Cloud Provider&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In principle, Coiled is just like these other Dask as a service products, in that you can also use Coiled to create a Dask cluster. While this might sound straightforward, there’s significant nuance to deploying a Dask cluster effectively.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="install-packages-and-sync-credentials"&gt;
&lt;h2&gt;Install Packages and Sync Credentials&lt;/h2&gt;
&lt;p&gt;On cluster start, Coiled examines your local machine, identifying pip and conda packages and any development packages you have locally. It packages these libraries and deploys them dynamically on cloud-based Linux machines in your cluster. This approach solves the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/index.html"&gt;software environment matching problem&lt;/a&gt; between your local and remote software environments. Additionally, Coiled securely transfers your local cloud credentials to the workers, ensuring they can access your cloud resources, like S3 buckets, safely and securely.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="use-gpus-arm-and-other-hardware"&gt;
&lt;h2&gt;Use GPUs, ARM, and Other Hardware&lt;/h2&gt;
&lt;p&gt;Coiled provides a range of options for configuring your Dask cluster, making it highly flexible. You can specify the number of virtual machines with n_workers and choose a region close to your remote data with the region option. Set the number of CPU cores with worker_cpu, &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/gpu.html"&gt;access GPUs&lt;/a&gt; with worker_gpu, or opt for more efficient &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/arm.html"&gt;ARM instances&lt;/a&gt; using arm=True.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_cpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;By contrast, if you’re using Kubernetes, you’d have to deploy a new Kubernetes cluster in this region with the node pool attached to a certain node type that has these kinds of machines in it. It can be kind of daunting.&lt;/p&gt;
&lt;p&gt;Coiled’s cluster configuration flexibility extends to AWS, GCP, and Azure allowing you to dynamically start machines of any type in any region, a powerful feature for hardware experimentation.&lt;/p&gt;
&lt;section id="getting-started-with-dask-on-azure"&gt;
&lt;h3&gt;Getting Started with Dask on Azure&lt;/h3&gt;
&lt;p&gt;Create Dask clusters on Azure by connecting &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;Coiled&lt;/a&gt; to your Azure account. Coiled starts virtual machines when you need them and turns them off when you’re done. It’s easy to set up Coiled to run from your Azure account:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;login
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;aws
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Or you can skip the CLI and get started at &lt;a class="reference external" href="https://cloud.coiled.io/"&gt;cloud.coiled.io&lt;/a&gt;:&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/d6XouzFP_AY" title="How do I Set Up Coiled?" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="observability-into-your-dask-cluster"&gt;
&lt;h2&gt;Observability Into Your Dask Cluster&lt;/h2&gt;
&lt;p&gt;Coiled provides &lt;a class="reference external" href="https://www.youtube.com/watch?v=4PiZf6UvCv0"&gt;visibility and historical tracking&lt;/a&gt;, offering insights into all activities for your team. You can monitor currently running and past clusters, with flags marking specific workload characteristics. For each cluster, you gain access to information about executed code, errors, and metrics. This visibility lets you optimize work across your team, even for members who are less familiar with the Dask codebase.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="managing-cloud-costs"&gt;
&lt;h2&gt;Managing Cloud Costs&lt;/h2&gt;
&lt;p&gt;Effective cost management is crucial for organizations using cloud resources. Coiled offers team management features that enable you to limit the number of CPU cores running concurrently and on a monthly basis. You can also control team spending by monitoring usage. By default, all Coiled clusters will shutdown after 20 minutes of inactivity. These features are especially valuable for teams supporting users with varying skill levels, ensuring costs remain within budgetary constraints.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="optimizing-cloud-costs"&gt;
&lt;h2&gt;Optimizing Cloud Costs&lt;/h2&gt;
&lt;p&gt;Coiled goes beyond cost management and helps optimize expenses in several ways, including minimizing network costs, using &lt;a class="reference external" href="https://docs.coiled.io/blog/burstable-vs-nonburstable.html"&gt;cost-effective non-burstable instance types as the default&lt;/a&gt;, and making it easy to use &lt;a class="reference external" href="https://docs.coiled.io/blog/dask-graviton.html"&gt;efficient hardware like ARM instances&lt;/a&gt;. There’s nuance to offering this level of configurability and also creating a seamless user experience, though. One example of this is how Coiled helps you take advantage of spot virtual machines.&lt;/p&gt;
&lt;p&gt;Though spot virtual machines are heavily discounted, most users are willing to pay more to avoid interruptions to their workflows as spot instances become unavailable. With Coiled, when you request spot instances, we look at your region and select the data center in that region with the best spot availability. Once we get the warning that these spot instances are being deprovisioned, we move your tasks onto new spot or on-demand instances. This ensures the continuity of your workloads without interruption. Learn more in &lt;a class="reference internal" href="blog/save-money-with-spot.html"&gt;&lt;span class="doc std std-doc"&gt;our blog post&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Coiled is a seamless managed Dask solution. It offers a smooth and secure cloud infrastructure experience for both individual users and organizations. Notably, you can use Coiled for free for the first 500 CPU hours per month, making it accessible to many users (you’ll still be responsible for your Azure costs).&lt;/p&gt;
&lt;p&gt;If you are currently using other Dask as a service products, like Dask Cloud Provider or Dask Kubernetes, Coiled offers cost savings and an improved user experience, making it worth considering to manage your Dask workflows in the cloud.&lt;/p&gt;
&lt;p&gt;It’s easy to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;get started&lt;/a&gt; with Dask on Azure, GCP, or AWS.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-azure.html"/>
    <summary>‍Dask is a flexible Python library for parallel and distributed computing. There are a number of ways you can create Dask clusters, each with their own benefits. In this article, we explore how Coiled provides a managed cloud infrastructure solution for deploying Dask on Azure, addressing:</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-aws.html</id>
    <title>Dask on AWS</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="dask-on-aws"&gt;

&lt;p&gt;‍&lt;a class="reference external" href="https://www.dask.org/"&gt;Dask&lt;/a&gt; is a flexible Python library for parallel and distributed computing. There are a number of ways you can create Dask clusters, each with their own benefits. In this article, we explore how Coiled provides a managed cloud infrastructure solution for deploying Dask on AWS, addressing:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Deployment challenges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Software environment and credential management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hardware flexibility&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Historical tracking and observability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost optimization&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/eXP-YuERvi4" title="Six Coiled features for Dask users" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;section id="how-to-deploy-a-dask-cluster"&gt;
&lt;h2&gt;How to Deploy a Dask Cluster&lt;/h2&gt;
&lt;p&gt;There are a number of tools you can use to manage Dask clusters, including:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html#dask-kubernetes"&gt;Dask Kubernetes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html#dask-gateway"&gt;Dask Gateway&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html#dask-cloudprovider"&gt;Dask Cloud Provider&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In principle, Coiled is just like these other Dask as a service products, in that you can also use Coiled to create a Dask cluster. While this might sound straightforward, there’s significant nuance to deploying a Dask cluster effectively.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="install-packages-and-sync-credentials"&gt;
&lt;h2&gt;Install Packages and Sync Credentials&lt;/h2&gt;
&lt;p&gt;On cluster start, Coiled examines your local machine, identifying pip and conda packages and any development packages you have locally. It packages these libraries and deploys them dynamically on cloud-based Linux machines in your cluster. This approach solves the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/index.html"&gt;software environment matching problem&lt;/a&gt; between your local and remote software environments. Additionally, Coiled securely transfers your local cloud credentials to the workers, ensuring they can access your cloud resources, like S3 buckets, safely and securely.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="use-gpus-arm-and-other-hardware"&gt;
&lt;h2&gt;Use GPUs, ARM, and Other Hardware&lt;/h2&gt;
&lt;p&gt;Coiled provides a range of options for configuring your Dask cluster, making it highly flexible. You can specify the number of virtual machines with n_workers and choose a region close to your remote data with the region option. Set the number of CPU cores with worker_cpu, &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/gpu.html"&gt;access GPUs&lt;/a&gt; with worker_gpu, or opt for more efficient &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/arm.html"&gt;ARM instances&lt;/a&gt; using arm=True.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_cpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;spot_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;spot_with_fallback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;By contrast, if you’re using Kubernetes, you’d have to deploy a new Kubernetes cluster in this region with the node pool attached to a certain node type that has these kinds of machines in it. It can be kind of daunting.&lt;/p&gt;
&lt;p&gt;Coiled’s cluster configuration flexibility extends to AWS, GCP, and Azure allowing you to dynamically start machines of any type in any region, a powerful feature for hardware experimentation.&lt;/p&gt;
&lt;section id="getting-started-with-dask-on-aws"&gt;
&lt;h3&gt;Getting Started with Dask on AWS&lt;/h3&gt;
&lt;p&gt;Create Dask clusters on AWS by connecting &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;Coiled&lt;/a&gt; to your AWS account. Coiled starts EC2 instances when you need them and turns them off when you’re done. It’s easy to set up Coiled to run from your AWS account:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;coiled
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;login
$&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;aws
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Or you can skip the CLI and get started at &lt;a class="reference external" href="https://cloud.coiled.io/"&gt;cloud.coiled.io&lt;/a&gt;:&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/d6XouzFP_AY" title="How do I Set Up Coiled?" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="observability-into-your-dask-cluster"&gt;
&lt;h2&gt;Observability Into Your Dask Cluster&lt;/h2&gt;
&lt;p&gt;Coiled provides &lt;a class="reference external" href="https://www.youtube.com/watch?v=4PiZf6UvCv0"&gt;visibility and historical tracking&lt;/a&gt;, offering insights into all activities for your team. You can monitor currently running and past clusters, with flags marking specific workload characteristics. For each cluster, you gain access to information about executed code, errors, and metrics. This visibility lets you optimize work across your team, even for members who are less familiar with the Dask codebase.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="managing-cloud-costs"&gt;
&lt;h2&gt;Managing Cloud Costs&lt;/h2&gt;
&lt;p&gt;Effective cost management is crucial for organizations using cloud resources. Coiled offers team management features that enable you to limit the number of CPU cores running concurrently and on a monthly basis. You can also control team spending by monitoring usage. By default, all Coiled clusters will shutdown after 20 minutes of inactivity. These features are especially valuable for teams supporting users with varying skill levels, ensuring costs remain within budgetary constraints.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="optimizing-cloud-costs"&gt;
&lt;h2&gt;Optimizing Cloud Costs&lt;/h2&gt;
&lt;p&gt;Coiled goes beyond cost management and helps optimize expenses in several ways, including minimizing network costs, using &lt;a class="reference external" href="https://docs.coiled.io/blog/burstable-vs-nonburstable.html"&gt;cost-effective non-burstable instance types as the default&lt;/a&gt;, and making it easy to use &lt;a class="reference external" href="https://docs.coiled.io/blog/dask-graviton.html"&gt;efficient hardware like ARM instances&lt;/a&gt;. There’s nuance to offering this level of configurability and also creating a seamless user experience, though. One example of this is how Coiled helps you take advantage of spot (or preemptible) instances.&lt;/p&gt;
&lt;p&gt;Though spot instances are heavily discounted, most users are willing to pay more to avoid interruptions to their workflows as spot instances become unavailable. With Coiled, when you request spot instances, we look at your region and select the data center in that region with the best spot availability. Once we get the warning that these spot instances are being deprovisioned, we move your tasks onto new spot or on-demand instances. This ensures the continuity of your workloads without interruption. Learn more in &lt;a class="reference internal" href="blog/save-money-with-spot.html"&gt;&lt;span class="doc std std-doc"&gt;our blog post&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Coiled is a seamless managed Dask solution. It offers a smooth and secure cloud infrastructure experience for both individual users and organizations. Notably, you can use Coiled for free for the first 500 CPU hours per month, making it accessible to many users (you’ll still be responsible for your AWS costs).&lt;/p&gt;
&lt;p&gt;If you are currently using other Dask as a service products, like Dask Cloud Provider or Dask Kubernetes, Coiled offers cost savings and an improved user experience, making it worth considering to manage your Dask workflows in the cloud.&lt;/p&gt;
&lt;p&gt;It’s easy to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/setup/index.html"&gt;get started&lt;/a&gt; with Dask on AWS, GCP, or Azure.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-aws.html"/>
    <summary>‍Dask is a flexible Python library for parallel and distributed computing. There are a number of ways you can create Dask clusters, each with their own benefits. In this article, we explore how Coiled provides a managed cloud infrastructure solution for deploying Dask on AWS, addressing:</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-for-parallel-python.html</id>
    <title>Dask for Parallel Python</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="dask-for-parallel-python"&gt;

&lt;p&gt;Dask is a general purpose library for parallel computing. Dask can be used on its own to parallelize Python code, or with integrations to other popular libraries to scale out common workflows.&lt;/p&gt;
&lt;p&gt;Dask has its own &lt;a class="reference external" href="https://dask.org/"&gt;docs&lt;/a&gt;, but we’ll include a few typical use cases below.&lt;/p&gt;
&lt;section id="big-pandas"&gt;
&lt;h2&gt;Big pandas&lt;/h2&gt;
&lt;p&gt;Dask DataFrames use pandas under the hood, so your current code likely just works. It’s &lt;a class="reference external" href="https://docs.coiled.io/blog/spark-vs-dask.html"&gt;faster than Spark&lt;/a&gt; and easier too. Here’s an illustrative example of how you would load data from Parquet and perform some standard data manipulation with pandas:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3://mybucket/myfile.parquet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask does pandas in parallel. Dask is lazy; when you want an in-memory result add &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.compute()&lt;/span&gt;&lt;/code&gt;. Persist data in distributed memory with the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;.persist()&lt;/span&gt;&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3://mybucket/myfile.*.parquet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joined&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask comfortably scales to handle datasets that range in size from 10 GiB to 100 TiB. Dask DataFrame is used across a wide variety of applications—anywhere you’re working with a large tabular dataset.&lt;/p&gt;
&lt;p&gt;Here are a few large-scale examples using Coiled to deploy Dask on the cloud:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/uber-lyft.html"&gt;Parquet ETL with Dask DataFrame&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/xgboost.html"&gt;XGBoost model training with Dask DataFrame&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/datashader.html"&gt;Visualize 1,000,000,000 points&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="parallel-for-loops"&gt;
&lt;h2&gt;Parallel For Loops&lt;/h2&gt;
&lt;p&gt;With Dask, you can parallelize any Python code, no matter how complex. Dask is flexible and supports arbitrary dependencies and fine-grained task scheduling that extends Python’s concurrent.futures interface. Dask futures allow you to scale generic Python workflows across a Dask cluster with minimal code changes.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LocalCluster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                &lt;span class="c1"&gt;# Runs locally, but you can deploy Dask anywhere&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;                  &lt;span class="c1"&gt;# Define your own Python function&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;              &lt;span class="c1"&gt;# Submit many tasks in a loop&lt;/span&gt;
    &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# Wait until done and gather results&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;As your data grows or your computational demands increase, you might want to deploy Dask on a cluster. If you’re using the cloud, this is easy to do with Coiled:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Cluster&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Request 100 VMs on AWS &lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;                     &lt;span class="c1"&gt;# Define your own Python function&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                 &lt;span class="c1"&gt;# Submit many tasks in a loop&lt;/span&gt;
    &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# Wait until done and gather results&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here’s an &lt;a class="reference external" href="https://docs.coiled.io/user_guide/arxiv-matplotlib.html"&gt;example extracting text&lt;/a&gt; from PDFs stored in a public S3 bucket, where we use Dask to scrape 1 TiB of data in ~5 minutes.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="big-xarray"&gt;
&lt;h2&gt;Big Xarray&lt;/h2&gt;
&lt;p&gt;Dask integrates well with Xarray to process array data in parallel. This includes common file formats like HDF, NetCDF, TIFF, or Zarr. Working with Dask and Xarray feels much like using NumPy arrays, but enables handling significantly larger datasets. The integration between Dask and Xarray is seamless, so you rarely need to manage parallelism explicitly—these details are handled automatically.&lt;/p&gt;
&lt;p&gt;In the following example, we can import Zarr data and perform an aggregation without explicitly importing Dask at all. If Dask is available, it will be used automatically to process the data in parallel.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xarray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xr&lt;/span&gt;

&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_zarr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/path/to/data.zarr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;timeseries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;temp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Compute result&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This allows you to write scalable code that transitions from small, in-memory datasets on a single machine to large, distributed datasets on a cluster, requiring only minimal adjustments.&lt;/p&gt;
&lt;p&gt;Here’s an &lt;a class="reference external" href="https://docs.coiled.io/blog/coiled-xarray.html"&gt;example aggregating 250 TiB of raster data&lt;/a&gt; from the NOAA National Water Model to calculate the average water table depth for each county in the US.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-for-machine-learning"&gt;
&lt;h2&gt;Dask for Machine Learning&lt;/h2&gt;
&lt;p&gt;Dask integrates with a number of machine learning libraries to train or predict on large datasets, increasing model accuracy by using &lt;em&gt;all&lt;/em&gt; of your data.&lt;/p&gt;
&lt;p&gt;Here’s an illustrative example using Dask with XGBoost for distributed model training:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xgboost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xgb&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://my-data/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dtrain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xgb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DaskDMatrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xgb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dtrain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tree_method&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Model training can often be both computationally- and memory-intensive. Here are a few examples of speeding up machine learning tasks by taking advantage of cloud resources with Coiled:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/xgboost.html"&gt;Distributed model training with XGBoost + Dask&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/hpo.html"&gt;Model tuning with XGBoost + Optuna + Dask&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.coiled.io/user_guide/pytorch.html"&gt;PyTorch model training on GPUs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="deploying-dask"&gt;
&lt;h2&gt;Deploying Dask&lt;/h2&gt;
&lt;p&gt;Many of these examples process larger-than-memory datasets on Dask clusters deployed on the cloud with Coiled. If your data is stored on the cloud or you need access to more/better hardware, you can use Coiled to run Dask on AWS, GCP, or Azure. See our documentation on all the ways you can &lt;a class="reference external" href="https://docs.coiled.io/user_guide/dask-deployment-comparisons.html"&gt;deploy Dask&lt;/a&gt; for more details.&lt;/p&gt;
&lt;div class="video-container" style="display: flex; justify-content: center; margin: 20px 0;"&gt;
&lt;iframe style="width: 560px; height: 315px;" src="https://www.youtube.com/embed/mfLyF7nZlRw" title="Dask on Single Machine with Coiled" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-for-parallel-python.html"/>
    <summary>Dask is a general purpose library for parallel computing. Dask can be used on its own to parallelize Python code, or with integrations to other popular libraries to scale out common workflows.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/pydata-dask.html</id>
    <title>Dask and the PyData Stack</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="dask-and-the-pydata-stack"&gt;

&lt;section id="tl-dr"&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;The PyData stack for scientific computing in Python is an interoperable collection of tools for data analysis, visualization, statistical inference, machine learning, interactive computing and more that is used across all types of industries and academic research. Dask, the open source package for scalable data science that was developed to meet the needs of modern data professionals, was born from the PyData community and is considered foundational in this computational stack. This post describes a schematic for thinking about the PyData stack, along with detailing the technical, cultural, and community forces that led to Dask becoming the go-to package for scalable analytics in Python, including its interoperability with packages such as NumPy and pandas, among many others.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-python-data-science-stack"&gt;
&lt;h2&gt;The Python Data Science Stack&lt;/h2&gt;
&lt;p&gt;In 2017, Jake VanderPlas &lt;a class="reference external" href="https://www.youtube.com/watch?v=ZyjCqQEUa8o"&gt;gave a keynote at PyCon&lt;/a&gt; called “The Unreasonable Effectiveness of Python in Science”, in which he presented a schematic of the scientific Python stack, also commonly referred to as the PyData stack. This is a broad collection of Python packages used for scientific computing and data science, in both industry and basic research:&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Untitled 2024-02-21" src="https://docs.coiled.io/_images/66f7382de0db153ab2b8825e_66f737dabf6a30538773616b_Untitled-2024-02-21-1221.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;At the bottom of the stack, you have the Python standard library. As you move out, the next layer extends Python in ways that are useful to scientists and foundational for data science in Python, such as&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://numpy.org/"&gt;NumPy&lt;/a&gt; for numerical array computing,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://jupyter.org/"&gt;Project Jupyter&lt;/a&gt; and IPython for interactive computing and IDEs,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="http://numba.pydata.org/"&gt;Numba&lt;/a&gt; for just-in-time compilation, and&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://dask.org/"&gt;Dask&lt;/a&gt; for scaling your data science to larger datasets and larger models.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jake stated that “built on top of this layer are tools that are slightly more specialized”, such as matplotlib and bokeh for data visualization, pandas for data ingestion and wrangling, Xarray for multidimensional labelled structures (these, as Jake made clear, depend on the level below them, and are “higher level tools”).&lt;/p&gt;
&lt;p&gt;Moving up to even higher level tools, we discover scikit-learn for machine learning, statsmodels for statistical modeling, and PyMC for Bayesian inference and probabilistic programming, among others. On top of these, you have field-specific packages like astropy and biopython (for astronomy and biology, respectively).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-is-foundational-in-the-pydata-stack"&gt;
&lt;h2&gt;Dask is Foundational in the PyData stack&lt;/h2&gt;
&lt;p&gt;If you’re asking “Why is Dask foundational in the PyData stack?”, you’re asking a great question. The answer is: because it composes well with all of the PyData tools. It’s a simple system designed to parallelize &lt;em&gt;any&lt;/em&gt; PyData library. This means that, if you’re looking to natively scale up your Python code to larger datasets within the PyData ecosystem, you’ll be using Dask. One result of this is the elegance of the Dask API:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;if you’re writing Dask code to scale your NumPy code, what you write will mimic the NumPy API, providing a smooth transition;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Similarly, if you’re writing Dask code to scale your pandas code, what you write will mimic the pandas API.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Dask array" src="https://docs.coiled.io/_images/66f73648cea19a16e37b44db_dask-array.svg" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Dask arrays scale Numpy workflows, enabling multi-dimensional data analysis in earth science, satellite imagery, genomics, biomedical applications, and machine learning algorithms (from the Dask documentation).&lt;/p&gt;
&lt;p&gt;Matt Rocklin, creator of Dask and CEO of Coiled, once said to me:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;People really like that Dask Array follows the NumPy API almost exactly. Dask DataFrame follows the Pandas API almost exactly. Dask-ML follows the scikit-learn API almost exactly. There are definitely differences but those differences are necessary because there are some things you do need to think about in parallel algorithms. The Dask maintainers have written a lot of Python data science code and we’re actually the maintainers of all those other libraries too. So it’s rare to find someone who understands NumPy and won’t be able to transition on their own to Dask Array. There’s a very smooth transition process between all the different systems.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As an example, compare the following NumPy and Dask code chunks, noticing how you use NumPy syntax when working with your Dask array:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Create array of random numbers&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;  &lt;span class="c1"&gt;# Add x to its transpose&lt;/span&gt;
&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[::&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Compute mean of a slice of Y&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;da&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Create Dask array&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;  &lt;span class="c1"&gt;# Add x to its transpose&lt;/span&gt;
&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[::&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Compute mean of a slice of Y&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Not only is the end-user programming experience of a Dask Array (or DataFrame) “like” the NumPy or Pandas equivalent, it actually delegates to NumPy (or pandas) code running against real NumPy (or pandas) objects as much as possible. Why is this important? It means, for example, that pandas users’ mental model of what’s happening is close and, in some cases, identical to what actually runs, making a user more productive with less work. By contrast, some other tools – like Koalas (a wrapper over Spark) – imitate the Pandas API but delegate to vastly different machinery underneath. In the case of Spark, the real challenge is understanding enough of that machinery to work with it well, and Koalas can make a tough situation even harder, by hiding the traps and pitfalls until a user falls in.&lt;/p&gt;
&lt;p&gt;This consistency of Dask with the PyData is both (1) by design and (2) a result of cultural and community forces. With respect to the design of Dask, there were several technical and social goals for Dask:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Technical: Harness the power of all of the cores on the laptop/workstation in parallel;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Technical: Support larger-than-memory computation, allowing datasets that fit on disk, but not in RAM;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Social: Invent nothing. We wanted to be as familiar as possible to what users already knew.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sociotechnical: Dask was designed to be lightweight technically so that it could be integrated into other Python libraries.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now let’s dive into the cultural and community forces that led to the simplicity of Dask’s API.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-dask-community-is-the-pydata-community"&gt;
&lt;h2&gt;The Dask Community IS the PyData Community&lt;/h2&gt;
&lt;p&gt;In terms of cultural and community forces, Dask was developed by members of the Python data science community for Pythonista data scientists. Moreover, the Dask developer community is the PyData community:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Stefan Hoyer, the creator of Xarray, hooked up Dask and Xarray in around two weeks;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Min Ragan-Kelly, from Project Jupyter and IPython parallel, and Olivier Grisel, from Scikit-Learn &amp;amp; Joblib, helped to build the current incarnation of the dask-distributed scheduler;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The original idea for Dask arose at Anaconda Inc. (the company that built the popular Anaconda distribution, along with many other open source tools) from conversations and work with founders Peter Wang and Travis Oliphant, the former of whom created the PyData conference series, the latter of whom created NumPy in 2005!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tom Augspurger is a core maintainer of both Pandas and Dask so when Tom makes features in Pandas, he also makes them in Dask.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are a mere handful of examples and there are many more. The cultural and the technical aspects of any design system are interwoven. Due to the social and technical forces outlined here, a key differentiator of Dask in the scalable data science world is that it is Python top-to-bottom: it’s Python all the way down! For high-level API calls, we may not care what’s underneath. But when we need extensibility, flexibility, and support for custom use cases, non-Pythonic abstractions such as PySpark tend to leak heavily. Many Pythonistas have gotten burned trying to use PySpark, thinking it’s a Python library, only to discover Python is a minimal veneer over a complex Scala and Java system lacking Pythonic behaviors.&lt;/p&gt;
&lt;p&gt;The PyData stack is more than a collection of disparate packages created and maintained by different people. It is, rather, a collection of interoperable tools and packages for data science developed by a community (a cool data science project would be a network analysis of PyData stack contributors and packages, using publicly available github data). And the way to scale your data science while staying in this ecosystem is by using Dask.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Dask dataframe" src="https://docs.coiled.io/_images/633f7b5a64f4f9ad51f53404_dask-dataframe.svg" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Dask DataFrames scale Pandas workflows, enabling applications in time series, business intelligence, and general data munging on big data (from the Dask documentation).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="learn-more"&gt;
&lt;h2&gt;Learn More&lt;/h2&gt;
&lt;p&gt;To learn more about Dask and what we’re working on at Coiled to help meet the needs of teams and the enterprise in scaling data science, analytics, and machine learning, sign up to our email list below. We’ll also be writing more posts in the future about how Dask integrates with NumPy, pandas, popular machine learning such as scikit-learn and xgboost, and deep learning frameworks, including PyTorch and tensorflow so keep your eyes peeled for them!&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/pydata-dask.html"/>
    <summary>The PyData stack for scientific computing in Python is an interoperable collection of tools for data analysis, visualization, statistical inference, machine learning, interactive computing and more that is used across all types of industries and academic research. Dask, the open source package for scalable data science that was developed to meet the needs of modern data professionals, was born from the PyData community and is considered foundational in this computational stack. This post describes a schematic for thinking about the PyData stack, along with detailing the technical, cultural, and community forces that led to Dask becoming the go-to package for scalable analytics in Python, including its interoperability with packages such as NumPy and pandas, among many others.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-read-parquet-into-dataframe.html</id>
    <title>Dask Read Parquet Files into DataFrames with read_parquet</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="dask-read-parquet-files-into-dataframes-with-read-parquet"&gt;

&lt;p&gt;This blog post explains how to read Parquet files into Dask DataFrames. Parquet is a columnar, binary file format that has multiple advantages when compared to a row-based file format like CSV. Luckily Dask makes it easy to read Parquet files into Dask DataFrames with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It’s important to properly read Parquet files to take advantage of performance optimizations. Disk I/O can be a major bottleneck for distributed compute workflows on large datasets. Reading Parquet files properly allows you to send less data to the computation cluster, so your analysis can run faster.&lt;/p&gt;
&lt;p&gt;Let’s look at some examples on small datasets to better understand the options when reading Parquet files. Then we’ll look at examples on larger datasets with thousands of Parquet files that are processed on a cluster in the cloud.&lt;/p&gt;
&lt;section id="dask-read-parquet-basic-usage"&gt;
&lt;h2&gt;Dask read_parquet: basic usage&lt;/h2&gt;
&lt;p&gt;Let’s create a small DataFrame and write it out as Parquet files. This will give us some files to try out &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt;. Start by creating the DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;letters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now write the DataFrame to Parquet files with the pyarrow engine. The installation instructions for pyarrow are in the Conda environment for reading Parquet files section that follows.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/something&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here are the files that are output to disk.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;something&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="n"&gt;_common_metadata&lt;/span&gt;
  &lt;span class="n"&gt;_metadata&lt;/span&gt;
  &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
  &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can read the files into a Dask DataFrame with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/something&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Check the contents of the DataFrame to make sure all the Parquet data was properly read.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Dask DataFrame after reading Parquet" src="https://docs.coiled.io/_images/6372be662636c0ef5efea173_Coiled%2520blog%2520-%2520dask%2520read%2520parquet%25201.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Dask read Parquet supports two Parquet engines, but most users can simply use pyarrow, as we’ve done in the previous example, without digging deep into this option.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-parquet-pyarrow-vs-fastparquet-engines"&gt;
&lt;h2&gt;Dask read_parquet: pyarrow vs fastparquet engines&lt;/h2&gt;
&lt;p&gt;You can read and write Parquet files to Dask DataFrames with the fastparquet and pyarrow engines. Both engines work fine most of the time. The subtle differences between the two engines doesn’t matter for the vast majority of use cases.&lt;/p&gt;
&lt;p&gt;It’s generally best to avoid mixing and matching the Parquet engines. For example, you usually won’t want to write Parquet files with pyarrow and then try to read them with fastparquet.&lt;/p&gt;
&lt;p&gt;This blog post will only use the pyarrow engine and won’t dive into the subtle differences between pyarrow and fastparquet. You can typically just use pyarrow and not think about the minor difference between the engines.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-parquet-lots-of-files-in-the-cloud"&gt;
&lt;h2&gt;Dask read_parquet: lots of files in the cloud&lt;/h2&gt;
&lt;p&gt;Our previous example showed how to read two Parquet files on localhost, but you’ll often want to read thousands of Parquet files that are stored in a cloud based file system like Amazon S3.&lt;/p&gt;
&lt;p&gt;Here’s how to read a 662 million row Parquet dataset into a Dask DataFrame with a 5 node computational cluster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;read-parquet-demo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distributed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Take a look at the first 5 rows of this DataFrame to get a feel for the data.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="First 5 rows of the Dask DataFrame" src="https://docs.coiled.io/_images/6372be86ef6af4416789aa1d_Coiled%2520blog%2520-%2520dask%2520read%2520parquet%25202.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;This dataset contains a timestamp index and four columns of data.&lt;/p&gt;
&lt;p&gt;Let’s run a query to compute the number of unique values in the id column.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nunique&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query takes 59 seconds to execute.&lt;/p&gt;
&lt;p&gt;Notice that this query only requires the data in the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;id&lt;/span&gt;&lt;/code&gt; column. However, we transferred the data for all columns of the Parquet file to run this query. Spending time to transfer data that’s not used from the filesystem to the cluster is obviously inefficient.&lt;/p&gt;
&lt;p&gt;Let’s see how Parquet allows you to only read the columns you need to speed up query times.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-parquet-column-selection"&gt;
&lt;h2&gt;Dask read_parquet: column selection&lt;/h2&gt;
&lt;p&gt;Parquet is a columnar file format which allows you to selectively read certain columns when reading files. You can’t cherry pick certain columns when reading from row-based file formats like CSV. Parquet’s columnar nature is a major advantage.&lt;/p&gt;
&lt;p&gt;Let’s refactor the query from the previous section to only read the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;id&lt;/span&gt;&lt;/code&gt; column to the cluster by setting the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;columns&lt;/span&gt;&lt;/code&gt; argument.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now let’s run the same query as before.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nunique&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query only takes 43 seconds to execute, which is 27% faster. This performance enhancement can be much larger for different datasets / queries.&lt;/p&gt;
&lt;p&gt;Cherry picking individual columns from files is often referred to as column pruning. The more columns you can skip, the more column pruning will help speed up your query.&lt;/p&gt;
&lt;p&gt;Definitely make sure to leverage column pruning when you’re querying Parquet files with Dask.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-parquet-row-group-filters"&gt;
&lt;h2&gt;Dask read_parquet: row group filters&lt;/h2&gt;
&lt;p&gt;Parquet files store data in row groups. Each row group contains metadata, including the min/max value for each column in the row group. For certain filtering queries, you can skip over entire row groups just based on the row group metadata.&lt;/p&gt;
&lt;p&gt;For example, suppose &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;columnA&lt;/span&gt;&lt;/code&gt; in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;row_group_3&lt;/span&gt; &lt;/code&gt;has a min value of 2 and a max value of 34. If you’re looking for all rows with a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;columnA&lt;/span&gt;&lt;/code&gt; value greater than 95, then you know &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;row_group_3&lt;/span&gt; &lt;/code&gt; won’t contain any data that’s relevant for your query. You can skip over the row group entirely for that query.&lt;/p&gt;
&lt;p&gt;Let’s run a query without any row group filters and then run the same query with row group filters to see the performance book predicate pushdown filtering can provide.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1170&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query takes 77 seconds to execute.&lt;/p&gt;
&lt;p&gt;Let’s run the same query with row group filtering.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1170&lt;/span&gt;&lt;span class="p"&gt;)]],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1170&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This query runs in 4.5 seconds and is significantly faster.&lt;/p&gt;
&lt;p&gt;Row group filtering is also known as predicate pushdown filtering and can be applied in Dask read Parquet by setting the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;filters&lt;/span&gt;&lt;/code&gt; argument when invoking &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Predicate pushdown filters can provide massive performance gains or none at all. It depends on how many row groups Dask will be able to skip for the specific query. The more row groups you can skip with the row group filters, the less data you’ll need to read to the cluster, and the faster your analysis will execute.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-parquet-ignore-metadata-file"&gt;
&lt;h2&gt;Dask read_parquet: ignore metadata file&lt;/h2&gt;
&lt;p&gt;When you write Parquet files with Dask, it’ll output a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;_metadata&lt;/span&gt;&lt;/code&gt; file by default. The &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;_metadata&lt;/span&gt;&lt;/code&gt; file contains the Parquet file footer information for all files in the filesystem, so Dask doesn’t need to individually read the file footer for every file in the Parquet dataset every time the Parquet lake is read.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;_metadata&lt;/span&gt;&lt;/code&gt; file is a nice performance optimization for smaller datasets, but it has downsides.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;_metadata&lt;/span&gt;&lt;/code&gt; is a single file, so it’s not scalable for huge datasets. For large data lakes, even the metadata can be “big data”, with the same scaling issues of “regular data”.&lt;/p&gt;
&lt;p&gt;You can have Dask read Parquet ignore the metadata file by setting &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;ignore_metadata_file=True&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ignore_metadata_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask will gather and process the metadata for each Parquet file in the lake when it’s instructed to ignore the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;_metadata&lt;/span&gt;&lt;/code&gt; file.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-read-parquet-index"&gt;
&lt;h2&gt;Dask read_parquet: index&lt;/h2&gt;
&lt;p&gt;You may be surprised to see that Dask can intelligently infer the index when reading Parquet files. Dask is able to confirm the index from the Pandas parquet file metadata. You can manually specify the index as well.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;timestamp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can also read in all data as regular columns without specifying an index.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="DataFrame with timestamp as a regular column" src="https://docs.coiled.io/_images/6372bec865b60c5a93990d28_Coiled%2520blog%2520-%2520dask%2520read%2520parquet%25203.png" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;/section&gt;
&lt;section id="dask-read-parquet-categories-argument"&gt;
&lt;h2&gt;Dask read_parquet: categories argument&lt;/h2&gt;
&lt;p&gt;You can read in a column as a category column by setting the categories option.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Check the dtypes to make sure this was read in as a category.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtypes&lt;/span&gt;

&lt;span class="nb"&gt;id&lt;/span&gt;      &lt;span class="n"&gt;category&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;      &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;        &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt;        &lt;span class="n"&gt;float64&lt;/span&gt;
&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="conda-environment-for-reading-parquet"&gt;
&lt;h2&gt;Conda environment for reading Parquet&lt;/h2&gt;
&lt;p&gt;Here’s an abbreviated Conda YAML file for creating an environment with the pyarrow and fastparquet dependencies:&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;standard-coiled&lt;/span&gt;
&lt;span class="nt"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;conda-forge&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;defaults&lt;/span&gt;
&lt;span class="nt"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python=3.9&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pandas&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dask&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pyarrow&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fastparquet&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You don’t need to include both pyarrow and fastparquet in your environment. Just add the Parquet engine you’ll be using.&lt;/p&gt;
&lt;p&gt;You can use this environment when running Dask to keep your life simple.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="additional-resources"&gt;
&lt;h2&gt;Additional resources&lt;/h2&gt;
&lt;p&gt;Here are additional resources if you’d like to learn more:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.coiled.io/blog/5-reasons-parquet-is-better-than-csv-for-data-analyses"&gt;5 Reasons Parquet is better than CSV for data analyses&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.coiled.io/blog/advantages-of-parquet-file-format"&gt;Advantages of Parquet file format&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.coiled.io/blog/how-to-speed-up-a-pandas-query-10x-with-6-dask-tricks"&gt;How to speed up a Pandas query 10x with 6 Dask tricks&lt;/a&gt;, many of which involve the Parquet tactics outlined in this post&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This blog post showed you how to properly read Parquet files with Dask.&lt;/p&gt;
&lt;p&gt;There are a lot of options and they can impact the runtime of your analysis significantly, so knowing how to read Parquet files is quite important.&lt;/p&gt;
&lt;p&gt;Column pruning is of particular importance. It’s easy to apply column pruning and it often yields a significant performance gain.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-read-parquet-into-dataframe.html"/>
    <summary>This blog post explains how to read Parquet files into Dask DataFrames. Parquet is a columnar, binary file format that has multiple advantages when compared to a row-based file format like CSV. Luckily Dask makes it easy to read Parquet files into Dask DataFrames with read_parquet.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/dask-disk-partition-on.html</id>
    <title>Creating Disk Partitioned Lakes with Dask using partition_on</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="creating-disk-partitioned-lakes-with-dask-using-partition-on"&gt;

&lt;p&gt;This post explains how to create disk-partitioned Parquet lakes using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;partition_on&lt;/span&gt;&lt;/code&gt; and how to read disk-partitioned lakes with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;read_parquet&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;filters&lt;/span&gt;&lt;/code&gt;. Disk partitioning can significantly improve performance when used correctly.&lt;/p&gt;
&lt;p&gt;However, when used naively disk-partitioning may in fact reduce performance (because of expensive file listing operations or lots of small files). This article will build your intuition and skills around recognizing and implementing opportunities to improve performance using &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;partition_on&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let’s start by outputting a Dask DataFrame with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;to_parquet&lt;/span&gt;&lt;/code&gt; using disk partitioning to develop some intuition on how this design pattern can improve query performance.&lt;/p&gt;
&lt;section id="output-disk-partitioned-lake"&gt;
&lt;h2&gt;Output disk partitioned lake&lt;/h2&gt;
&lt;p&gt;Create a Dask DataFrame with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;letter&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;number&lt;/span&gt;&lt;/code&gt; columns and write it out to disk, partitioning on the letter column.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;letter&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;number&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tmp/partition/1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partition_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;letter&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here are the files that are written to disk.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
    &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
    &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
  &lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
  &lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
  &lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Organizing the data in this directory structure lets you easily skip files for certain read operations. For example, if you only want the data where the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;letter&lt;/span&gt;&lt;/code&gt; equals &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;a&lt;/span&gt;&lt;/code&gt;, then you can look in the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;tmp/partition/1/letter=a&lt;/span&gt;&lt;/code&gt; directory and skip the other Parquet files.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;letter&lt;/span&gt;&lt;/code&gt; column is referred to as the partition key in this example.&lt;/p&gt;
&lt;p&gt;Let’s take a look at the syntax for reading the data from a certain partition.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="read-disk-partitioned-lake"&gt;
&lt;h2&gt;Read disk partitioned lake&lt;/h2&gt;
&lt;p&gt;Here’s how to read the data in the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;letter=a&lt;/span&gt;&lt;/code&gt; disk partition.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;tmp/partition/1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;letter&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;==&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

   &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;       &lt;span class="mi"&gt;1&lt;/span&gt;      &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;       &lt;span class="mi"&gt;4&lt;/span&gt;      &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;       &lt;span class="mi"&gt;5&lt;/span&gt;      &lt;span class="n"&gt;a&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask is smart enough to apply the partition filtering based on the filters argument. Dask only reads the data from tmp/partition/1/letters=a into the DataFrame and skips all the files in other partitions.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="trade-offs"&gt;
&lt;h2&gt;Trade Offs&lt;/h2&gt;
&lt;p&gt;Disk partitioning improves performance for queries that filter on the partition key. It usually hurts query performance for queries that don’t filter on the partition key.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Example query that runs faster on disk partitioned lake&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;tmp/partition/1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;letter&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;==&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Example query that runs slower on disk partitioned lake&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tmp/partition/1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;number&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The performance drag from querying a partitioned lake without filtering on the partition key depends on the underlying filesystem. Unix-like filesystems are good at performing file listing operations on nested directories. So you might not notice much of a performance drag on your local machine.&lt;/p&gt;
&lt;p&gt;Cloud-based object stores, like AWS S3, are not Unix-like filesystems. They store data as key value pairs and are slow when listing nested files with a wildcard character (aka globbing).&lt;/p&gt;
&lt;p&gt;You need to carefully consider your organization’s query patterns when evaluating the costs/benefits of disk partitioning. If you are always filtering on the partition key, then a partitioned lake is typically best. Disk partitioning might not be worth it if you only filter on the partitioned key sometimes.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="multiple-partition-keys"&gt;
&lt;h2&gt;Multiple partition keys&lt;/h2&gt;
&lt;p&gt;You can also partition on multiple keys (the examples thus far have only partitioned on a single key).&lt;/p&gt;
&lt;p&gt;Create another Dask DataFrame and write it out to disk with multiple partition keys.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;north america&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mexico&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;carlos&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;asia&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;india&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ram&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;asia&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;china&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;li&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;continent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;country&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;first_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;tmp/partition/2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partition_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;continent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;country&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here are the files that are outputted to the filesystem:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="n"&gt;continent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asia&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;china&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
      &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;india&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
      &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
  &lt;span class="n"&gt;continent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;north&lt;/span&gt; &lt;span class="n"&gt;america&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mexico&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
      &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parquet&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Read in the files where &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;continent=asia&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;country=china&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;tmp/partition/2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;continent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;==&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;asia&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;country&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;==&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;china&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="n"&gt;continent&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;         &lt;span class="n"&gt;li&lt;/span&gt;      &lt;span class="n"&gt;asia&lt;/span&gt;   &lt;span class="n"&gt;china&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Deeply partitioned lakes are even more susceptible to the partitioned lake performance pitfalls because file listing operations on cloud stores for nested folders can be slow. You’ll typically only be rewarded with performance gains when you filter on at least one of the partition keys.&lt;/p&gt;
&lt;p&gt;Globbing gets more expensive as the directory structure gets more nested.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="additional-considerations"&gt;
&lt;h2&gt;Additional considerations&lt;/h2&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Dask dataframe" src="https://docs.coiled.io/_images/633f7b5a64f4f9ad51f53404_dask-dataframe.svg" style="width: 700px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;Key Point&lt;/p&gt;
&lt;p&gt;When creating a partitioned lake, you need to choose a partition key that doesn’t have too many distinct values.&lt;/p&gt;
&lt;p&gt;Suppose you have a 5GB dataset and often filter on &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;column_a&lt;/span&gt;&lt;/code&gt;. Should you partition your data lake on column_a?&lt;/p&gt;
&lt;p&gt;That depends on the number of unique values in &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;column_a&lt;/span&gt;&lt;/code&gt;. If &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;column_a&lt;/span&gt;&lt;/code&gt; has 5 unique values, then it could be a great partition key. If &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;column_a&lt;/span&gt;&lt;/code&gt; has 100,000 distinct values, then partitioning the 5GB dataset will break up the dataset into at least 100,000 files, which is way too much. That’d be 100,000 files that are only 0.05MB and it’s usually best to target files that are 100MB, see &lt;a class="reference external" href="https://coiled.io/blog/repartition-dataframe/"&gt;this blog post&lt;/a&gt; for more detail.&lt;/p&gt;
&lt;p&gt;Partitioned data lakes are particularly susceptible to the small file problem. Dask analyses often work best with files that are around 100 MB when uncompressed. A data lake that’s incrementally updated frequently will often create files that are quite small. Incrementally updating partitioned lakes will create files that are even smaller.&lt;/p&gt;
&lt;p&gt;Suppose you have a data pipeline that ingests 600 MB of data per hour. If you’re writing this data to an unpartitioned lake, then you can repartition into 100MB partitions and write out 6 files per hour. If you’re writing to a partitioned lake with 10,000 unique partition key values, then you may be writing up to 10,000 files per hour.&lt;/p&gt;
&lt;p&gt;Make sure you have a small file compaction routine if you build a lake that’s partitioned on a high cardinality partition key and incrementally update frequently.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Disk partitioning is a powerful performance optimization that can make certain queries much faster.&lt;/p&gt;
&lt;p&gt;Due to the key-value nature of cloud object stores, disk partitioning can also make certain queries a lot slower.&lt;/p&gt;
&lt;p&gt;It’s a powerful optimization, but you need to understand your query patterns and limitations to make it work well with your data systems.&lt;/p&gt;
&lt;p&gt;Thanks for reading! And if you’re interested in trying out Coiled Cloud, which provides hosted Dask clusters, docker-less managed software, and one-click deployments, you can do so for free today when you click below.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/dask-disk-partition-on.html"/>
    <summary>This post explains how to create disk-partitioned Parquet lakes using partition_on and how to read disk-partitioned lakes with read_parquet and filters. Disk partitioning can significantly improve performance when used correctly.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/cost-savings-with-dask-and-coiled.html</id>
    <title>Cost Savings with Dask and Coiled</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="cost-savings-with-dask-and-coiled"&gt;

&lt;section id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Coiled can often save money for an organization running Dask. This article goes through the most common ways in which we see that happen.&lt;/p&gt;
&lt;p&gt;We start and end with the big abstract points of “people are expensive” and “you can move faster” and fill in the middle with lots of technical details.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="personnel-cost-savings"&gt;
&lt;h2&gt;1 – Personnel cost savings&lt;/h2&gt;
&lt;p&gt;First and foremost, running Dask in the cloud takes effort by smart people. Smart people are expensive. We often see data science groups spending half of an FTE maintaining the cloud environment where Dask is run. This includes activities like the following:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Setting up and tending EC2 / Kubernetes clusters&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Managing docker images for the team&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tracking resource usage and shutting down things that shouldn’t be running&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handling upgrades&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Debugging issues from their teammates&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Half-an-FTE often costs between $50k and $150k/year depending on their seniority. Allowing this person to go back to their day job is, often, the largest cost savings for groups just getting started. Learn more in our &lt;a class="reference external" href="https://coiled.io/build-vs-buy"&gt;Build vs. Buy page&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="track-and-shut-down-idle-resources"&gt;
&lt;h2&gt;2 – Track and shut down idle resources&lt;/h2&gt;
&lt;p&gt;People leave things on, either through negligence (whoops! I didn’t realize that I left this notebook open) through things slipping through the cracks (hrm, I definitely shut this down, but it looks like the signal never made it to the cloud manager) or through ignorance (what do you mean I had to clean up network resources as well as instances? We get charged for those separately?)&lt;/p&gt;
&lt;p&gt;Coiled tracks everything and provides a zero-cost default experience, allowing people to rest easier. If, somehow, something sneaks through, our pagers go off, not yours.&lt;/p&gt;
&lt;p&gt;This changes the way we work. As Coiled users we often don’t really care about being diligent shutting down clusters. We know that the automated systems will detect idleness and clean them up.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="spot-preemptible-instances"&gt;
&lt;h2&gt;3 – Spot / Preemptible instances&lt;/h2&gt;
&lt;p&gt;Instances that can disappear are cheaper than instances that can’t, often by a factor of 2-3x. By leveraging spot instances we can drastically reduce costs. However, using Spot is hard, especially when you need something to work. Coiled allows the following policies:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p&gt;Prefer spot, but use on-demand if no spot instances are available&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As spot machines are reclaimed&lt;/p&gt;
&lt;p&gt;a. Give Dask workers notice so that they can gracefully terminate, allowing smooth operation within Dask and no lost            work
b. Backfill these slots with new on-demand instances&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As spot instances become available again (work in progress)&lt;/p&gt;
&lt;p&gt;a. Gracefully retire on-demand instances
b. Backfill with spot&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Doing this dance well is hard, but it allows us to substitute an expensive resource with a cheap one with little degradation in experience. Cost savings here can be ~ 50%. Learn more in &lt;a class="reference external" href="https://www.coiled.io/blog/save-money-with-spot"&gt;our blog post&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="arm"&gt;
&lt;h2&gt;4 – ARM&lt;/h2&gt;
&lt;p&gt;ARM chips are fast and cheap, often resulting in &lt;a class="reference external" href="https://blog.coiled.io/blog/dask-graviton.html"&gt;20-30% cost reductions&lt;/a&gt;. Coiled makes it easy to switch between instance types and try ARM on your existing workloads. Coiled also makes it easy to substitute out x86 Python packages for ARM packages without any effort in the common case.&lt;/p&gt;
&lt;p&gt;This effort is new and exciting. We encourage users engaged with ARM chips to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/arm.html"&gt;try it out&lt;/a&gt;!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="transparency"&gt;
&lt;h2&gt;5 – Transparency&lt;/h2&gt;
&lt;p&gt;Coiled tracks everything that is going on and gives aggregated views to cost owners in an organization. This allows organizations to make informed decisions and focus their energies appropriately.&lt;/p&gt;
&lt;p&gt;Transparency takes a few forms. You can view the cost of currently running clusters, as well as the cost of historical computations both started by yourself, and by others in your team. You can also dive in to individual clusters and see what operations and even what lines of code accounted for the largest part of the cost.&lt;/p&gt;
&lt;p&gt;You can also view aggregated cost information by user and by time to help identify opportunities for future improvement and cost assignment. See our &lt;a class="reference external" href="https://docs.coiled.io/user_guide/costs/index.html"&gt;documentation on managing costs&lt;/a&gt; for more details.&lt;/p&gt;
&lt;img alt="Overproduction" src="https://docs.coiled.io/_images/636d733d2f19c63b2e0d5eb8_overproduction.png" /&gt;
&lt;p&gt;Account administrators can see a detailed breakdown of Coiled usage by a number of dimensions like user, GitHub reference, or tag.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="limits-and-bounds"&gt;
&lt;h2&gt;6 – Limits and bounds&lt;/h2&gt;
&lt;p&gt;We also make it easy to bound the amount that you spend with us both through sensible tagging of cloud resources, and through our own limiting controls.&lt;/p&gt;
&lt;section id="cloud-limits"&gt;
&lt;h3&gt;Cloud limits&lt;/h3&gt;
&lt;p&gt;Each resource that Coiled creates will be tagged with **owner: coiled, &lt;strong&gt;allowing you to see all the resources&lt;/strong&gt; **created by your account. Alternatively, you can set custom tags for each cluster created so you have better visibility into cost breakdown. Refer to the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/costs/tags.html"&gt;section about tags&lt;/a&gt; in the documentation.&lt;/p&gt;
&lt;p&gt;This allows you to more easily set alerts in your cloud provider account.&lt;/p&gt;
&lt;p&gt;When you set alerts, AWS or GCP will send you an email notification when you have reached a certain threshold, so you can know how much you have spent so far. You can follow the official documentation on how to set up billing alerts:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/monitor_estimated_charges_with_cloudwatch.html"&gt;Create a billing alarm to monitor your estimated AWS charges&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://cloud.google.com/blog/topics/developers-practitioners/protect-your-google-cloud-spending-budgets"&gt;Protect your Google Cloud spending with budgets&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="coiled-usage-limits"&gt;
&lt;h3&gt;Coiled usage limits&lt;/h3&gt;
&lt;img alt="Dashboard" src="https://docs.coiled.io/_images/636d73d89811138543c33fd9_dashboard.png" /&gt;
&lt;p&gt;Additionally Coiled can help you bound how much you or your teammates spend with us. Coiled offers the following bounds:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;A monthly total spending limit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instantaneous spending limit on the full account (set by Coiled)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instantaneous spending limits on each user (set by account admins)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These allow users to bound their overall spend, as well as enforce bounds on individual teammates. See our &lt;a class="reference external" href="https://docs.coiled.io/user_guide/users/index.html"&gt;documentation&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="risk-and-support"&gt;
&lt;h2&gt;7 – Risk and support&lt;/h2&gt;
&lt;p&gt;Coiled is maintained by a professional team and monitored 24x7. The risk associated with such a platform is lower than a DIY approach. This team understands cloud infrastructure, Dask, and the underlying computational libraries well enough to provide a more consistent and predictable experience for organizations that rely on this technology stack for business-critical workloads.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="sensible-configuration"&gt;
&lt;h2&gt;8 – Sensible configuration&lt;/h2&gt;
&lt;p&gt;There are many other small configuration choices that we make in our cloud architecture that each have non-trivial effects. These compound to significant savings. We’ll list a few below:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Good default instance choices, particularly those in the t3 line, which support cheap burstable compute&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoiding NAT gateways which are common causes of surprising data transfer bills.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-Zone worker pools, allowing users to get the best availability possible for Spot or rare instance types, even when supply is low&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;…&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These choices are technical and in the weeds, but can each have a strongly positive impact on overall costs.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="opportunity-costs-and-time-to-delivery"&gt;
&lt;h2&gt;9 – Opportunity costs and time to delivery&lt;/h2&gt;
&lt;p&gt;You’ve opted to use the cloud because you value moving quickly.&lt;/p&gt;
&lt;p&gt;The median Coiled user spends low thousands of dollars per month. While real money, this isn’t that big of a deal related to the value of moving faster. Coiled empowers teams to shave several months off of their timelines. When compared to the costs of monthly burn and waiting to build something internally, the costs here are often trivial in relation.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Coiled is free to try, with $25 of usage included. Coiled provides a cost-constrained experience that also delights data science and data engineering users, making them more productive with their time.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/cost-savings-with-dask-and-coiled.html"/>
    <summary>Coiled can often save money for an organization running Dask. This article goes through the most common ways in which we see that happen.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/convert-large-json-to-parquet-with-dask.html</id>
    <title>Convert Large JSON to Parquet with Dask</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="convert-large-json-to-parquet-with-dask"&gt;

&lt;section id="tl-dr"&gt;
&lt;h2&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;You can use Coiled, the cloud-based Dask platform, to easily convert large JSON data into a tabular DataFrame stored as Parquet in a cloud object-store. Start off by iterating with Dask locally first to build and test your pipeline, then transfer the same workflow to Coiled with minimal code changes. We demonstrate a JSON to Parquet conversion for a 75GB dataset that runs without downloading the dataset to your local machine.&lt;/p&gt;
&lt;/section&gt;
&lt;hr class="docutils" /&gt;
&lt;section id="why-convert-json-to-parquet"&gt;
&lt;h2&gt;Why convert JSON to Parquet&lt;/h2&gt;
&lt;p&gt;Data scraped from the web in nested JSON format often needs to be converted into a tabular format for exploratory data analysis (EDA) and/or machine learning (ML). The Parquet file format is an optimal method for storing tabular data, allowing operations like column pruning and predicate pushdown filtering which &lt;a class="reference external" href="http://www.coiled.io/blog/parquet-file-column-pruning-predicate-pushdown"&gt;greatly increases the performance of your workflows&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;This post demonstrates a JSON to Parquet pipeline for a 75GB dataset, using Dask and Coiled to convert and store the data to a cloud object store. This pipeline bypasses the need for the dataset to be stored locally on your machine.&lt;/p&gt;
&lt;img alt="Overproduction" src="https://docs.coiled.io/_images/636d733d2f19c63b2e0d5eb8_overproduction.png" /&gt;
&lt;p&gt;Upon completing this notebook, you will be able to:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Build and test your ETL workflow locally first, using a single test file representing 1 hour of Github activity data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scale that same workflow out to the cloud using Coiled to process the entire dataset.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Spoiler alert – y&lt;/em&gt;ou’ll be running the exact same code in both cases, just changing the place where the computations are run.&lt;/p&gt;
&lt;p&gt;You can find a full-code example in &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/main/blogs/convert-large-json-to-parquet-with-dask.ipynb"&gt;this notebook&lt;/a&gt;. To run the notebook locally, build a conda environment with the environment.yml file located in the notebook repo.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="json-to-parquet-build-your-pipeline-locally"&gt;
&lt;h2&gt;JSON to Parquet: Build your Pipeline Locally&lt;/h2&gt;
&lt;p&gt;It’s good practice to begin by building your pipeline locally first. The notebook linked above walks you through this process step-by-step. We’ll just summarize the steps here.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extract a single file&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’ll be working with data from the &lt;a class="reference external" href="https://www.gharchive.org/"&gt;Github Archive project&lt;/a&gt; for the year 2015. This dataset logs all public activity on Github and takes up ~75GB in uncompressed form.&lt;/p&gt;
&lt;p&gt;Begin by extracting a single file from the Github Archive. This represents 1 hour of data and takes up ~5MB of data. There’s no need to work with any kind of parallel or cloud computing here, so you can iterate locally for now.&lt;/p&gt;
&lt;div class="highlight-shell notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://data.gharchive.org/2015-01-01-15.json.gz
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Only scale out to the cloud if and when necessary to avoid unnecessary costs and code complexity.&lt;/em&gt;&lt;/p&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transform JSON data into DataFrame.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Great, you’ve extracted the data from its source. Now you can proceed to transform it into tabular DataFrame format.** **There are several different schemas overlapping in the data, which means you can’t simply cast this into a pandas or Dask DataFrame. Instead, you could filter out one subset, such as PushEvents, and work with that.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;PushEvent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can apply the process function (defined in the notebook) to flatten the nested JSON data into tabular format, with each row now representing a single Github commit.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;PushEvent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;flattened&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;PushEvent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then cast this data into a DataFrame using the .to_dataframe() method.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flattened&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_dataframe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Load data to a local directory in Parquet file format&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You’re now all set to write your DataFrame to a local directory as a .parquet file using the Dask DataFrame .to_parquet() method.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;test.parq&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;snappy&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="json-to-parquet-scaling-out-with-dask-clusters-on-coiled"&gt;
&lt;h2&gt;JSON to Parquet: Scaling out with Dask Clusters on Coiled&lt;/h2&gt;
&lt;p&gt;Great job building and testing out your workflow locally! Now let’s build a workflow that will collect the data for a full year, process it, and save it to the cloud object storage.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spin up your Cluster&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’ll start by launching a Dask cluster in the cloud that can run your pipeline on the entire dataset. To run the code in this section, you’ll need a free Coiled account by logging into &lt;a class="reference external" href="http://cloud.coiled.io"&gt;Coiled Cloud&lt;/a&gt;. You’ll only need to provide your Github credentials to create an account.&lt;/p&gt;
&lt;p&gt;You will then need to make a software environment with the correct libraries so that the workers in your cluster are able to execute our computations.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="c1"&gt;# create Coiled software environment&lt;/span&gt;
&lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_software_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;github-parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;conda&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dask&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pyarrow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;s3fs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ujson&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;requests&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;lz4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;fastparquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;You can also create Coiled software environments using Docker images, environment.yml (conda), or requirements.txt (pip) files. For more information, check out the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software_environment_creation.html"&gt;Coiled Docs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, let’s spin up your Coiled cluster, specifying the cluster name, the software environment it’s running, and the number of Dask workers.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# spin up a Coiled cluster&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;github-parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;software&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;coiled-examples/github-parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Finally, point Dask to run on computations on your Coiled cluster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# connect Dask to your Coiled cluster&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run your pipeline on the Github Archive dataset&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The moment we’ve all been waiting for! Your cluster is up and running, which means you’re all set to run the JSON to Parquet pipeline you built above on the entire dataset.&lt;/p&gt;
&lt;p&gt;This requires 2 subtle changes to your code:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;download &lt;em&gt;all&lt;/em&gt; of the Github Archive files instead of just 1 test file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;point df.to_parquet() to write to your s3 bucket instead of locally&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note that the code below uses a filenames list that contains all of the files for the year 2015 and the process function mentioned above. Refer to &lt;a class="reference external" href="https://github.com/coiled/coiled-resources/blob/main/blogs/convert-large-json-to-parquet-with-dask.ipynb"&gt;the notebook&lt;/a&gt; for the definitions of these two objects.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="c1"&gt;# read in json data&lt;/span&gt;
&lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ujson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# filter out PushEvents&lt;/span&gt;
&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;PushEvent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# process into tabular format, each row is a single commit&lt;/span&gt;
&lt;span class="n"&gt;processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# flatten and cast to dataframe&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_dataframe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# write to parquet&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;s3://coiled-datasets/etl/test.parq&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pyarrow&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snappy&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;CPU&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="mf"&gt;15.1&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.74&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;16.8&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;Wall&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Excellent, that works. But let’s see if we can speed it up a little…&lt;/p&gt;
&lt;p&gt;Let’s scale our cluster up to boost performance. We’ll use the cluster.scale() command to double the number of workers in our cluster. We’ll also include a call to client.wait_for_workers() which will block activity until all of the workers are online. This way we can be sure that we’re throwing all the muscle we have at our computation.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# double n_workers&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# this blocks activity until the specified number of workers have joined the cluster&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_for_workers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Let’s now re-run the same ETL pipeline on our scaled cluster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="c1"&gt;# re-run etl pipeline&lt;/span&gt;
&lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ujson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;PushEvent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_dataframe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;s3://coiled-datasets/etl/test.parq&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pyarrow&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snappy&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;CPU&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="mf"&gt;11.4&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;12.5&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;Wall&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt; &lt;span class="mi"&gt;53&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’ve cut the runtime in half, great job!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="converting-large-json-to-parquet-summary"&gt;
&lt;h2&gt;Converting Large JSON to Parquet Summary&lt;/h2&gt;
&lt;p&gt;In this notebook, we showed how to convert JSON to Parquet by converting a raw JSON data into a flattened DataFrame and stored it in the efficient Parquet file format on a cloud object-store. We performed this workflow first on a single test file locally. We then scaled the same workflow out to run on the cloud using Dask clusters on Coiled in order to process the entire 75GB dataset.&lt;/p&gt;
&lt;p&gt;Main takeaways:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Coiled allows you to scale common ETL workflows to larger-than-memory datasets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only scale to the cloud if and when you need to. Cloud computing comes with its own set of challenges and overhead. So be strategic about deciding if and when to import Coiled and spin up a cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scale up your cluster for increased performance. We cut the runtime of the ETL function in half by scaling our cluster from 10 to 20 workers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have any questions or suggestions for future material, feel free to drop us a line at support&amp;#64;coiled.io or in our &lt;a class="reference external" href="https://join.slack.com/t/coiled-users/shared_invite/zt-hx1fnr7k-In~Q8ui3XkQfvQon0yN5WQ"&gt;Coiled Community Slack channel&lt;/a&gt;. We’d love to hear from you!&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/convert-large-json-to-parquet-with-dask.html"/>
    <summary>You can use Coiled, the cloud-based Dask platform, to easily convert large JSON data into a tabular DataFrame stored as Parquet in a cloud object-store. Start off by iterating with Dask locally first to build and test your pipeline, then transfer the same workflow to Coiled with minimal code changes. We demonstrate a JSON to Parquet conversion for a 75GB dataset that runs without downloading the dataset to your local machine.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/coiled-one-year-in.html</id>
    <title>Coiled, one year in</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>Matthew Rocklin</name>
    </author>
    <content type="html">&lt;section id="coiled-one-year-in"&gt;

&lt;p&gt;Coiled, a Dask company, is about one year old.  We’ll have a more official celebration in mid-February (official date of incorporation), but I wanted to take this opportunity to talk a little bit about the journey over the last year, where that has placed us today, and what I think comes next.&lt;/p&gt;
&lt;section id="history-building-a-company-from-an-existing-community"&gt;
&lt;h2&gt;History: building a company from an existing community&lt;/h2&gt;
&lt;p&gt;January last year I (matt) left NVIDIA to start Coiled.  This was an interesting and challenging process in which I received a lot of help.  This section chronicles that journey in three phases, and should be a good peek into what starting a company looks like for anyone interested in the process.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Company growth timeline" src="https://docs.coiled.io/_images/634880e98041414d2a59bba2_image-2-1024x292.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;section id="january-february-company-size-1"&gt;
&lt;h3&gt;January-February — Company size: 1&lt;/h3&gt;
&lt;p&gt;I spent most of this period meeting with many different kinds of people&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Other entrepreneurs who knew what to expect&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lawyers to help incorporate the company&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Potential anchor clients to help us bootstrap&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Potential first-hire employees to both help build out the company, and then run parts of it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Investors to explore if we wanted to take venture or angel funding&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I found that I had to make lots of high impact decisions on very little information.  This was an uncomfortable process, but in the end, things mostly worked out.  After you talk to a few people in any category (like lawyers, potential employees, clients, investors) you get a sense for what you’re looking for, and can usually make a sensible decision.&lt;/p&gt;
&lt;p&gt;This period ended with the &lt;a class="reference external" href="https://web.archive.org/web/20220619122148/https://blog.dask.org/2020/04/28/dask-summit"&gt;Dask Developer Summit&lt;/a&gt;, a great reminder that I wasn’t alone in this process, and then the beginning of COVID-19, a less-than-great reminder that business success relies on adapting to changing circumstances.&lt;/p&gt;
&lt;section id="entrepreneurs"&gt;
&lt;h4&gt;Entrepreneurs&lt;/h4&gt;
&lt;p&gt;Special thanks to folks like Travis Oliphant/Peter Wang (Anaconda), Wes McKinney (Ursa), Andrew Montalenti (Parse.ly), Jeremiah Lowin (Prefect), Rodrigo and Felipe Aramburu (BlazingSQL) and many others who helped out both logistically and emotionally.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="employees"&gt;
&lt;h4&gt;Employees&lt;/h4&gt;
&lt;p&gt;Most of Coiled’s current leadership team was assembled during this period (Rami, Hugo, James) while others were picked up later on in the process (Scott).&lt;/p&gt;
&lt;p&gt;There were also some mis-steps.  Very early on there was an engineer who built out a product prototype on his own time in order to see if we would work well together.  This ended up not working out, which caused mutual stress.  There were also early hires that ended up not being a good fit.  We found and used contractors for various functions, some of which worked well and some which didn’t.  In general, I learned that expecting and dealing with failure is probably more important than learning to avoid it.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="investors"&gt;
&lt;h4&gt;Investors&lt;/h4&gt;
&lt;p&gt;My original plan was to bootstrap.  I personally know many large companies that use Dask today that were happy to be early clients.&lt;/p&gt;
&lt;p&gt;However, it quickly became clear that VCs valued this nascent company at levels that, to me, seemed silly to pass up at the time.  We ended up taking $5,000,000 in seed funding, giving up 20% of the company while retaining all control (the only thing I couldn’t do was pay myself an unreasonable salary).  I ended up taking this deal, which in hindsight was great.  It allowed us to hire people with a wide range of risk tolerances, and allowed for a sensible work-life balance.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="march-august-company-size-6"&gt;
&lt;h3&gt;March-August — Company Size: 6&lt;/h3&gt;
&lt;p&gt;So we hired some engineers, a marketing guru, and went to work.  This was a very small team relative to the funding that we had, but it worked well.  An engineering team of four can be incredibly efficient.  We had a prototype up in June, and launched in the beginning of September.&lt;/p&gt;
&lt;p&gt;At the same time there was a lot of client negotiation and brand building.&lt;/p&gt;
&lt;section id="client-negotiation"&gt;
&lt;h4&gt;Client negotiation&lt;/h4&gt;
&lt;p&gt;We had good momentum in February with a few large financial clients in February.  In March all momentum was put on hold due to the COVID-19 outbreak.  Eventually COVID became a boon to technology companies, but back in March everyone was concerned about a financial crisis, and so spending in many organizations froze.&lt;/p&gt;
&lt;p&gt;By the summer things had thawed, and I was spending a good deal of time in business and legal negotiations.  I have some experience working on the business side of deals, but almost no experience navigating the legal and internal corporate politics of enterprise sales.  At this point we found Scott, who joined us as a sales advisor.  This made a world of difference.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="renewed-focus-on-smaller-teams"&gt;
&lt;h4&gt;Renewed focus on smaller teams&lt;/h4&gt;
&lt;p&gt;At the same time we were also talking to some much smaller groups using Dask and wanting to use Coiled.  These conversations were much more similar, and required an order of magnitude less work.   In general the person that we found ourselves talking to was …&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;A team leader of a data science / machine learning team of 2-10&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;6-12 months in to using Dask&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Had successfully used it on a big machine&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Had then stood up some cloud infrastructure, usually with Kubernetes to scale out&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Realized quickly that they definitely didn’t want to do this.  This was taking 25-50% of an FTE, and wasn’t even that good of a solution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Coiled’s lightweight platform did everything that they wanted, and was exactly the user experience that they were looking for&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;They’re happy to double their current Dask spending, typically around $3000-5000 / month in order to remove the DevOps pain, and hopefully become more efficient cost wise as well&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These deals were not nearly as lucrative as the larger enterprise deals, but they happened quickly, and resulted in us improving our product much more quickly.  Coiled’s engineering team transitioned from “doing what Matt thought was a good idea” to “doing what several customers were all telling us in unison”.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="spreading-the-word"&gt;
&lt;h4&gt;Spreading the Word&lt;/h4&gt;
&lt;p&gt;During this time, we also wanted to make sure that people could find us. To this end, we built out the MVP of our Marketing and Evangelism engines. This included our website, mailing list, a consistent blog with a consistent cadence, and a variety of social channels (Twitter, LinkedIn, YouTube, Medium). We also collaborated with broader PyData activities by appearing on podcasts, speaking at conferences, co-marketing with partner organizations, and presenting at Meetups.  These activities then lead to tight feedback loops between Product, Marketing, and Sales, which helped us when we &lt;a class="reference external" href="https://web.archive.org/web/20220619122148/https://medium.com/coiled-hq/coiled-dask-for-everyone-everywhere-376f5de0eff4"&gt;launched in late September.&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="september-december-company-size-15"&gt;
&lt;h3&gt;September-December — Company Size: 15&lt;/h3&gt;
&lt;p&gt;Things were clearly working at this stage.  We had signed a couple of the large enterprise contracts, had a bit less than $1M coming in the door, and had about 1000 signups on the public cloud service (only about 5-10% of which do meaningful work).&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Growth metrics" src="https://docs.coiled.io/_images/cumulative-users.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;p&gt;We were also slammed.&lt;/p&gt;
&lt;p&gt;Everyone was busy, and so we decided to grow.  Hugo/Marketing hired a couple of people to take the day-to-day burden off of him.  Scott/Sales hired our first inside sales rep.  Engineering went from four people to ten.&lt;/p&gt;
&lt;p&gt;A lot of this period was about going from a small nimble team where everyone knew what was going on to building out a machine.  Today if you sign up for and spin up a cluster on Coiled there are now automatic signals that go to Marketing and Sales.  You’ll get a ping from Oren (our inside sales rep) pretty soon after asking if you’d like to talk.  Some fraction of these turn into meetings with Scott/Sales, and some fraction of those turn into a commercial partnership going forward.&lt;/p&gt;
&lt;p&gt;It’s surprising how consistent/predictable this flow is.  As a Dask maintainer it has also been thrilling to see how many people out there use this software, and how many of them we can help get over some of the common early humps of devops, and Dask support.&lt;/p&gt;
&lt;p&gt;At the beginning of this period we were very busy as individuals.  Now that we have a machine flowing we’re very busy as a company.  The amount of pressure is just as high, but we’re more able to scale to adapt to it.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="where-coiled-is-today"&gt;
&lt;h2&gt;Where Coiled is today&lt;/h2&gt;
&lt;p&gt;Today Coiled is focused around &lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;cloud.coiled.io&lt;/a&gt;, a hosted Dask-as-a-service product that makes it easy for anyone to be successful scaling their Python data science / machine learning workloads.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Cost comparison" src="https://docs.coiled.io/_images/6348823dd5c17d614f4de5d5_cost.png" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Most of our feedback comes from individual users&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Most of our customers are small teams&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Most of our revenue comes from a few large enterprise contracts.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is great, we’ve developed a product that large companies want to pay for that benefits individuals as well.  We get to be a successful business and help the world at the same time.&lt;/p&gt;
&lt;p&gt;We’ve also found that many companies benefit from a little bit of advice.  Dask is designed to be easy to use, but running anything at scale generally benefits from access to expertise.  Coiled generally doesn’t take on services contracts (although we partner with and highly recommend &lt;a class="reference external" href="https://web.archive.org/web/20220619122148/https://www.quansight.com/"&gt;Quansight&lt;/a&gt; for this work).  However, we have found that we’re a good fit for both moderate annual contracts ($250k+) and small Q&amp;amp;A only monthly contracts ($2-5k) in order to make Dask perform optimally for companies.  It turns out that Dask is much more powerful if you have access to some of the core maintainers.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-future-of-coiled"&gt;
&lt;h2&gt;The future of Coiled&lt;/h2&gt;
&lt;p&gt;We started the company conservatively, hiring only a few engineers and a skeleton support staff.  This has allowed us to safely test a few hypotheses and learn a few lessons:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;A lot&lt;/em&gt; of small teams have started using Dask in the last 6-12 months&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Companies are quite happy to pay for managed Dask-as-a-service&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We knew a lot more about what we needed to build than we realized&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because of this, we have high confidence that we’re on a good path, and so we’re more comfortable spending from our war chest (thank you investors).  We’re hiring, and planning to expand significantly over the next few months in a few different directions.&lt;/p&gt;
&lt;section id="product"&gt;
&lt;h3&gt;Product&lt;/h3&gt;
&lt;p&gt;Our Coiled-on-AWS product is, today, the easiest and fastest way to get started with Dask on the Cloud.  Universally users and customers say that this is exactly the user experience that they want.&lt;/p&gt;
&lt;p&gt;However, they also say that they want many more things, including Azure/GCP support, integration with other products, a smoother GPU experience, and more.  Coiled also falls over from time to time when under heavy load.  These are all great features, and we’re working on them, but it’s also a lot for the current team to handle on our own.  We’re stretched thin and need help &lt;a class="reference external" href="https://jobs.lever.co/coiled"&gt;so come join us&lt;/a&gt;!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="enterprise"&gt;
&lt;h3&gt;Enterprise&lt;/h3&gt;
&lt;p&gt;Now that we’re within some larger companies there are a whole new set of constraints to deal with.  We have this covered with current staff, but the deals in the sales pipeline loom large over us, and we’re likely to be quickly overwhelmed.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="partnerships"&gt;
&lt;h3&gt;Partnerships&lt;/h3&gt;
&lt;p&gt;Coiled, like Dask, is designed to solve a specific piece of a larger problem.  Dask adds scalability, but relies on other projects like Pandas/Scikit-Learn for computation and UI.  Coiled adds scalability+DevOps but relies on other products like Prefect/Dash/Sagemaker to provide the other components of a data platform.&lt;/p&gt;
&lt;p&gt;Many product companies at about our size see the potential here, and are looking to add a scale button to their product.  We’re excited about these opportunities in order to see how effectively we can weave together a collection of similarly aligned companies.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask"&gt;
&lt;h3&gt;Dask&lt;/h3&gt;
&lt;p&gt;Originally Coiled didn’t devote substantial resources to Dask itself other than having existing maintainer/employees organize and maintain on the side, and as part of paid work with some of the larger clients (thank you NVIDIA and DE Shaw).&lt;/p&gt;
&lt;p&gt;However, there is still &lt;em&gt;a lot&lt;/em&gt; going on in Dask right now.  We need to step up on maintenance, and also drive forward some of the long running design changes that we’ve wanted for years.  We’re happy to say that we have about one maintainer joining the company per month for the next few months.  We’re excited about the added bandwidth that this will give the project.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="final-thoughts"&gt;
&lt;h2&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;We’ve built a machine that both generates revenue and serves the open-source community.  Commercial open source businesses are hard to get right, and while we’re not there yet, I think that we’re headed in a good direction.&lt;/p&gt;
&lt;p&gt;In a recent &lt;a class="reference external" href="https://web.archive.org/web/20220619122148/https://www.youtube.com/watch?v=xV-asqgKaL4"&gt;Talk Python to Me podcast &lt;/a&gt;with other startup founders, we talked a little bit about a sailing analogy to commercial open source companies.&lt;/p&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="Sailing analogy" src="https://docs.coiled.io/_images/634881e46940a606e80a3235_pexels-min-an-1213982.jpeg" style="width: 600px;" /&gt;
&lt;/figure&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Working on your own in OSS is like rowing a boat.  You have full control on where you go, but you can only go so fast, and you can’t stop rowing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Building a business around OSS introduces wind.  You go much faster, don’t have to row all the time, but you lose control about exactly where you end up&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Our goal is to build directional technology like rudders and jibs, which allow us to harness wind (commercial interest) and direct it to where we want to go (saving the world)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Coiled today looks like a little skiff, with a jury-rigged rudder and a plastic sheet as a front sail.  It works!  But it could also use some love.  We’re in calm waters and winds are strong, and so it feels like we could have a large impact.&lt;/p&gt;
&lt;p&gt;Coiled today has grown a lot from me sitting alone in an AirBnB in Oakland, CA.  We’re about twenty people strong today.  However, we’re also still very young and need scrappy individuals to jump on board and help us build stuff ranging from product, to Dask itself, to messaging, to sales infrastructure and operations.  If you’re looking for a new challenge with the potential for high impact then please take a look at our &lt;a class="reference external" href="https://jobs.lever.co/coiled"&gt;current jobs postings&lt;/a&gt;, or if nothing there looks right, but you think that you yourself would be a good fit then e-mail us at jobs.coiled.io.  At our stage companies are more about high-impact people than specific roles.&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled-one-year-in.html"/>
    <summary>Coiled, a Dask company, is about one year old.  We’ll have a more official celebration in mid-February (official date of incorporation), but I wanted to take this opportunity to talk a little bit about the journey over the last year, where that has placed us today, and what I think comes next.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/coiled-cloud-architecture.html</id>
    <title>Coiled Cloud Architecture</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="coiled-cloud-architecture"&gt;

&lt;p&gt;Running Dask in the cloud is easy.&lt;/p&gt;
&lt;p&gt;Running Dask in the cloud securely is hard.&lt;/p&gt;
&lt;p&gt;Running Dask for an enterprise in the cloud is &lt;em&gt;really&lt;/em&gt; hard.&lt;/p&gt;
&lt;p&gt;Over the last couple of years, Coiled has made a cloud-SaaS application that runs Dask for folks smoothly and securely in the cloud.&lt;/p&gt;
&lt;p&gt;We thought you would like to hear a bit about the choices we made and why.&lt;/p&gt;
&lt;section id="design-objectives"&gt;
&lt;h2&gt;Design objectives&lt;/h2&gt;
&lt;p&gt;Coiled had three main objectives when making our design:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Easy for users&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trustworthy for IT&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy for us to maintain&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This led us down a path of a cloud SaaS application with:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;A centralized control plane to make our development faster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It’s ok for Coiled to see users’ metadata&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deployment of Dask clusters within user cloud accounts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It’s not ok for Coiled to see users’ data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As few opinions as possible, following a “best of breed” philosophy&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We felt that this was the best choice to enable scalable Python for the most people in a way that we could execute on with a small team.  We’re pretty happy with the result.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="paths-we-didn-t-choose"&gt;
&lt;h2&gt;Paths we didn’t choose&lt;/h2&gt;
&lt;p&gt;There are lots of excellent paths here that make sense, but that we chose not to pursue.  For example:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Host computation on our servers, like Snowflake does&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We felt that most customers wouldn’t be comfortable giving a small company like us their data directly.&lt;/p&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;&lt;p&gt;Deploy entirely on customer accounts, like Cloudera does&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We felt that this would make larger customers more comfortable with us, but would slow down our development cycle too much.&lt;/p&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;&lt;p&gt;Build an end-to-end data science/engineering work environment, like Domino/Saturn Cloud/IBM/Databricks do&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We had built these kinds of systems pre-Coiled and found that they’re just really really hard to do well.  Data science environments were, in our opinion, too varied to give people a good experience.&lt;/p&gt;
&lt;p&gt;These are all great choices, and target different kinds of users.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="coiled-architecture"&gt;
&lt;h2&gt;Coiled architecture&lt;/h2&gt;
&lt;p&gt;Coiled is a web application that manages cloud resources within user accounts on their behalf.  You give us highly restricted access to your cloud.  Members of your team then ask for Dask clusters, and we set up those Dask clusters for them on the fly.&lt;/p&gt;
&lt;img alt="Coiled architecture" src="https://docs.coiled.io/_images/6520457a34a9fa5490a538f5_coiled-architecture.png" /&gt;
&lt;p&gt;We’ve found that this approach provides a good balance of giving users the data security that they need while letting us iterate very quickly on the product and so provide a better user experience. It’s a little nuanced though, let’s dig in a bit.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="admin-users-give-coiled-permissions-to-manage-cloud-resources"&gt;
&lt;h2&gt;Admin users give Coiled permissions to manage cloud resources&lt;/h2&gt;
&lt;p&gt;First, let’s go through how we coordinate cloud access safely to set up resources for users.&lt;/p&gt;
&lt;p&gt;As a user you need to to give Coiled enough permissions to do things like:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Create and destroy instances&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create and destroy network infrastructure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write and read logs into cloud services&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write and read images into Docker registries&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is done once and then the Coiled web app has these permissions until you revoke them. Notably, you don’t give any permission to read and write from sensitive data sources like cloud object stores, Snowflake, and so on. All Coiled can really do is run up your cloud bill.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="users-ask-for-dask-clusters"&gt;
&lt;h2&gt;Users ask for Dask clusters&lt;/h2&gt;
&lt;p&gt;Then when users ask for a Dask cluster&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled looks up your credentials, creates the necessary infrastructure, creates trusted security tokens, and gives those tokens to either side so that the user can then connect directly from their system to that cluster.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coiled sets up infrastructure, brokers a secure connection between the user and that infrastructure, and then gets out of the way as quickly as possible.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="users-send-data-access-credentials-to-the-cluster"&gt;
&lt;h2&gt;Users send data access credentials to the cluster&lt;/h2&gt;
&lt;p&gt;The user then has access to a Dask cluster without Coiled acting in the middle. This is great. The user then sends data access credentials from their machine up to the Dask cluster. This allows the cluster to access sensitive data resources within the company without Coiled itself ever seeing the data or the credentials.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="results"&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;The result is that anyone on your team can quickly get access to a fully authenticated and secure Dask cluster that can access all of the data that they have access to in a minute or two.&lt;/p&gt;
&lt;p&gt;Let’s talk a bit about how we actually set up and deploy resources.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="set-up-dask-workers-in-the-cloud"&gt;
&lt;h2&gt;Set up Dask workers in the cloud&lt;/h2&gt;
&lt;p&gt;To actually create the Dask scheduler and worker processes Coiled uses raw instances. This is in contrast to other options like using Kubernetes or container-as-a-service options like AWS Fargate.&lt;/p&gt;
&lt;p&gt;Kubernetes is great. We love Kubernetes here at Coiled, but it also introduces complexity that we actually don’t need to accomplish our goals. Also, removing the intermediate layer of Kubernetes allowed us to develop a product that was more flexible and raw-metal.&lt;/p&gt;
&lt;p&gt;Instead, we use technologies like EC2 fleets so that we could easily adapt to our customers’ many varied workloads with often very different hardware requirements. Once we have a bunch of VMs at our disposal we need to install just the right versions of Python packages on them. This can be tricky.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="manage-python-libraries-in-the-cloud"&gt;
&lt;h2&gt;Manage python libraries in the cloud&lt;/h2&gt;
&lt;p&gt;In order for Dask to work well, the versions of software libraries need to match exactly between where Coiled/Dask is invoked, and the workers that are run in the cloud. We do this in two ways (learn more in &lt;a class="reference external" href="https://docs.coiled.io/user_guide/software/index.html"&gt;our documentation&lt;/a&gt;):&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;We enable users to ask for specific conda/pip packages, and build docker images to their specification.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_software_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;prefect-flow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;conda&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;channels&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;conda-forge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
           &lt;span class="s2"&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;coiled-runtime&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;prefect&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This works well, but can get a bit finicky especially when the user’s environment changes rapidly.  Adding a conda solve and docker build step into development cycles causes a lot of slowdown and pain.&lt;/p&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;&lt;p&gt;We dynamically query the user’s local environment and install those packages on-the-fly This is far more magical, but works pretty well:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package_sync&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="handle-dask-logs-in-the-cloud"&gt;
&lt;h2&gt;Handle Dask logs in the cloud&lt;/h2&gt;
&lt;p&gt;When something breaks users often want to go and look at logs. Where should these logs go?&lt;/p&gt;
&lt;p&gt;As always, Coiled tries hard not to store anything from the user, and instead rely on cloud-native solutions.  This means that for AWS we store user logs in CloudWatch, and for Google Cloud we store user logs in Google Cloud Logging.  We provide easy methods to query these logs, but they are also easily accessible from however the user already handles logs within their organization.&lt;/p&gt;
&lt;p&gt;As always, Coiled tries to be convenient, but not intrusive.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="x7-tracking-of-cluster-status-and-dask-usage"&gt;
&lt;h2&gt;24x7 tracking of cluster status and Dask usage&lt;/h2&gt;
&lt;p&gt;Finally, Coiled also acts as a watchdog over your Dask processes.  If a cluster has been idle for too long we’ll kill it.  If a user is launching GPU resources and not using them, we make folks aware of it.  Gathering usage information is key.&lt;/p&gt;
&lt;p&gt;To accomplish this, Coiled installs a SchedulerPlugin in the Dask schedulers that tracks various usage metrics like idleness, which operations are running, etc. This information is &lt;em&gt;really&lt;/em&gt; useful for debugging and cost optimization, but can also be somewhat invasive. We allow users to turn various bits of this data tracking on and off with configuration (see the &lt;a class="reference external" href="https://docs.coiled.io/user_guide/analytics.html"&gt;Coiled documentation on analytics&lt;/a&gt;).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="billing"&gt;
&lt;h2&gt;Billing&lt;/h2&gt;
&lt;p&gt;Coiled is free for the first 500 CPU hours a month (although you still have to pay your cloud provider).  Beyond that we charge $0.05 per managed CPU hour, with rates that decline based on usage.&lt;/p&gt;
&lt;p&gt;We’ve found that this is enough for individuals to operate entirely for free, and for folks at larger organizations to “try before they buy” pretty effectively. For companies who do spend thousands of dollars or more per month, we look comparable to services like Databricks or SageMaker. Especially when compared against the personnel cost of building and maintaining a system like Coiled, this very obviously becomes worth the cost.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="id1"&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;aside class="system-message"&gt;
&lt;p class="system-message-title"&gt;System Message: INFO/1 (&lt;span class="docutils literal"&gt;/home/runnerx/actions-runner/_work/platform/platform/python-api/docs/source/blog/coiled-cloud-architecture.md&lt;/span&gt;, line 158); &lt;em&gt;&lt;a href="#id1"&gt;backlink&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Duplicate implicit target name: “results”.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Anyone with a cloud account can connect their accounts to Coiled in a couple of minutes, and can be scaling happily afterwards, knowing that they’re secure. Our development teams have enough information to see what’s going on to rapidly iterate on a better user experience, while users can rest assured that both their data and budgets are safe and secure. For a more details, see &lt;a class="reference external" href="https://docs.coiled.io/user_guide/why.html"&gt;Why Coiled?&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you’re interested, Coiled is easy to try out. Just run the following in any Python-enabled environment after you’ve created a Coiled account:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;
&lt;span class="n"&gt;coiled&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;
&lt;span class="n"&gt;coiled&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;
&lt;span class="n"&gt;ipython&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;package_sync&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;da&lt;/span&gt;
&lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/coiled-cloud-architecture.html"/>
    <summary>Running Dask in the cloud is easy.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/code-formatting-jupyter-notebooks-with-black.html</id>
    <title>Code Formatting Jupyter Notebooks with Black</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="code-formatting-jupyter-notebooks-with-black"&gt;

&lt;p&gt;&lt;a class="reference external" href="https://github.com/psf/black"&gt;Black&lt;/a&gt; is an amazing Python code formatting tool that automatically corrects your code.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://github.com/ryantam626/jupyterlab_code_formatter"&gt;jupyterlab_code_formatter&lt;/a&gt; extension makes it easy for you to run black in a Jupyter Notebook with the click of a button.&lt;/p&gt;
&lt;p&gt;This post explains how to use the plugin and why consistent code formatting is so important.&lt;/p&gt;
&lt;section id="installation"&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;You can add the necessary libraries to your conda environment with this command:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;conda&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;conda&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;forge&lt;/span&gt; &lt;span class="n"&gt;jupyterlab_code_formatter&lt;/span&gt; &lt;span class="n"&gt;black&lt;/span&gt; &lt;span class="n"&gt;isort&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can also add the three required dependencies to a YAML file if you’re using a multi-environment conda workflow.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="usage"&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;Restart your Jupyter Lab session, and you’ll now see this formatting button:&lt;/p&gt;
&lt;img alt="Image" src="https://docs.coiled.io/_images/633f7b5a3a4f10fe585db590_image.png" /&gt;
&lt;p&gt;Click the button to format all the cells in your notebook with Black’s code formatting rules.&lt;/p&gt;
&lt;p&gt;This notebook is running locally, but you can use Coiled to &lt;a class="reference external" href="https://docs.coiled.io/user_guide/notebooks.html"&gt;run Jupyter Notebooks on the cloud&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="example-code-formatting"&gt;
&lt;h2&gt;Example code formatting&lt;/h2&gt;
&lt;p&gt;Let’s take a look at a chunk of code that’s not formatted and then see how it looks after it’s been formatted.  Here’s the unformatted code.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;use_ssl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This code uses inconsistent apostrophe symbols, and the last argument to read_parquet doesn’t have a trailing comma.&lt;/p&gt;
&lt;p&gt;Click the code formatting button, and this cell is automatically reformatted as follows:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You might feel like code formatting is minor, but it’s actually really important.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="why-code-formatting-is-important"&gt;
&lt;h2&gt;Why code formatting is important&lt;/h2&gt;
&lt;p&gt;Before automatic code formatting was popular, teams would create style guides and manually enforce coding rules.&lt;/p&gt;
&lt;p&gt;This was tedious and sometimes contentious.  Some programmers are more passionate about their whitespace preferences than you might think!&lt;/p&gt;
&lt;p&gt;Spending time creating style guides, commenting about formatting in pull requests, and arguing about double or single quotes is not a good use of developer time.  It’s better to follow a community-accepted, fully automated solution.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="other-code-formatting-options"&gt;
&lt;h2&gt;Other code formatting options&lt;/h2&gt;
&lt;p&gt;jupyterlab_code_formatter also supports YAPF and Autopep8 code formatting.  We recommend using Black because it’s popular and used in several popular open-source libraries.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;JupyterLab has an extension that makes it easy to automatically format the Python code in your notebooks.&lt;/p&gt;
&lt;p&gt;Automatic code formatters make it easy for teams to consistently style their code without extra work.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/code-formatting-jupyter-notebooks-with-black.html"/>
    <summary>Black is an amazing Python code formatting tool that automatically corrects your code.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/cloud-etl-automation-github-actions-coiled.html</id>
    <title>Cloud ETL automation with Github Actions and Coiled</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="cloud-etl-automation-with-github-actions-and-coiled"&gt;

&lt;p&gt;Github Actions let you launch automated jobs from your Github repository. Coiled lets you run your Python code in the cloud. Combining the two gives you lightweight workflow orchestration for heavy ETL (extract-transform-load) jobs that can run in the cloud without any complicated infrastructure provisioning or DevOps.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/coiled/ci-with-coiled/blob/main/.github/workflows/quickstart.yaml"&gt;This Github Action&lt;/a&gt; runs an ETL job on 15 GB of data and writes the results to an Amazon S3 bucket. Running this with a Github Action alone would not be possible as the default virtual machines on which Github Actions are run only have 7GB of memory. Running the action on a Coiled cluster lets you crunch the entire dataset. Since the Coiled cluster runs on AWS, the data can easily be written to an S3 bucket.&lt;/p&gt;
&lt;p&gt;This post will demonstrate how running Github Actions on Coiled can be a useful way to schedule automated data-processing jobs. We’ll start with some background on Github Actions and Coiled and then walk through an example script in detail.&lt;/p&gt;
&lt;section id="github-actions-s3-and-coiled-workflow-orchestration"&gt;
&lt;h2&gt;Github Actions, S3 and Coiled: Workflow Orchestration&lt;/h2&gt;
&lt;p&gt;Many data analysts have tasks that need to be run on a fixed schedule. For example, you might want to fetch new data every day at 8AM, preprocess it, and use it to train or update a machine learning model. Workflow orchestration tools allow you to schedule jobs like this without human intervention. Popular orchestration tools include Apache Airflow, Prefect, Argo and others.&lt;/p&gt;
&lt;p&gt;Managing these workflow orchestration tools can be more work than you want to do. &lt;a class="reference external" href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt; is a lightweight and cheap (&lt;a class="reference external" href="https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions"&gt;or even free)&lt;/a&gt; alternative that lets you run automated jobs based on predefined trigger events. Since many developers and data analysts already have their code in Github repositories, this can be an ideal way to run automated jobs without having to learn another tool or manage a full-fledged orchestration deployment.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="github-actions-s3-and-coiled-scale-to-the-cloud"&gt;
&lt;h2&gt;Github Actions, S3 and Coiled: Scale to the Cloud&lt;/h2&gt;
&lt;p&gt;Github Actions are run on virtual machines that are limited in the hardware resources they let you access. This makes sense because Github Actions are free (entirely for public repositories, up to a limit for private ones) and Github probably doesn’t want you running massive workloads on their free service. By default, Github Actions are hosted on ‘runners’ with &lt;a class="reference external" href="https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners"&gt;7GB of RAM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So what do you do if you want to run a scheduled ETL job with Github Actions that requires more memory resources than the Github Action default?&lt;/p&gt;
&lt;p&gt;If you want to use Github Actions for more compute-intensive or memory-bound tasks, you’ll need to deploy the task to another virtual machine or cluster of machines. There are ways to do this yourself with Amazon ECS, Azure or Google Kubernetes. But deploying Github Actions to the cloud yourself is &lt;a class="reference external" href="https://docs.github.com/en/actions/deployment/deploying-to-your-cloud-provider/deploying-to-amazon-elastic-container-service"&gt;complex&lt;/a&gt; and time-intensive. Github also offers an option to use &lt;a class="reference external" href="https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners"&gt;self-hosted runners&lt;/a&gt; for Github Actions. These also require an extensive installation, setup and maintenance protocol.&lt;/p&gt;
&lt;p&gt;Coiled makes it easy to deploy Python code to the cloud. Coiled handles all the infrastructure setup for you, including software environments, security and cost-management. Coiled Clusters are launched on-demand and can be stopped immediately once the computation is completed, so there will be no unexpected costs. Even better: Coiled currently provides a generous free tier, so you’ll only be paying your own cloud account fees.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="github-actions-s3-and-coiled-in-action"&gt;
&lt;h2&gt;Github Actions, S3 and Coiled: In Action&lt;/h2&gt;
&lt;p&gt;We’ve put together &lt;a class="reference external" href="https://github.com/coiled/ci-with-coiled/blob/main/.github/workflows/quickstart.yaml"&gt;a Github Action&lt;/a&gt; that you can customize to run your own automated ETL jobs. Let’s walk through the script in detail. If you’re totally new to Github Actions, we recommend following &lt;a class="reference external" href="https://docs.github.com/en/actions/quickstart"&gt;the official Github tutoria&lt;/a&gt;l first.&lt;/p&gt;
&lt;p&gt;Let’s break down the workflow that our Github Action performs step by step.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="defining-your-github-action"&gt;
&lt;h2&gt;Defining your Github Action&lt;/h2&gt;
&lt;p&gt;We’ll set this ETL job to run on a schedule: it will be triggered at 12pm (noon) every day.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="c1"&gt;# run at noon every day&lt;/span&gt;
   &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0 12 * * *&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Github Actions uses &lt;a class="reference external" href="https://www.netiq.com/documentation/cloud-manager-2-5/ncm-reference/data/bexyssf.html"&gt;the cron format&lt;/a&gt; to specify dates and time. You can also have your Github Actions triggered by other events, such as push events to specific repositories.&lt;/p&gt;
&lt;p&gt;We’ll then define our quickstart job:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;quickstart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ubuntu-latest&amp;quot;&lt;/span&gt;
   &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And define the steps this job will consist of:&lt;/p&gt;
&lt;div class="highlight-YAML notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Needed to run on PR or branches&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Checkout source&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/checkout@v2&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Set up local environment&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;conda-incubator/setup-miniconda@v2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;miniforge-variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Mambaforge&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;use-mamba&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;environment-file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ci/environment-3.9.yaml&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Run quickstart&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;DASK_COILED__TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;${{ secrets.COILED_TOKEN }}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;${{ secrets.BLOG_AWS_ACCESS_KEY_ID }}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;${{ secrets.BLOG_AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python scripts/quickstart.py&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;The first and second steps are standard setup protocols. The third step is the one that will actually perform our ETL job: it will run the Python script quickstart.py located in the scripts directory.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="authenticate-with-s3"&gt;
&lt;h2&gt;Authenticate with S3&lt;/h2&gt;
&lt;p&gt;Notice that we passed four environment variables to this script:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;DASK_COILED__TOKEN contains the token your script will need to authenticate with Coiled and launch cloud computing resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY contain your AWS credentials that let you authenticate with S3.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Secrets are pulled from your Github account settings. Follow &lt;a class="reference external" href="https://docs.github.com/en/actions/security-guides/encrypted-secrets"&gt;these steps&lt;/a&gt; if you do not know how to store secrets in your Github repository.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="running-your-etl-job"&gt;
&lt;h2&gt;Running your ETL Job&lt;/h2&gt;
&lt;p&gt;Now let’s take a closer look at the &lt;a class="reference external" href="https://github.com/coiled/ci-with-coiled/blob/main/scripts/quickstart.py"&gt;quickstart.py script&lt;/a&gt; that our Github Action will run.&lt;/p&gt;
&lt;p&gt;After importing dependencies, the script starts by launching a Coiled cluster:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;GITHUB_RUN_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GITHUB_RUN_ID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;github-actions-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GITHUB_RUN_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;worker_memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;8Gib&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This spins up a cluster of 10 virtual machines with 8GB of RAM each. Coiled automatically replicates the local Python environment in your cluster. You can &lt;a class="reference external" href="https://docs.coiled.io/user_guide/clusters/index.html"&gt;customize your cluster&lt;/a&gt; to specify different instance types and hardware specs. Each cluster that is launched is given a unique name using the Github Run ID.&lt;/p&gt;
&lt;p&gt;We then connect the Python client running the script (in this case the virtual machine that’s running the Github Action) to our Coiled cluster:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now we’re all set to load in our large dataset. We’ll use Dask to do this. &lt;a class="reference external" href="https://dask.org/"&gt;Dask&lt;/a&gt; is a parallel computing library for Python that makes it easy to scale your pandas and NumPy workloads to larger-than-memory datasets.&lt;/p&gt;
&lt;p&gt;The code below loads the Parquet data and performs a groupby aggregation:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/nyc-tlc/2019&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;passenger_count&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;tip_amount&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# perform groupby aggregation&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;passenger_count&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tip_amount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="write-to-s3"&gt;
&lt;h2&gt;Write to S3&lt;/h2&gt;
&lt;p&gt;Chances are that once you have this result, you’d like to store it somewhere. Let’s write the result to an Amazon S3 bucket. We’ll write to a dedicated S3 bucket and create a unique directory for each Github Action run:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# write result to s3&lt;/span&gt;
&lt;span class="n"&gt;bucket_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3://coiled-github-actions-blog/github-action-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GITHUB_RUN_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/quickstart.parquet&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bucket_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The result was successfully written to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The cluster will shutdown automatically after 20 minutes, but we can turn it off with:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;All set! Once pushed to the repository, the Github Action will run this script at noon every day. You can of course adjust the cron schedule to run this at a different interval. After a few runs, your S3 bucket UI will look something like this, with a unique directory for each completed run:&lt;/p&gt;
&lt;img alt="Coiled blog github actions" src="https://docs.coiled.io/_images/6372d950ee01afc01826807e_Coiled%2520blog%2520-%2520github%2520actions%25201.png" /&gt;
&lt;/section&gt;
&lt;section id="why-coiled"&gt;
&lt;h2&gt;Why Coiled?&lt;/h2&gt;
&lt;p&gt;While there are other ways to run your Github Actions on cloud-computing resources (or with self-hosted runners), these all require complicated setup processes and careful maintenance to implement security protocols and avoid unnecessary costs. Coiled takes care of all of this DevOps for you so you can focus on writing and running your Python code.&lt;/p&gt;
&lt;p&gt;Coiled is also very scriptable and is not picky about where you are running your Python code. Unlike some of the other cloud-computing services, Coiled does not limit you to launching clusters from hosted notebooks. This means you can launch Coiled clusters from anywhere you can run Python code, including Github Actions (see our &lt;a class="reference internal" href="user_guide/integrations.html"&gt;&lt;span class="doc"&gt;integration guide&lt;/span&gt;&lt;/a&gt; for more details).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="github-actions-s3-and-coiled-your-turn"&gt;
&lt;h2&gt;Github Actions, S3 and Coiled: Your turn&lt;/h2&gt;
&lt;p&gt;If you’re ready to build your own Github Actions that leverage cloud-computing resources, follow these steps:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Fork the &lt;a class="reference external" href="https://github.com/coiled/github-actions-with-coiled"&gt;template repository&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://cloud.coiled.io/signup"&gt;Sign up&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edit the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;quickstart.py&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;quickstart.yaml&lt;/span&gt;&lt;/code&gt; files to suit your specific use case&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Include your Coiled token and AWS S3 credentials in your Github settings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Launch your Github Action!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/cloud-etl-automation-github-actions-coiled.html"/>
    <summary>Github Actions let you launch automated jobs from your Github repository. Coiled lets you run your Python code in the cloud. Combining the two gives you lightweight workflow orchestration for heavy ETL (extract-transform-load) jobs that can run in the cloud without any complicated infrastructure provisioning or DevOps.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/better-shuffling-in-dask-a-proof-of-concept.html</id>
    <title>Better Shuffling in Dask: a Proof-of-Concept</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="better-shuffling-in-dask-a-proof-of-concept"&gt;

&lt;p&gt;&lt;em&gt;Updated May 16th, 2023: With release 2023.2.1, dask.dataframe introduced this shuffling method called P2P, making sorts, merges, and joins faster and using constant memory. Benchmarks show impressive improvements. &lt;a class="reference external" href="https://blog.coiled.io/blog/shuffling-large-data-at-constant-memory.html"&gt;See our blog post&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Over the last few weeks, the Coiled team has been experimenting with a new approach to DataFrame shuffling in Dask. It’s not ready for release yet, but it does show a promising path forward for significantly improving performance, and we’d love it if you &lt;a class="reference external" href="https://github.com/dask/dask/pull/8223"&gt;tried it out&lt;/a&gt;!&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Good news 👍 : our proof-of-concept can shuffle &lt;em&gt;much&lt;/em&gt; larger datasets than were ever possible before. We’ve shuffled 7TiB DataFrames with 40,000 partitions and only needed a fraction of the full dataset size in total worker RAM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bad news 👎 : the code is just a proof-of-concept, and not stable or maintainable enough to merge yet.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="shuffling-is-easy"&gt;
&lt;h2&gt;Shuffling is easy&lt;/h2&gt;
&lt;p&gt;Shuffling is a key part of most ETL workloads (namely the “transform” part, where the dataset is split up one way and needs to be reorganized along different lines). merge, set_index, groupby().apply(), and other common operations are all (sometimes) backed by shuffling.&lt;/p&gt;
&lt;p&gt;In principle, shuffling is very simple: you look at a row in a DataFrame, figure out which output partition number it belongs to, figure out which worker should hold that partition in the end, and send the data along:&lt;/p&gt;
&lt;img alt="Image 3" src="https://docs.coiled.io/_images/633f7b5df9c63728c2ce7ac6_image-3-700x340.png" /&gt;
&lt;p&gt;&lt;em&gt;Illustration of how a distributed shuffle works in principle&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Oddly though, this operation that sounds like a simple for-loop is awkward to express as a &lt;a class="reference external" href="https://docs.dask.org/en/latest/graphs.html"&gt;task graph&lt;/a&gt; like Dask uses. Every output partition depends on every input partition, so the graph becomes N² in size. Even with reasonable amounts of input data, this can crash the Dask scheduler.&lt;/p&gt;
&lt;img alt="Image 7" src="https://docs.coiled.io/_images/633f7b5df9c6372f4bce7ac3_image7.png" /&gt;
&lt;p&gt;&lt;em&gt;The current task graph of a very small shuffle (20 partitions). It grows quadratically with the number of partitions, so imagine this times 100 or 1000—it gets large very quickly!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And when it does work, the scheduler is still the bottleneck, trying to micromanage the transfer of these many sub-fragments of data. Since there’s no heavy number-crunching involved, the limiting factor should be IO: sending data over the network, and to disk if shuffling a larger-than-memory dataset. However, Dask’s current implementation isn’t efficient enough to reach these hardware limits.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="a-new-approach-to-shuffling-in-dask"&gt;
&lt;h2&gt;A new approach to shuffling in Dask&lt;/h2&gt;
&lt;p&gt;So we tested a rather heretical idea to make Dask’s shuffling better: just bypass a lot of Dask. We wrote an extension that attaches to every worker and uses the worker’s networking APIs to send data directly to its peers without scheduler intervention. We still used a task graph, letting us lean on the centralized scheduler for some synchronization that’s otherwise tricky to handle in a distributed system, but effectively hid the low-level details from the scheduler, keeping the graph small (O(N) instead of O(N²)).&lt;/p&gt;
&lt;p&gt;We confirmed two wholly unsurprising things:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;A dedicated, peer-to-peer, decentralized implementation is much more performant and gives us the low-level control to manage memory, network, and disk better than the centrally-scheduled equivalent can.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bypassing the task graph in a system designed around task graphs leads to some pretty hacky code.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is great to confirm! Because it tells us:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;We’re not condemned to bad shuffles forever—there’s a path forward.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It may be worth it to adjust the system so that bypassing task graphs isn’t a hack, but a supported (and tested and maintained) feature.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The second won’t be a radical change, or even something any but the most advanced end-users would ever do. Rather, it’s a practical recognition that Dask’s goal is to make scalable parallel computing broadly accessible to Python users, and task graphs are not the only way to do that. Graphs serve Dask very well, but like every tool, they’re not perfect for every situation. In select situations that are both very important and very poorly suited to task graphs, we can help users a lot by not clinging too tightly to existing abstractions.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="actually-shuffling-is-hard"&gt;
&lt;h2&gt;Actually, shuffling is hard&lt;/h2&gt;
&lt;p&gt;With that philosophizing out of the way, we’ll talk about an assortment of interesting challenges we ran into with this new system.&lt;/p&gt;
&lt;p&gt;Though the shuffling algorithm is simple in principle, doing it well takes some Goldilocks-like balancing of resources (memory, network, disk, concurrency). Both network and disk perform better in batches, rather than lots of tiny tiny sends/writes. So we added logic to accumulate pieces of the input partitions until we have enough to send to a given worker and to accumulate those received pieces until we have enough to write to disk. Buffering like this while not running out of memory is a balancing act. In our final implementation, we hope to make this both simpler and more self-tuning.&lt;/p&gt;
&lt;p&gt;But it turns out the whole thing may have been a premature optimization since network and disk are still far from being the bottleneck.&lt;/p&gt;
&lt;section id="gil-gotchas"&gt;
&lt;h3&gt;GIL Gotchas&lt;/h3&gt;
&lt;p&gt;We profiled the shuffle using &lt;a class="reference external" href="https://github.com/benfred/py-spy"&gt;py-spy&lt;/a&gt;, which gave a detailed view of what was happening from our Python code all the way down to the lowest-level C code within the CPython standard library. This quickly showed we were spending 70% of our time waiting on the GIL (Global Interpreter Lock)!&lt;/p&gt;
&lt;img alt="Image 4" src="https://docs.coiled.io/_images/633f7b5df9c6373a54ce7ac7_image-4-700x488.png" /&gt;
&lt;p&gt;&lt;em&gt;py-spy profile of our new shuffle running on a worker. The beige pthread_cond_timedwait at the bottom of the stacks is time spent blocked waiting for the GIL.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Since we’re writing a multi-threaded program, this is concerning in general—it means we won’t be able to get much parallelism out of it. Our worker threads (which split and re-group DataFrames as they’re sent or received) call pd.concat a lot, which internally calls np.concatenate, which, it turns out, doesn’t release the GIL. So this is &lt;a class="reference external" href="https://github.com/pandas-dev/pandas/issues/43155#issuecomment-915689887"&gt;one problem&lt;/a&gt;: we can’t parallelize our CPU work as we’d hoped to.&lt;/p&gt;
&lt;p&gt;But the bigger problem is that this is slowing down our networking enormously. We’re using non-blocking sockets, so the calls to send and receive are supposed to be nearly instantaneous and—as the name suggests—non-blocking. But because these calls actually have to block on re-acquiring the GIL, they might take 10x longer than they need to, gumming up the asyncio event loop (the cardinal sin of asynchronous programming). In the end, this prevents the &lt;em&gt;next&lt;/em&gt; send or receive from running as soon as it should have, and so on, such that we complete these sends and receives far less frequently than we want to.&lt;/p&gt;
&lt;p&gt;This is a well-known problem in Python that’s been debated since 2010 (&lt;a class="reference external" href="https://bugs.python.org/issue7946"&gt;bpo-7946&lt;/a&gt;) known as the “convoy effect”. For an excellent in-depth explanation of this, see &lt;a class="reference external" href="https://tenthousandmeters.com/blog/python-behind-the-scenes-13-the-gil-and-its-effects-on-python-multithreading/"&gt;this blog post&lt;/a&gt;. But the premise is something like this:&lt;/p&gt;
&lt;p&gt;There are hundreds of people in a house who are sharing one bathroom. Most of them want to shower (slow). Some of them just want to brush their teeth (fast). But the tooth-brushers are very considerate, and recognize that while they’re brushing their teeth, they don’t actually need to be in the bathroom—they could step outside, then come back in when they’re done to spit in the sink. But what happens is that when they step out, usually a showerer immediately jumps in and hogs the bathroom for a long time while singing show tunes, leaving the tooth-brusher awkwardly waiting around through a rendition of the entire soundtrack to &lt;em&gt;Hamilton&lt;/em&gt;. Overall, it means the tooth-brushing takes far longer than it needs to, and if the tooth-brushers had just been a little less considerate, they could have finished much faster and nobody would have complained much.&lt;/p&gt;
&lt;p&gt;As much as this sounds bleak (a known issue for over a decade! in CPython! labeled as wontfix!), I’m actually very excited and optimistic about this. We’ve understood the root problem, and it means we can stop worrying about optimizing other stuff for now, since it won’t have much of an effect. And I think there are opportunities for both an “avoid it” solution (work with NumPy devs to release the GIL during concat? avoid pandas and NumPy altogether, and use Arrow/polars/etc. instead?) and a “fix it” solution (don’t release the GIL with non-blocking sockets, either in CPython or in an alternative library like uvloop?). And either approach will benefit not just shuffling, but Dask as a whole, and likely the entire Python community.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;As a side note, this particularly shows how valuable good profiling tools like py-spy are. If we’d just looked at the Python call stack (say, through Dask’s built-in profiler), we might have seen read and write took most of the time and concluded that we were network bound, and the network was just slow. But by being able to look deeper, we discovered that most of that supposed networking time was actually spent doing nothing at all.&lt;/em&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="pandas-concatenation"&gt;
&lt;h3&gt;Pandas concatenation&lt;/h3&gt;
&lt;p&gt;Besides the GIL issue, we found concatenating many pandas DataFrames was slower than we expected. In &lt;a class="reference external" href="https://github.com/pandas-dev/pandas/issues/43155"&gt;conversations with pandas developers&lt;/a&gt;, they’ve identified a couple of issues that will hopefully make their way into performance improvements in future pandas versions.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="root-task-overproduction"&gt;
&lt;h3&gt;Root task overproduction&lt;/h3&gt;
&lt;p&gt;Another interesting problem was that Dask workers would run out of memory because they loaded more input DataFrames upfront than they needed to, instead of loading and immediately starting to transfer them to other workers. This turns out to be a rather core consequence of the way distributed scheduling is designed in Dask, and is an issue that’s been bothering many Dask users &lt;a class="reference external" href="https://github.com/dask/distributed/issues/2602"&gt;for a while&lt;/a&gt;, and has been &lt;a class="reference external" href="https://github.com/dask/distributed/issues/3974"&gt;proposed to fix&lt;/a&gt; (but the fix is a complex change).&lt;/p&gt;
&lt;p&gt;You can read about the problem in more detail &lt;a class="reference external" href="https://github.com/dask/distributed/issues/5223"&gt;in this issue&lt;/a&gt;, but the premise is that the scheduler tells workers about all of the root tasks upfront, but doesn’t tell them about downstream tasks until it hears that the corresponding root tasks have completed. In the time gap between a root task completing and the scheduler telling the worker, there’s something else to use that root data for, the worker goes off and runs extra root tasks since that’s all it knows about.&lt;/p&gt;
&lt;img alt="Image 6" src="https://docs.coiled.io/_images/633f7b5df9c6373120ce7ac5_image-6-700x590.png" /&gt;
&lt;p&gt;&lt;em&gt;How root tasks get over-produced&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is an important issue, and we’re excited to solve it. However, for this particular case, we realized there was a simple workaround: the “fusion” step of graph optimization. By fusing the “load the data” root task to its single “transfer the data” dependent task (producing one “load and transfer the data” root task), it didn’t matter if workers ran too many of those tasks initially. Thanks to &lt;a class="reference external" href="https://blog.dask.org/2021/07/07/high-level-graphs"&gt;recent work&lt;/a&gt; on High-Level Graphs, with a slight tweak to the graph, this fusion step happens automatically and will fuse across most DataFrame operations.&lt;/p&gt;
&lt;img alt="Image 7" src="https://docs.coiled.io/_images/633f7b5df9c63735ffce7ac4_image-7-700x320.png" /&gt;
&lt;p&gt;&lt;em&gt;Task fusion to the rescue!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We’re glad this workaround was easy, since we expect that delivering effective shuffling can be done a bit faster than solving this underlying issue, and it will help lots of users. But addressing this root task overproduction issue is very high on the priority list after that.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="trying-this-out"&gt;
&lt;h2&gt;Trying this out&lt;/h2&gt;
&lt;p&gt;If you’ve been struggling with large set_index or groupby().apply() operations failing, we’d love to get some early feedback on this proof-of-concept PR. Keep in mind that this is experimental and has some very sharp edges; see the warnings listed at &lt;a class="github reference external" href="https://github.com/dask/dask/pull/8223"&gt;dask/dask#8223&lt;/a&gt;. Nonetheless, it does work on straightforward cases, so if it works for you—or it doesn’t—we’d love to hear the feedback as comments on that PR (or new issues, if you encounter specific problems).&lt;/p&gt;
&lt;p&gt;Here’s how you can try out the new shuffle implementation on Coiled.&lt;/p&gt;
&lt;p&gt;First, create a local virtual environment and install the versions from git:&lt;/p&gt;
&lt;div class="highlight-bash notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;conda&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;shuffle&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;conda-forge&lt;span class="w"&gt; &lt;/span&gt;dask&lt;span class="w"&gt; &lt;/span&gt;coiled&lt;span class="w"&gt; &lt;/span&gt;jupyterlab&lt;span class="w"&gt; &lt;/span&gt;dask-labextension
$&lt;span class="w"&gt; &lt;/span&gt;conda&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;shuffle
$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-U&lt;span class="w"&gt; &lt;/span&gt;--no-deps&lt;span class="w"&gt; &lt;/span&gt;git+https://github.com/gjoseph92/dask@shuffle_service&lt;span class="w"&gt; &lt;/span&gt;git+https://github.com/dask/distributed
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then spin up a cluster, letting &lt;a class="reference external" href="https://docs.coiled.io/user_guide/package_sync.html"&gt;package sync&lt;/a&gt; handle replicating your environment in your cluster:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;worker_cpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;worker_memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;4GiB&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_for_workers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now make some example data, and try re-indexing it.&lt;/p&gt;
&lt;p&gt;Be sure to open the cluster dashboard at &lt;a class="reference external" href="https://cloud.coiled.io"&gt;https://cloud.coiled.io&lt;/a&gt; to see what’s going on.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datasets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeseries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2000-01-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2005-01-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dfz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dfz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;z&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;service&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Try removing the shuffle=”service” to see how much of an improvement this makes!&lt;/p&gt;
&lt;img alt="Image 2" src="https://docs.coiled.io/_images/633f7b5df9c6378706ce7ac8_image2-700x398.png" /&gt;
&lt;p&gt;&lt;em&gt;Before: re-indexing using the current task-based shuffle.&lt;/em&gt;&lt;/p&gt;
&lt;img alt="Image 1" src="https://docs.coiled.io/_images/633f7b5df9c637aa0bce7ac9_image1-700x398.png" /&gt;
&lt;p&gt;&lt;em&gt;After: using the experimental new shuffle&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here’s a before-and-after of the current standard shuffle versus this new shuffle implementation. The most obvious difference is memory: workers are running out of memory with the old shuffle, but barely using any with the new. You can also see there are almost 10x fewer tasks with the new shuffle, which greatly relieves pressure on the scheduler.&lt;/p&gt;
&lt;p&gt;Thanks for reading! And if you’re interested in trying out Coiled Cloud, which provides hosted Dask clusters, docker-less managed software, and one-click deployments, you can do so for free today when you click below.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/better-shuffling-in-dask-a-proof-of-concept.html"/>
    <summary>Updated May 16th, 2023: With release 2023.2.1, dask.dataframe introduced this shuffling method called P2P, making sorts, merges, and joins faster and using constant memory. Benchmarks show impressive improvements. See our blog post.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/accelerating-microstructural-analytics-dask-coiled.html</id>
    <title>Accelerating Microstructural Analytics with Dask and Coiled</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>null</name>
    </author>
    <content type="html">&lt;section id="accelerating-microstructural-analytics-with-dask-and-coiled"&gt;

&lt;p&gt;In this article, we will discuss an interesting use case of Dask and Coiled: &lt;strong&gt;Accelerating Volumetric X-ray Microstructural Analytics using distributed and high-performance computing&lt;/strong&gt;. This blog post is inspired by the &lt;a class="reference external" href="https://blog.kitware.com/accelerating-volumetric-x-ray-microstructural-analytics-with-dask-itk-from-supercomputers-to-the-cloud/"&gt;article published in Kitware Blog&lt;/a&gt; and corresponding &lt;a class="reference external" href="https://ieeexplore.ieee.org/document/9307950"&gt;research&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We will explore:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Research problem - analyzing large scale volumetric microstructural data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Environment and methods used for the research&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Role of Dask and Coiled&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="align-default" id="id1"&gt;
&lt;img alt="Visualization" src="https://docs.coiled.io/_images/633f7b569ab355682c9bcd40_visualization-700x368.gif" style="width: 700px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Source: &lt;a class="reference external" href="https://blog.kitware.com/accelerating-volumetric-x-ray-microstructural-analytics-with-dask-itk-from-supercomputers-to-the-cloud/"&gt;blog.kitware.com&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Do you have any exciting Dask or Coiled showcases to share? Tweet them to us &lt;a class="reference external" href="https://twitter.com/coiledhq?lang=en"&gt;&amp;#64;CoiledHQ&lt;/a&gt;, we would love to hear about it!&lt;/p&gt;
&lt;section id="dask-for-parallel-and-distributed-computing"&gt;
&lt;h2&gt;Dask for Parallel and Distributed Computing&lt;/h2&gt;
&lt;p&gt;Dask is a library for parallel and distributed computing in Python. It helps scale machine learning workflows and provides a powerful framework to build distributed applications. Its familiar API, flexibility, and seamless integration with the Scientific Python Ecosystem make it a popular choice among data scientists and researchers.&lt;/p&gt;
&lt;p&gt;Dask is used by academics and industry professionals across a variety of domains from life sciences and geophysical sciences to finance and retail corporations. It is also used behind-the-scenes by other software libraries like RAPIDS, Apache Airflow, Prefect, and many more. Learn more about Dask in &lt;a class="reference external" href="https://coiled.io/blog/what-is-dask/"&gt;What is Dask?&lt;/a&gt; and &lt;a class="reference external" href="https://coiled.io/who-uses-dask/"&gt;Who uses Dask?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this showcase, we see Dask being used on a personal laptop, a supercomputer, and on the cloud with Coiled for accelerating the analysis of microstructural data in materials sciences.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="microstructural-analytics-for-material-science"&gt;
&lt;h2&gt;Microstructural Analytics for Material Science&lt;/h2&gt;
&lt;figure class="align-default" id="id2"&gt;
&lt;img alt="Fiber segmentation pipeline" src="https://docs.coiled.io/_images/633f7b569ab355ac399bcd43_pipeline-1024x352.png" style="width: 1024px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Fibre Segmentation pipeline.&lt;br /&gt;
Source: &lt;a class="reference external" href="https://github.com/dani-lbnl/SC20_pyHPC/blob/master/SC20_pyHPC_dask4volumes.pdf"&gt;Article presented at Super Computing 2020&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;X-Ray Imaging is widely used to study new materials and to measure their properties. Advances in X-Ray technology at &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Synchrotron_light_source"&gt;synchrotron light sources&lt;/a&gt; greatly increase the size of datasets, which has led to efforts in automating analysis. So far, the efforts have been promising, but three-dimensional imagery is still in its infancy compared to human labeling. Human labeling (manual curation) is not feasible for large 3D datasets, especially when a single sample is often imaged many times, and each volume can contain several gigabytes of data. Moreover, previous approaches have scalability issues. The approach described in this paper provides a solution for all these problems using Dask.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dataset-softwares-and-infrastructure-for-research"&gt;
&lt;h2&gt;Dataset, Softwares, and Infrastructure for Research&lt;/h2&gt;
&lt;figure class="align-default" id="id3"&gt;
&lt;img alt="Architecture diagram" src="https://docs.coiled.io/_images/633f7b569ab3553e1e9bcd42_architecture-1024x631.png" style="width: 1024px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;3D microstructure interactive image analysis and visualization system architecture.&lt;br /&gt;
Source: &lt;a class="reference external" href="https://github.com/dani-lbnl/SC20_pyHPC/blob/master/SC20_pyHPC_dask4volumes.pdf"&gt;Article presented at Super Computing 2020&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The data acquired from previous related work are available publically in a data collection of 6 terabytes in total, where each file contains about 60 GB and more than 14 billion voxels. That’s a lot of data! This research uses one of the volumes in the data collection alongside synthetic datasets of about 200³ voxels generated to suit the need. The team uses Dask on a local machine (Dell XPS 13 7390), on a supercomputer (NERSC Cori), and on the cloud (AWS) with Coiled to analyze this data.&lt;/p&gt;
&lt;p&gt;The dataset is stored in &lt;a class="reference external" href="https://zarr.readthedocs.io/en/stable/"&gt;Zarr&lt;/a&gt; format using &lt;a class="reference external" href="http://xarray.pydata.org/en/stable/"&gt;xarray&lt;/a&gt;. Zarr is a format for storing chunked n-dimensional arrays, and XArray is a library for labeled arrays and datasets in Python. This chunked and compressed multi-scale pyramid storage allows parallel analysis and visualization with Dask.&lt;a class="reference external" href="https://itk.org"&gt;Insight Toolkit (ITK)&lt;/a&gt; is used for its rich assortment of 2D and 3D image analysis algorithms. &lt;a class="reference external" href="https://github.com/InsightSoftwareConsortium/itkwidgets"&gt;itkwidgets&lt;/a&gt; provides interactive Jupyter widgets to visualize images, point sets, and meshes in 3D or 2D. &lt;a class="reference external" href="https://jupyter.org"&gt;Jupyter&lt;/a&gt; notebooks are the interactive IDEs used to write Python code. Finally, Dask is used for parallel computing as described in the following section.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="dask-and-coiled-for-parallel-distributed-and-cloud-computing"&gt;
&lt;h2&gt;Dask and Coiled for Parallel, Distributed, and Cloud Computing&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://dask.org/"&gt;Dask&lt;/a&gt; is used to parallelize computation and accelerate the analysis. Dask schedulers are a core part of Dask. They take a task graph and execute it on parallel hardware, where the task graphs can also be specified as Python dictionaries. There are various schedulers with different performance optimizations ranging from single machine, single core to threaded, multiprocessing, and distributed.&lt;/p&gt;
&lt;p&gt;Dask schedulers proved very useful for this research:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“The Dask single-machine schedulers have logic to execute the graph in a way that minimizes memory footprint. As a result, our algorithms can be quickly extended on smaller datasets with a laptop or workstation. Then, the same code can scale to extremely large datasets by running Dask over a HPC cluster backed by MPI, a cluster with a scheduler like SLURM, or a dynamically-generated, cloud service running a Kubernetes cluster.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://coiled.io/cloud/"&gt;Coiled Cloud&lt;/a&gt; makes it easy to deploy Dask on the cloud. It takes care of all the DevOps involved in cloud computing and provides a simple interface that can be run on any machine to leverage hundreds of cores on an AWS cluster. Support for Azure is also in the works! In this showcase, Coiled Cloud was used to run the same computing environment on the cloud to compare runtime benchmarks.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="coiled-makes-computation-6x-faster"&gt;
&lt;h2&gt;Coiled Makes Computation 6x Faster!&lt;/h2&gt;
&lt;figure class="align-default" id="id4"&gt;
&lt;img alt="Performance comparison" src="https://docs.coiled.io/_images/633f7b569ab355609d9bcd41_comparison.png" style="width: 700px;" /&gt;
&lt;figcaption&gt;
&lt;p&gt;&lt;span class="caption-text"&gt;&lt;em&gt;Source: &lt;a class="reference external" href="https://github.com/dani-lbnl/SC20_pyHPC/blob/master/SC20_pyHPC_dask4volumes.pdf"&gt;Article presented at Super Computing 2020&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The computation took &lt;strong&gt;13 minutes on a standard laptop&lt;/strong&gt; and the same processing pipeline was executed on a &lt;strong&gt;Coiled cluster in 2 minutes&lt;/strong&gt;. The paper notes how the same computing environment could run the JupyterLab server and client with custom JupyterLab extensions, the Jupyter Kernel process, as well as the Dask scheduler, workers, and dashboard.&lt;/p&gt;
&lt;p&gt;The team also describes powerful similarities across computing environments:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“High-memory resources were not required – the limited memory capacities of the laptop and Coiled workers were sufficient for the large dataset. The user experience, i.e. interactive Jupyter workspaces accessed through the browser on the laptop, was exactly the same. And, most importantly, the same software was used without modification; to change the computational backend, the Dask Distributed backend was simply changed to a distributed cluster resource, which resulted in a 25X acceleration when processing the larger dataset.”&lt;/em&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="get-the-most-out-of-your-data-science"&gt;
&lt;h2&gt;Get the most out of your data science&lt;/h2&gt;
&lt;p&gt;We always love seeing Dask and Coiled being used to make data science accessible to the scientific community. This use case for scaling computer vision and 3D data analysis is a perfect demonstration of &lt;strong&gt;Dask’s flexibility and power&lt;/strong&gt;. You can check out their &lt;a class="reference external" href="https://github.com/dani-lbnl/SC20_pyHPC"&gt;code repository on GitHub&lt;/a&gt; and try Coiled Cloud for free below!&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/accelerating-microstructural-analytics-dask-coiled.html"/>
    <summary>In this article, we will discuss an interesting use case of Dask and Coiled: Accelerating Volumetric X-ray Microstructural Analytics using distributed and high-performance computing. This blog post is inspired by the article published in Kitware Blog and corresponding research.</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/abalone-bio-case-study.html</id>
    <title>Abalone Bio: Accelerating Antibody Discovery</title>
    <updated>2022-01-01T00:00:00+00:00</updated>
    <author>
      <name>Sameer Soi</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Jan 1, 2022&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="abalone-bio-accelerating-antibody-discovery"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;img alt="" class="sd-avatar-sm sd-outline-muted" src="https://docs.coiled.io/_images/67005d51ccfdae6b40560bb3_sameer.jpeg" /&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Sameer Soi &lt;br&gt;&lt;small&gt;VP of Data Science &amp;amp; AI&lt;/small&gt;&lt;/br&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="about-abalone-bio"&gt;
&lt;h2&gt;&lt;strong&gt;About Abalone Bio&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.abalonebio.com/"&gt;Abalone Bio&lt;/a&gt; is an innovative drug discovery company specializing in antibody drugs that activate challenging drug targets to treat disease affecting millions of patients. Their proprietary Functional Antibody Selection Technology (FAST) platform interrogates large antibody libraries with engineered yeast cells and generates vast amounts of data on the functional activity of hundreds of millions of antibodies, going beyond the binding and inhibition focus of traditional antibody discovery methods. These large-scale activity datasets uniquely power protein large language models to unlock new therapies for untreated patients.&lt;/p&gt;
&lt;p&gt;Abalone Bio’s FAST platform was generating gigabytes of data with millions of data points, far more than traditional drug discovery methods. However, they were only able to analyze a fraction of this data, focusing on the strongest signals and potentially missing valuable insights hidden in the rest of the dataset.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;“We were generating an unprecedented amount of data, but we were only scratching the surface of its potential. We needed a way to unlock the hidden value in our datasets.”&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Recognizing the potential of machine learning to unlock hidden value in their data, Abalone Bio turned to Coiled. They needed a flexible, scalable solution that could:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Handle large datasets (10-20 million sequences)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Support the entire machine learning lifecycle, from exploration to production&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Offer cloud provider flexibility to avoid vendor lock-in&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provide robust support for data science tools like Dask&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Accelerating and extending the capabilities of a small computational biology team&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section id="why-coiled"&gt;
&lt;h2&gt;&lt;strong&gt;Why Coiled?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Abalone Bio chose Coiled for several key reasons:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Flexibility: Coiled’s cloud-agnostic approach and ability to integrate with a variety of modern machine learning tools allowed Abalone Bio to iterate quickly and maintain flexibility in their infrastructure choices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scalability: Coiled easily scaled to handle Abalone Bio’s growing datasets, from 1 million to 20 million sequences, making it easy to accelerate model training across NVIDIA GPUs on the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Full Lifecycle Support: Coiled offered a number of developer-friendly tools for every stage of development, from prototyping  in a notebook to production-ready deployments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dask Integration: Coiled’s strong support for Dask and ability to integrate with the broader Python ecosystem aligned well with Abalone Bio’s data processing needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Responsive Support&lt;/strong&gt;: The Coiled team provided timely, personalized support, crucial for Abalone Bio’s small data science team.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;“Coiled support is like traveling first class.”&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="results-impact"&gt;
&lt;h2&gt;&lt;strong&gt;Results + Impact&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Coiled made it easy for Abalone Bio to train their protein LLMs at scale on GPU-accelerated hardware to identify drug target-activating antibodies. This integration of machine learning, powered by Coiled, has had a significant impact on Abalone Bio’s operations and value proposition:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Increased Hit Rate: The ML model found 30 promising antibody candidates where traditional methods only identified one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Expanded Platform Capabilities: Machine learning is now a core part of Abalone Bio’s FAST platform, enhancing their value proposition to partners and investors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Accelerated Discovery: The automated ML pipeline, running on Coiled’s infrastructure, has become a push-button solution for analyzing new antibody selections.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Novel Antibody Generation: Coiled enables Abalone Bio to use generative models to design entirely new antibodies, expanding their discovery potential beyond naturally occurring antibodies.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;“Coiled has transformed our approach to drug discovery. Machine learning is no longer an add-on to our platform—it’s become an integral part of our value proposition.”&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="looking-ahead"&gt;
&lt;h2&gt;&lt;strong&gt;Looking Ahead&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;With Coiled as a crucial part of their ML operations, Abalone Bio is well-positioned to continue pushing the boundaries of antibody discovery. They’re currently working on discovering and developing drugs for therapeutic areas ranging from metabolic disease, cancer, and inflammation. Using the Coiled platform, Abalone Bio has developed anti-inflammatory candidates that treat neuropathic pain and fibrosis in animal models and in human tissue samples, and uncovered activator antibodies for its pharma partners.&lt;/p&gt;
&lt;p&gt;The scalability and flexibility offered by Coiled will allow Abalone Bio to continue expanding their computational capabilities as they tackle ever more complex challenges in drug discovery.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;“As we look to the future, we’re excited about the possibilities that Coiled opens up for us. Their platform gives us the confidence to tackle increasingly complex computational challenges, knowing we have the scalability and support to back up our ambitions.”&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/abalone-bio-case-study.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Jan 1, 2022</summary>
    <published>2022-01-01T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/parallelize-pandas-apply-and-map-with-dask-dataframe.html</id>
    <title>Pandas parallel apply and map with Dask DataFrame</title>
    <updated>2021-11-22T00:00:00+00:00</updated>
    <author>
      <name>Pavithra Eswaramoorthy</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Nov 22, 2021&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="pandas-parallel-apply-and-map-with-dask-dataframe"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Pavithra Eswaramoorthy&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Dask DataFrame can speed up pandas &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;apply()&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map()&lt;/span&gt;&lt;/code&gt; operations by running in parallel across all cores on a single machine or a cluster of machines.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Dask DataFrame helps you quickly &lt;a class="reference internal" href="examples/dataframes.html"&gt;&lt;span class="doc"&gt;scale your single-core pandas code&lt;/span&gt;&lt;/a&gt;, while keeping the API familiar. The &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map()&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;apply()&lt;/span&gt;&lt;/code&gt; functions are at the core of data manipulation with pandas. In this post, we’ll take a closer look at how you can perform these operations efficiently with Dask DataFrame.&lt;/p&gt;
&lt;p&gt;In particular:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Dask’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map_partitions()&lt;/span&gt;&lt;/code&gt; function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dask’s implementation of pandas parallel &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;apply()&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map()&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="quick-overview-of-pandas-apply-and-map"&gt;
&lt;h2&gt;Quick overview of pandas apply() and map()&lt;/h2&gt;
&lt;p&gt;You can use pandas’ &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;apply()&lt;/span&gt;&lt;/code&gt; function to apply any inbuilt or custom Python function across a pandas one-dimensional array (for example, a Series or a single dimension of a DataFrame). This can be applied across columns (axis=0), or rows (axis=1).&lt;/p&gt;
&lt;p&gt;Consider a pandas DataFrame with 10 rows and 2 columns: a and b, where each element in the DataFrame is a random integer:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;    a   b&lt;/span&gt;
&lt;span class="go"&gt;0  15   3&lt;/span&gt;
&lt;span class="go"&gt;1   9  18&lt;/span&gt;
&lt;span class="go"&gt;2   5  12&lt;/span&gt;
&lt;span class="go"&gt;3  14   9&lt;/span&gt;
&lt;span class="go"&gt;4  12  15&lt;/span&gt;
&lt;span class="go"&gt;5   9   9&lt;/span&gt;
&lt;span class="go"&gt;6  15   5&lt;/span&gt;
&lt;span class="go"&gt;7   9   3&lt;/span&gt;
&lt;span class="go"&gt;8  10  10&lt;/span&gt;
&lt;span class="go"&gt;9  14  11&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Also, consider a function &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;minmax()&lt;/span&gt;&lt;/code&gt; that sleeps for 1 second and returns the difference between the largest and smallest value:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’ll use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;apply()&lt;/span&gt;&lt;/code&gt; to do the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;minmax()&lt;/span&gt;&lt;/code&gt; operation on each row in the DataFrame with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;df.apply(minmax,&lt;/span&gt; &lt;span class="pre"&gt;axis=1)&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;CPU times: user 10.3 ms, sys: 1.37 ms, total: 11.7 ms&lt;/span&gt;
&lt;span class="go"&gt;Wall time: 10 s&lt;/span&gt;
&lt;span class="go"&gt;0    12&lt;/span&gt;
&lt;span class="go"&gt;1     9&lt;/span&gt;
&lt;span class="go"&gt;2     7&lt;/span&gt;
&lt;span class="go"&gt;3     5&lt;/span&gt;
&lt;span class="go"&gt;4     3&lt;/span&gt;
&lt;span class="go"&gt;5     0&lt;/span&gt;
&lt;span class="go"&gt;6    10&lt;/span&gt;
&lt;span class="go"&gt;7     6&lt;/span&gt;
&lt;span class="go"&gt;8     0&lt;/span&gt;
&lt;span class="go"&gt;9     3&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This computation takes ~10s because we have 10 rows.&lt;/p&gt;
&lt;p&gt;You can use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map()&lt;/span&gt;&lt;/code&gt; for element-wise operations across a pandas DataFrame. Consider a function &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;inc&lt;/span&gt;&lt;/code&gt; that sleeps for 1s and returns the input incremented by 1:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’ll use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map()&lt;/span&gt;&lt;/code&gt; to do this &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;inc()&lt;/span&gt;&lt;/code&gt; operation all values in the pandas DataFrame:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;CPU times: user 11.2 ms, sys: 1.66 ms, total: 12.9 ms&lt;/span&gt;
&lt;span class="go"&gt;Wall time: 20.1 s&lt;/span&gt;
&lt;span class="go"&gt;    a   b&lt;/span&gt;
&lt;span class="go"&gt;0  16   4&lt;/span&gt;
&lt;span class="go"&gt;1  10  19&lt;/span&gt;
&lt;span class="go"&gt;2   6  13&lt;/span&gt;
&lt;span class="go"&gt;3  15  10&lt;/span&gt;
&lt;span class="go"&gt;4  13  16&lt;/span&gt;
&lt;span class="go"&gt;5  10  10&lt;/span&gt;
&lt;span class="go"&gt;6  16   6&lt;/span&gt;
&lt;span class="go"&gt;7  10   4&lt;/span&gt;
&lt;span class="go"&gt;8  11  11&lt;/span&gt;
&lt;span class="go"&gt;9  15  12&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This computation takes ~20s because we have 20 values.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="parallel-pandas-apply-with-dask"&gt;
&lt;h2&gt;Parallel pandas apply with Dask&lt;/h2&gt;
&lt;div class="sd-container-fluid sd-sphinx-override sd-mb-4 docutils"&gt;
&lt;div class="sd-row sd-row-cols-1 sd-row-cols-xs-1 sd-row-cols-sm-1 sd-row-cols-md-2 sd-row-cols-lg-2 docutils"&gt;
&lt;div class="sd-col sd-d-flex-column sd-col-12 sd-col-xs-12 sd-col-sm-12 sd-col-md-8 sd-col-lg-8 docutils"&gt;
&lt;p&gt;A Dask DataFrame consists of multiple pandas DataFrames, and each pandas DataFrame is called a partition. This mechanism allows you to work with larger-than-memory data because your computations are distributed across these pandas DataFrames and can be executed in parallel. This includes the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;apply()&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map()&lt;/span&gt;&lt;/code&gt; computations.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-d-flex-column sd-col-12 sd-col-xs-12 sd-col-sm-12 sd-col-md-4 sd-col-lg-4 docutils"&gt;
&lt;figure class="align-default"&gt;
&lt;img alt="https://docs.dask.org/en/stable/_images/dask-dataframe.svg" src="https://docs.dask.org/en/stable/_images/dask-dataframe.svg" style="width: 100%;" /&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Dask DataFrame with 5 partitions&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We can manually access and work on these individual partitions using Dask’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DataFrame.apply&lt;/span&gt;&lt;/code&gt; function, which is like running a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pandas.DataFrame.apply&lt;/span&gt;&lt;/code&gt; in parallel.&lt;/p&gt;
&lt;p&gt;Using the same &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;minmax()&lt;/span&gt;&lt;/code&gt; function we defined earlier, we can apply this to our Dask DataFrame in parallel.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;int64&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;CPU times: user 9.23 ms, sys: 1.66 ms, total: 10.9 ms&lt;/span&gt;
&lt;span class="go"&gt;Wall time: 2.01 s&lt;/span&gt;
&lt;span class="go"&gt;0    12&lt;/span&gt;
&lt;span class="go"&gt;1     9&lt;/span&gt;
&lt;span class="go"&gt;2     7&lt;/span&gt;
&lt;span class="go"&gt;3     5&lt;/span&gt;
&lt;span class="go"&gt;4     3&lt;/span&gt;
&lt;span class="go"&gt;5     0&lt;/span&gt;
&lt;span class="go"&gt;6    10&lt;/span&gt;
&lt;span class="go"&gt;7     6&lt;/span&gt;
&lt;span class="go"&gt;8     0&lt;/span&gt;
&lt;span class="go"&gt;9     3&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;A number of Dask DataFrame methods have an important keyword argument called &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;meta&lt;/span&gt;&lt;/code&gt;, which describes the structure of the expected output of your computation. In this example we passed &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;meta=(None,&lt;/span&gt; &lt;span class="pre"&gt;'int64')&lt;/span&gt;&lt;/code&gt; since the result of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;minmax&lt;/span&gt;&lt;/code&gt; is a Series of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;int64&lt;/span&gt;&lt;/code&gt; values. See &lt;a class="reference external" href="https://blog.dask.org/2022/08/09/understanding-meta-keyword-argument"&gt;Understanding Dask’s meta keyword argument&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Dask is nearly 5x faster than pandas. This is because Dask applies the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;minmax&lt;/span&gt;&lt;/code&gt; function in parallel across each partition and there are five partitions in the Dask DataFrame. There’s a small amount of overhead from Dask, which is why it’s not exactly 5x faster.&lt;/p&gt;
&lt;p&gt;Similar to pandas &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DataFrame.map&lt;/span&gt;&lt;/code&gt; we can also use Dask’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DataFrame.map&lt;/span&gt;&lt;/code&gt; to apply an element-wise function across a DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;CPU times: user 9.56 ms, sys: 2.21 ms, total: 11.8 ms&lt;/span&gt;
&lt;span class="go"&gt;Wall time: 4.03 s&lt;/span&gt;
&lt;span class="go"&gt;    a   b&lt;/span&gt;
&lt;span class="go"&gt;0  16   4&lt;/span&gt;
&lt;span class="go"&gt;1  10  19&lt;/span&gt;
&lt;span class="go"&gt;2   6  13&lt;/span&gt;
&lt;span class="go"&gt;3  15  10&lt;/span&gt;
&lt;span class="go"&gt;4  13  16&lt;/span&gt;
&lt;span class="go"&gt;5  10  10&lt;/span&gt;
&lt;span class="go"&gt;6  16   6&lt;/span&gt;
&lt;span class="go"&gt;7  10   4&lt;/span&gt;
&lt;span class="go"&gt;8  11  11&lt;/span&gt;
&lt;span class="go"&gt;9  15  12&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dask’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DataFrame.map&lt;/span&gt;&lt;/code&gt; is ~5x faster than using pandas since it is able to apply the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;inc&lt;/span&gt;&lt;/code&gt; function in parallel across the DataFrame partitions.&lt;/p&gt;
&lt;section id="dask-map-partitions"&gt;
&lt;h3&gt;Dask &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map_partitions&lt;/span&gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;In addition to Dask’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;apply&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map&lt;/span&gt;&lt;/code&gt; functions, there is also Dask’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DataFrame.map_partitions&lt;/span&gt;&lt;/code&gt; which you can use to apply any Python function on each DataFrame partition in parallel. This is very similar to Dask’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;DataFrame.apply&lt;/span&gt;&lt;/code&gt;; the main advantage is it can offer additional flexibility in some cases.&lt;/p&gt;
&lt;p&gt;For example, we can define a function that does a row-wise sum operation and returns a Series:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;myadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We can use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;map_partitions&lt;/span&gt;&lt;/code&gt; to apply this function, using the Dask DataFrame defined above:&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map_partitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myadd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;int64&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;0   23&lt;/span&gt;
&lt;span class="go"&gt;1   32&lt;/span&gt;
&lt;span class="go"&gt;2   22&lt;/span&gt;
&lt;span class="go"&gt;3   28&lt;/span&gt;
&lt;span class="go"&gt;4   32&lt;/span&gt;
&lt;span class="go"&gt;5   23&lt;/span&gt;
&lt;span class="go"&gt;6   25&lt;/span&gt;
&lt;span class="go"&gt;7   17&lt;/span&gt;
&lt;span class="go"&gt;8   25&lt;/span&gt;
&lt;span class="go"&gt;9   30&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="learn-more"&gt;
&lt;h2&gt;Learn more&lt;/h2&gt;
&lt;p&gt;In this post we went through how to perform a parallel pandas apply operation with Dask DataFrame. For more realistic use cases you might consider:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/dataframes.html"&gt;&lt;span class="doc"&gt;DataFrame Workloads with Coiled&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/datashader.html"&gt;&lt;span class="doc"&gt;Visualize 1,000,000,000 Points&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/scaling-xgboost-cloud-coiled.html"&gt;&lt;span class="doc"&gt;XGBoost model training with Dask DataFrame&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/parallelize-pandas-apply-and-map-with-dask-dataframe.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Nov 22, 2021</summary>
    <published>2021-11-22T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://docs.coiled.io/blog/converting-a-dask-dataframe-to-a-pandas-dataframe.html</id>
    <title>Converting a Dask DataFrame to a pandas DataFrame</title>
    <updated>2021-10-01T00:00:00+00:00</updated>
    <author>
      <name>Matthew Powers</name>
    </author>
    <content type="html">&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-date docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;Oct 1, 2021&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;&lt;span class="sd-pr-2"&gt;&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-clock" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;3m read&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section id="converting-a-dask-dataframe-to-a-pandas-dataframe"&gt;

&lt;div class="sd-container-fluid sd-sphinx-override sd-p-0 sd-mt-2 sd-mb-4 article-info-author docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-gx-2 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;div class="sd-container-fluid sd-sphinx-override docutils"&gt;
&lt;div class="sd-row sd-row-cols-2 sd-row-cols-xs-2 sd-row-cols-sm-3 sd-row-cols-md-3 sd-row-cols-lg-3 sd-gx-3 sd-gy-1 docutils"&gt;
&lt;div class="sd-col sd-col-auto sd-d-flex-row sd-align-minor-center docutils"&gt;
&lt;p class="sd-p-0 sd-m-0"&gt;Matthew Powers&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This post explains how to convert from a Dask DataFrame to a pandas DataFrame and when it’s a good idea to perform this operation.&lt;/p&gt;
&lt;p&gt;Each partition in a Dask DataFrame is a pandas DataFrame. Converting from a Dask DataFrame to a pandas DataFrame combines multiple pandas DataFrames (partitions) into a single pandas DataFrame.&lt;/p&gt;
&lt;p&gt;Dask DataFrames can store massive datasets, whereas pandas DataFrames must be smaller than the memory of a single computer. This means only small Dask DataFrames can be converted into pandas DataFrames.&lt;/p&gt;
&lt;p&gt;This post shows the syntax to perform the conversion, the error message if the Dask DataFrame is too big, and how to assess if conversion is possible.&lt;/p&gt;
&lt;section id="convert-from-dask-to-pandas-on-localhost"&gt;
&lt;h2&gt;Convert from Dask to pandas on localhost&lt;/h2&gt;
&lt;p&gt;Start by creating a Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;letters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;npartitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If we print the Dask DataFrame, we can see a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; instead of the actual values. This is because Dask DataFrame uses lazy evaluation and won’t compute the DataFrame until explicitly requested.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;Dask DataFrame Structure:&lt;/span&gt;
&lt;span class="go"&gt;                nums letters&lt;/span&gt;
&lt;span class="go"&gt;npartitions=2&lt;/span&gt;
&lt;span class="go"&gt;0              int64  string&lt;/span&gt;
&lt;span class="go"&gt;3                ...     ...&lt;/span&gt;
&lt;span class="go"&gt;5                ...     ...&lt;/span&gt;
&lt;span class="go"&gt;Dask Name: frompandas, 1 expression&lt;/span&gt;
&lt;span class="go"&gt;Expr=df&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To convert the Dask DataFrame into a pandas DataFrame, you can call &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;compute&lt;/span&gt;&lt;/code&gt;. This turns the lazy Dask DataFrame into an in-memory pandas DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;pandas_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pandas_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;pandas.core.frame.DataFrame&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can use &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;type(pandas_df)&lt;/span&gt;&lt;/code&gt; which returns &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;pandas.core.frame.DataFrame&lt;/span&gt;&lt;/code&gt; and confirms it’s a pandas DataFrame.&lt;/p&gt;
&lt;p&gt;You can also print the pandas_df to visually inspect the DataFrame contents.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pandas_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;   nums letters&lt;/span&gt;
&lt;span class="go"&gt;0     1       a&lt;/span&gt;
&lt;span class="go"&gt;1     2       b&lt;/span&gt;
&lt;span class="go"&gt;2     3       c&lt;/span&gt;
&lt;span class="go"&gt;3     4       d&lt;/span&gt;
&lt;span class="go"&gt;4     5       e&lt;/span&gt;
&lt;span class="go"&gt;5     6       f&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We’re easily able to convert a small Dask DataFrame to a pandas DataFrame on localhost (your local computer). Let’s try to convert a big Dask DataFrame to a pandas DataFrame on a cloud-based cluster.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="converting-big-datasets-on-the-cloud"&gt;
&lt;h2&gt;Converting big datasets on the cloud&lt;/h2&gt;
&lt;p&gt;This Parquet dataset contains 662 million records and is 16.7 GiB when saved on disk. It’s stored on S3 in a public bucket &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;s3://coiled-datasets/timeseries/20-years/parquet&lt;/span&gt;&lt;/code&gt;. This dataset is too big to fit in memory on my laptop and even if I had a local machine with enough memory, it would be prohibitively slow to download from cloud storage.&lt;/p&gt;
&lt;p&gt;Dask and Coiled make it easy to work with large DataFrames in the cloud (&lt;a class="reference internal" href="examples/dataframes.html"&gt;&lt;span class="doc"&gt;learn more&lt;/span&gt;&lt;/a&gt;). Let’s create a 5 node Dask cluster on AWS with Coiled and read it into a Dask DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;coiled&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dask.dataframe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dd&lt;/span&gt;

&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coiled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-2&amp;quot;&lt;/span&gt; &lt;span class="c1"&gt;# same region as the data, faster + cheaper&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3://coiled-datasets/timeseries/20-years/parquet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_ssl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The dataset contains 662 million records and uses 58 GB of memory. We won’t be able to convert this whole Dask DataFrame to a pandas DataFrame because it’s too big.&lt;/p&gt;
&lt;p&gt;Let’s run a filtering operation and see if a subset of the Dask DataFrame can be converted to a pandas DataFrame.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;filtered_ddf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1150&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;filtered_ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;                       id    name         x         y&lt;/span&gt;
&lt;span class="go"&gt;timestamp&lt;/span&gt;
&lt;span class="go"&gt;2000-01-09 01:52:30  1152   Edith  0.273674  0.997075&lt;/span&gt;
&lt;span class="go"&gt;2000-01-29 17:22:59  1175  Oliver -0.909065  0.017086&lt;/span&gt;
&lt;span class="go"&gt;2000-01-29 20:34:37  1158     Bob -0.910895  0.652333&lt;/span&gt;
&lt;span class="go"&gt;2000-02-06 00:13:44  1152   Sarah  0.080475  0.855420&lt;/span&gt;
&lt;span class="go"&gt;2000-02-08 05:23:59  1153   Kevin  0.258087 -0.144844&lt;/span&gt;
&lt;span class="go"&gt;...                   ...     ...       ...       ...&lt;/span&gt;
&lt;span class="go"&gt;2020-10-25 18:15:35  1175  George  0.060843  0.229963&lt;/span&gt;
&lt;span class="go"&gt;2020-11-01 14:25:07  1153   Alice -0.537439 -0.544084&lt;/span&gt;
&lt;span class="go"&gt;2020-11-20 02:30:17  1158  Oliver  0.733396  0.227974&lt;/span&gt;
&lt;span class="go"&gt;2020-11-30 03:01:06  1155   Kevin -0.963094 -0.638443&lt;/span&gt;
&lt;span class="go"&gt;2020-11-30 07:11:46  1163  Yvonne -0.671973 -0.700749&lt;/span&gt;

&lt;span class="go"&gt;[1103 rows x 4 columns]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It takes 22 seconds to query this dataset. The &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;filtered_ddf&lt;/span&gt;&lt;/code&gt; contains 1,103 rows and only uses 50 KB of memory.  It’s a tiny subset of the original dataset that contains 662 million rows and can easily be converted to a pandas DataFrame with &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;filtered_ddf.compute()&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Just for fun, let’s try to convert the entire Dask DataFrame to pandas and see what happens.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="error-when-converting-a-big-dataset"&gt;
&lt;h2&gt;Error when converting a big dataset&lt;/h2&gt;
&lt;p&gt;The entire Dask DataFrame takes 58 GB of memory, and we already know that’s too much data to be collected into a single pandas DataFrame. Each node in our cluster only has 16 GB of memory. You’d need a Dask DataFrame that’s smaller than the memory of a single node to collect the data in a pandas DataFrame, so &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;ddf.compute()&lt;/span&gt;&lt;/code&gt; will error out.&lt;/p&gt;
&lt;div class="highlight-python notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ddf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;MemoryError: Task (&amp;#39;repartitiontofewer-2a0f640e5769ea9c1c31514c1d6e3358&amp;#39;, 0) has 27.97 GiB worth of input dependencies, but worker tls://10.0.46.55:45469 has memory_limit set to 14.85 GiB.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Even if the Dask DataFrame is sufficiently small to be converted to a pandas DataFrame, you don’t necessarily want to perform the operation.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="when-to-convert-to-pandas"&gt;
&lt;h2&gt;When to convert to pandas&lt;/h2&gt;
&lt;p&gt;Dask uses multiple cores to operate on datasets in parallel. Dask is often quicker than pandas, even for localhost workflows, because it uses all the cores of your machine.&lt;/p&gt;
&lt;p&gt;For one-off tasks, it’s fine reverting to pandas, but it should generally be avoided when building production data pipelines, since pandas cannot scale when data sizes grow. Data pipelines that can scale up are more robust. Rebuilding large components of systems when datasets grow isn’t ideal.&lt;/p&gt;
&lt;p&gt;Only convert to pandas for one-off tasks or when you’re sure datasets will stay small and you won’t have to deal with pandas scaling issues in the future.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="learn-more"&gt;
&lt;h2&gt;Learn more&lt;/h2&gt;
&lt;p&gt;In this post we went through how to convert a Dask DataFrame to a pandas DataFrame. This works well when the dataset is small to begin with or it’s been reduced to a manageable size. For more realistic use cases you might consider:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/dataframes.html"&gt;&lt;span class="doc"&gt;DataFrame Workloads with Coiled&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/datashader.html"&gt;&lt;span class="doc"&gt;Visualize 1,000,000,000 Points&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="examples/scaling-xgboost-cloud-coiled.html"&gt;&lt;span class="doc"&gt;XGBoost model training with Dask DataFrame&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://docs.coiled.io/blog/converting-a-dask-dataframe-to-a-pandas-dataframe.html"/>
    <summary>&lt;svg version="1.1" width="16.0px" height="16.0px" class="sd-octicon sd-octicon-calendar" viewBox="0 0 16 16" aria-hidden="true"&gt;&lt;path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"&gt;&lt;/path&gt;&lt;/svg&gt;Oct 1, 2021</summary>
    <published>2021-10-01T00:00:00+00:00</published>
  </entry>
</feed>
