<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Cost Optimisation on Ghost in the data</title><link>https://ghostinthedata.info/tags/cost-optimisation/</link><description>Ghost in the data</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Ghost in the data</copyright><lastBuildDate>Sat, 25 Apr 2026 09:00:00 +1100</lastBuildDate><atom:link href="https://ghostinthedata.info/tags/cost-optimisation/index.xml" rel="self" type="application/rss+xml"/><item><title>Your Data Platform Costs More Than It Should</title><link>https://ghostinthedata.info/posts/2026/2026-04-25-cost-management/</link><pubDate>Sat, 25 Apr 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-04-25-cost-management/</guid><author>Chris Hillman</author><description>A practical guide to understanding, measuring, and reducing your Snowflake and AWS data platform costs — starting with the habits that actually move the needle.</description><content:encoded>&lt;p>Let me tell you about the moment I stopped treating cloud costs as someone else&amp;rsquo;s problem.&lt;/p>
&lt;p>We were three months into a Snowflake migration. Everything was humming. Pipelines were green, dashboards were fast, the analytics team was happier than I&amp;rsquo;d seen them before. I felt good about the work we&amp;rsquo;d done.&lt;/p>
&lt;p>Then finance forwarded me the invoice.&lt;/p>
&lt;p>The number wasn&amp;rsquo;t catastrophic. But it was significantly higher than what we&amp;rsquo;d budgeted, and when I started digging, I couldn&amp;rsquo;t explain where most of it was going. I knew we had warehouses running. I knew we had pipelines executing. But I couldn&amp;rsquo;t tell you which warehouse was responsible for what cost, which pipelines were the expensive ones, or whether the money was well spent. I had built a platform I was proud of — and I had no idea what it actually cost to operate.&lt;/p>
&lt;p>That&amp;rsquo;s the moment that changed how I think about data engineering. Not because of the dollar amount, but because of the realisation underneath it: &lt;strong>I had built something I couldn&amp;rsquo;t explain to the people paying for it.&lt;/strong> And if I couldn&amp;rsquo;t explain it, I couldn&amp;rsquo;t defend it. And if I couldn&amp;rsquo;t defend it, someone else would make the decisions for me — someone who didn&amp;rsquo;t understand why the platform mattered.&lt;/p>
&lt;br>
&lt;p>I&amp;rsquo;m telling you this because cost management is one of those things that sounds like a finance problem until you experience the consequences firsthand. It&amp;rsquo;s not about being cheap. It&amp;rsquo;s about being intentional. It&amp;rsquo;s about knowing that every credit you spend is buying something valuable — and being able to prove it when someone asks.&lt;/p>
&lt;p>The data engineers who understand their costs don&amp;rsquo;t just save money. They earn trust. They get budget for the projects that matter. They sleep better because they&amp;rsquo;ve eliminated the waste that eventually becomes someone else&amp;rsquo;s excuse to cut headcount or freeze hiring.&lt;/p>
&lt;p>This article is about building that understanding. Not with a vendor&amp;rsquo;s optimisation tool or a consultant&amp;rsquo;s audit — but with the habits, queries, and mental models that let you own your platform&amp;rsquo;s economics from the inside. Everything here is grounded in Snowflake and AWS, with specific code you can run today.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="you-cant-optimise-what-you-cant-see">You can&amp;rsquo;t optimise what you can&amp;rsquo;t see&lt;/h3>
&lt;br>
&lt;p>Before you touch a single warehouse configuration, you need to answer one question: &lt;strong>where is the money going?&lt;/strong>&lt;/p>
&lt;p>Most teams skip this step. They read a blog post about auto-suspend settings, change a few defaults, and call it optimisation. That&amp;rsquo;s like going on a diet by switching to diet soda while eating three pizzas a day. The soda wasn&amp;rsquo;t the problem.&lt;/p>
&lt;p>Here&amp;rsquo;s the query I run first on every Snowflake environment I touch. It tells you which warehouses are consuming the most credits over the last 30 days:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span>.&lt;span style="color:#ae81ff">00&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> estimated_cost_usd, &lt;span style="color:#75715e">-- adjust your credit price
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">DISTINCT&lt;/span> DATE_TRUNC(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, start_time)) &lt;span style="color:#66d9ef">AS&lt;/span> active_days,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(&lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#66d9ef">DISTINCT&lt;/span> DATE_TRUNC(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, start_time)), &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> credits_per_day
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span> snowflake.account_usage.warehouse_metering_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span> total_credits &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Run that. Right now. I&amp;rsquo;ll wait.&lt;/p>
&lt;p>If you&amp;rsquo;re like most teams, 70–80% of your credits come from two or three warehouses. That&amp;rsquo;s your starting point. Not everything — just the expensive stuff.&lt;/p>
&lt;br>
&lt;p>Now do the same thing for queries. This one finds your top 20 most expensive queries by bytes scanned:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> query_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> query_text,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> user_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_elapsed_time &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> elapsed_seconds,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bytes_scanned &lt;span style="color:#f92672">/&lt;/span> POWER(&lt;span style="color:#ae81ff">1024&lt;/span>, &lt;span style="color:#ae81ff">3&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> gb_scanned,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> partitions_scanned,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> partitions_total,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(partitions_scanned &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">NULLIF&lt;/span>(partitions_total, &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> pct_partitions_scanned
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.query_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">7&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> bytes_scanned &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bytes_scanned &lt;span style="color:#66d9ef">DESC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LIMIT&lt;/span> &lt;span style="color:#ae81ff">20&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Pay attention to that &lt;code>pct_partitions_scanned&lt;/code> column. If you see queries scanning 90–100% of a table&amp;rsquo;s partitions, those queries aren&amp;rsquo;t benefiting from clustering or partition pruning. That&amp;rsquo;s where the big wins hide.&lt;/p>
&lt;p>This 15-minute exercise — top warehouses, top queries — tells you more about your cost profile than any dashboard. It&amp;rsquo;s the equivalent of checking your bank statement before creating a budget. Obvious in hindsight. Almost nobody does it.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-idle-tax-is-your-single-biggest-waste">The idle tax is your single biggest waste&lt;/h3>
&lt;br>
&lt;p>Here&amp;rsquo;s what nobody tells you about Snowflake billing: you pay for compute in 60-second minimums. Every time a warehouse resumes from suspension, you&amp;rsquo;re billed for at least one minute — regardless of whether the query takes 2 seconds or 58 seconds.&lt;/p>
&lt;p>This matters more than it sounds. Picture a BI tool like Metabase or Tableau hitting your warehouse with 20 small metadata queries over 15 minutes. If your warehouse auto-suspends after 5 minutes (Snowflake&amp;rsquo;s default for many setups), it might suspend and resume multiple times during that window. Each resume triggers another 60-second charge.&lt;/p>
&lt;p>Twenty queries that take 3 seconds each? That&amp;rsquo;s 60 seconds of actual compute. But if the warehouse suspends and resumes 4 times, you&amp;rsquo;re billed for 240 seconds. A 4x overhead.&lt;/p>
&lt;p>The fix is straightforward but requires thought:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- For transformation warehouses (predictable, bursty workloads)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> WAREHOUSE transform_wh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> AUTO_SUSPEND &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">60&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUTO_RESUME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WAREHOUSE_SIZE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;MEDIUM&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- For BI/dashboard warehouses (frequent small queries)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> WAREHOUSE bi_wh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> AUTO_SUSPEND &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">300&lt;/span> &lt;span style="color:#75715e">-- 5 min keeps it warm between dashboard interactions
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> AUTO_RESUME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WAREHOUSE_SIZE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;SMALL&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- For ad-hoc/analyst warehouses (unpredictable, intermittent)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> WAREHOUSE adhoc_wh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> AUTO_SUSPEND &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">60&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUTO_RESUME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WAREHOUSE_SIZE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;XSMALL&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The principle: &lt;strong>transformation warehouses should suspend aggressively&lt;/strong> because they run in defined windows with gaps between runs. BI warehouses should stay warm a bit longer because dashboard users generate clusters of queries with short pauses in between. Ad-hoc warehouses should be small and aggressive — analysts can tolerate a 1–2 second resume delay.&lt;/p>
&lt;p>Here&amp;rsquo;s how to find warehouses that are running but idle — the silent cost killer:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used_compute) &lt;span style="color:#66d9ef">AS&lt;/span> compute_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used_cloud_services) &lt;span style="color:#66d9ef">AS&lt;/span> cloud_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used_compute))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">NULLIF&lt;/span>(&lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used), &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ) &lt;span style="color:#66d9ef">AS&lt;/span> pct_idle_cost
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.warehouse_metering_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">HAVING&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pct_idle_cost &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_credits &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If &lt;code>pct_idle_cost&lt;/code> is above 20% on any warehouse, you&amp;rsquo;re burning money on idle time. Tighten the auto-suspend, or investigate what&amp;rsquo;s keeping the warehouse awake between queries.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-most-expensive-query-is-the-one-that-scans-everything">The most expensive query is the one that scans everything&lt;/h3>
&lt;br>
&lt;p>Snowflake charges you for the compute time your queries consume, and nothing drives compute time like full table scans. The two most common culprits are queries that wrap filter columns in functions, and queries that select more columns than they need.&lt;/p>
&lt;p>Here&amp;rsquo;s what I mean. This query looks innocent:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Expensive: function on the filter column disables pruning
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id, customer_id, total_amount
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analytics.fct_orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATE(order_timestamp) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;2026-03-01&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But it&amp;rsquo;s quietly terrible. Wrapping &lt;code>order_timestamp&lt;/code> in &lt;code>DATE()&lt;/code> forces Snowflake to evaluate every row before filtering. The query planner can&amp;rsquo;t use micro-partition metadata to skip irrelevant partitions.&lt;/p>
&lt;p>This version does the same thing, but lets Snowflake prune:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Cheap: range filter on raw column enables pruning
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id, customer_id, total_amount
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> analytics.fct_orders
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_timestamp &lt;span style="color:#f92672">&amp;gt;=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;2026-03-01&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> order_timestamp &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;2026-03-02&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The difference can be enormous on large tables — I&amp;rsquo;ve seen this single change reduce bytes scanned by 95% on billion-row fact tables.&lt;/p>
&lt;p>The other silent killer is &lt;code>SELECT *&lt;/code> in intermediate transformations. In a dbt project, I often find staging models that look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- stg_orders.sql (before)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;raw&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;orders&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loaded_at &lt;span style="color:#f92672">&amp;gt;&lt;/span> (&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(_loaded_at) &lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> this &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That &lt;code>SELECT *&lt;/code> pulls every column from the source — including columns nobody downstream ever uses. In a columnar store like Snowflake, you only pay to scan the columns you reference. Trimming to the columns you actually need is free performance:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- stg_orders.sql (after)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_timestamp,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> status,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loaded_at
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">source&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;raw&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;orders&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _loaded_at &lt;span style="color:#f92672">&amp;gt;&lt;/span> (&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#66d9ef">MAX&lt;/span>(_loaded_at) &lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> this &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This isn&amp;rsquo;t premature optimisation. It&amp;rsquo;s hygiene. Every &lt;code>SELECT *&lt;/code> in your transformation layer is a small tax you pay on every single run.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="full-refreshes-are-the-most-expensive-default-in-data-engineering">Full refreshes are the most expensive default in data engineering&lt;/h3>
&lt;br>
&lt;p>If you&amp;rsquo;re using dbt — and most teams on Snowflake are — the default materialisation is either &lt;code>view&lt;/code> or &lt;code>table&lt;/code>. Both have the same problem at scale: they rebuild everything, every time.&lt;/p>
&lt;p>A table materialisation on a 500-million-row fact table means Snowflake reads, transforms, and writes 500 million rows every run. Even if only 50,000 rows changed since yesterday. That&amp;rsquo;s a 10,000x overhead.&lt;/p>
&lt;p>Switching to incremental materialisation is the single highest-impact cost change most teams can make:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- fct_orders.sql
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> materialized&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;incremental&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> unique_key&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;order_id&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> incremental_strategy&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;merge&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> on_schema_change&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;append_new_columns&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customer_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> order_timestamp,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_amount,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> status,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> updated_at
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> &lt;span style="color:#66d9ef">ref&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;stg_orders&amp;#39;&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> is_incremental() &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span> updated_at &lt;span style="color:#f92672">&amp;gt;&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;hour&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>, &lt;span style="color:#66d9ef">MAX&lt;/span>(updated_at))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{{&lt;/span> this &lt;span style="color:#960050;background-color:#1e0010">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> endif &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A few things worth noting here. The &lt;code>DATEADD('hour', -3, ...)&lt;/code> creates a 3-hour lookback window. This catches late-arriving data and handles clock skew between source systems. Without it, you&amp;rsquo;ll miss rows that arrive slightly out of order — and your numbers will silently drift.&lt;/p>
&lt;p>The &lt;code>unique_key&lt;/code> with &lt;code>incremental_strategy='merge'&lt;/code> means Snowflake will update existing rows and insert new ones. This is essential for tables where source records get modified after initial load (order status changes, for example).&lt;/p>
&lt;p>The rule of thumb: &lt;strong>if a table has more than a million rows and less than 20% changes per run, make it incremental.&lt;/strong> The cost savings are usually 80–95% on that model&amp;rsquo;s compute.&lt;/p>
&lt;p>But — and this is important — schedule a periodic full refresh to correct any drift. I typically set up a weekly &lt;code>dbt build --full-refresh --select fct_orders&lt;/code> via a separate Airflow DAG or GitHub Actions workflow. Belt and suspenders.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-hidden-costs-your-snowflake-dashboard-wont-show-you">The hidden costs your Snowflake dashboard won&amp;rsquo;t show you&lt;/h3>
&lt;br>
&lt;p>Most teams monitor warehouse credits because that&amp;rsquo;s what the Snowflake UI makes visible. But there are cost vectors that don&amp;rsquo;t show up in the obvious places.&lt;/p>
&lt;p>&lt;strong>Cloud services credits&lt;/strong> accrue when queries use Snowflake&amp;rsquo;s coordination layer — query compilation, metadata operations, result set caching. Normally this is covered by a 10% &amp;ldquo;free&amp;rdquo; adjustment against your compute credits. But if you have BI tools making thousands of small metadata queries, cloud services can exceed that adjustment and start costing real money.&lt;/p>
&lt;p>Find them:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATE_TRUNC(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, usage_date) &lt;span style="color:#66d9ef">AS&lt;/span> &lt;span style="color:#66d9ef">day&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_adjustment_cloud_services) &lt;span style="color:#66d9ef">AS&lt;/span> cloud_services_adjustment,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CASE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHEN&lt;/span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_adjustment_cloud_services) &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">THEN&lt;/span> &lt;span style="color:#66d9ef">ABS&lt;/span>(&lt;span style="color:#66d9ef">SUM&lt;/span>(credits_adjustment_cloud_services))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ELSE&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">END&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> excess_cloud_services_cost
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.metering_daily_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> usage_date &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">day&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">day&lt;/span> &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Serverless feature credits&lt;/strong> — Snowpipe, automatic clustering, materialised view maintenance, search optimisation — all consume credits outside your warehouse billing. They don&amp;rsquo;t show up in &lt;code>warehouse_metering_history&lt;/code>. You need to check separately:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Snowpipe costs
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pipe_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.pipe_usage_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pipe_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_credits &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Automatic clustering costs
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">table_name&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> total_credits
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.automatic_clustering_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">table_name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> total_credits &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>On the AWS side, the sneaky costs tend to be data transfer. Moving data between S3 regions, or from S3 out to the internet, adds up quietly. If your Snowflake account is in &lt;code>us-east-1&lt;/code> but your S3 landing zone is in &lt;code>ap-southeast-2&lt;/code>, every byte of ingestion carries a cross-region transfer charge. Check your AWS Cost Explorer with the &amp;ldquo;Data Transfer&amp;rdquo; service filter — the numbers are often surprising.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="tag-everything-or-youll-optimise-blind">Tag everything or you&amp;rsquo;ll optimise blind&lt;/h3>
&lt;br>
&lt;p>You cannot reduce costs you can&amp;rsquo;t attribute. The simplest and most underused tool in Snowflake is the query tag — a piece of metadata you attach to every query that tells you &lt;em>what&lt;/em> generated it.&lt;/p>
&lt;p>If you&amp;rsquo;re using dbt, this takes about five minutes to set up. Create a macro:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- macros/set_query_tag.sql
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> macro set_query_tag() &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> query_tag &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;dbt_model&amp;#34;&lt;/span>: model.name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;dbt_schema&amp;#34;&lt;/span>: model.&lt;span style="color:#66d9ef">schema&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;dbt_materialized&amp;#34;&lt;/span>: model.config.materialized,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;dbt_invocation_id&amp;#34;&lt;/span>: invocation_id,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;environment&amp;#34;&lt;/span>: target.name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">}&lt;/span> &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#66d9ef">do&lt;/span> run_query(&lt;span style="color:#e6db74">&amp;#34;ALTER SESSION SET QUERY_TAG = &amp;#39;{}&amp;#39;&amp;#34;&lt;/span>.format(query_tag &lt;span style="color:#f92672">|&lt;/span> tojson)) &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">{&lt;/span>&lt;span style="color:#f92672">%&lt;/span> endmacro &lt;span style="color:#f92672">%&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then add it as a pre-hook in your &lt;code>dbt_project.yml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># dbt_project.yml&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">models&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">your_project&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">+pre-hook&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;{{ set_query_tag() }}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now every query dbt runs is tagged with the model name, materialisation type, and environment. You can query cost by model:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARSE_JSON(query_tag):dbt_model::STRING &lt;span style="color:#66d9ef">AS&lt;/span> model_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PARSE_JSON(query_tag):dbt_materialized::STRING &lt;span style="color:#66d9ef">AS&lt;/span> materialization,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">COUNT&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> query_count,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(total_elapsed_time) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> total_seconds,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(bytes_scanned) &lt;span style="color:#f92672">/&lt;/span> POWER(&lt;span style="color:#ae81ff">1024&lt;/span>, &lt;span style="color:#ae81ff">4&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> tb_scanned
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.query_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> query_tag &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> TRY_PARSE_JSON(query_tag) &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">7&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model_name, materialization
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tb_scanned &lt;span style="color:#66d9ef">DESC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">LIMIT&lt;/span> &lt;span style="color:#ae81ff">20&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is how you find the one dbt model that&amp;rsquo;s responsible for 40% of your bill. Without tags, it&amp;rsquo;s a guessing game.&lt;/p>
&lt;p>For non-dbt workloads — Airflow tasks, Lambda functions, BI tools — set query tags at the session level in your connection configuration. In Python:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> snowflake.connector
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>conn &lt;span style="color:#f92672">=&lt;/span> snowflake&lt;span style="color:#f92672">.&lt;/span>connector&lt;span style="color:#f92672">.&lt;/span>connect(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> account&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;your_account&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> user&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;your_user&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> password&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;your_password&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;transform_wh&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> session_parameters&lt;span style="color:#f92672">=&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;QUERY_TAG&amp;#39;&lt;/span>: json&lt;span style="color:#f92672">.&lt;/span>dumps({
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;pipeline&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;customer_ingestion&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;task&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;load_raw_customers&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;environment&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;production&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The goal is simple: &lt;strong>every query that runs on your platform should be attributable to a team, a pipeline, or a tool.&lt;/strong> Start with the big consumers and work outward.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="kill-the-zombies">Kill the zombies&lt;/h3>
&lt;br>
&lt;p>Every data platform accumulates dead weight. Tables nobody queries. Pipelines that run faithfully every morning, transforming data that no dashboard, no analyst, and no model has touched in months.&lt;/p>
&lt;p>These zombies cost you twice: once in compute (the pipeline that refreshes them) and once in storage (the data that sits there). Finding them is straightforward:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Tables with zero reads in the last 90 days
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.table_schema,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#66d9ef">table_name&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#66d9ef">row_count&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.bytes &lt;span style="color:#f92672">/&lt;/span> POWER(&lt;span style="color:#ae81ff">1024&lt;/span>, &lt;span style="color:#ae81ff">3&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> size_gb,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.last_altered,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MAX&lt;/span>(ah.query_start_time) &lt;span style="color:#66d9ef">AS&lt;/span> last_queried
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.tables t
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">LEFT&lt;/span> &lt;span style="color:#66d9ef">JOIN&lt;/span> snowflake.account_usage.access_history ah
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> ah.base_objects_accessed &lt;span style="color:#66d9ef">LIKE&lt;/span> &lt;span style="color:#e6db74">&amp;#39;%&amp;#39;&lt;/span> &lt;span style="color:#f92672">||&lt;/span> t.&lt;span style="color:#66d9ef">table_name&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#39;%&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> ah.query_start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">90&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.table_schema &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">IN&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;INFORMATION_SCHEMA&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> t.deleted &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> t.&lt;span style="color:#66d9ef">row_count&lt;/span> &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.table_schema, t.&lt;span style="color:#66d9ef">table_name&lt;/span>, t.&lt;span style="color:#66d9ef">row_count&lt;/span>, t.bytes, t.last_altered
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">HAVING&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> last_queried &lt;span style="color:#66d9ef">IS&lt;/span> &lt;span style="color:#66d9ef">NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> size_gb &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>On the AWS side, check your S3 storage for orphaned data. Landing zones accumulate raw files that were loaded months ago and never cleaned up. A simple lifecycle policy handles this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Rules&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;ID&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Archive raw data after 90 days&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Status&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Enabled&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Filter&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Prefix&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;raw-landing/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Transitions&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Days&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">90&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;StorageClass&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;GLACIER_INSTANT_RETRIEVAL&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Expiration&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;Days&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">365&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The cultural shift matters as much as the tooling. Make it a quarterly habit: pull the zombie table list, review it with the team, and deprecate what nobody uses. If someone screams, you can always restore from Time Travel. But in my experience, nobody screams. The data was already dead — you&amp;rsquo;re just acknowledging it.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="cost-anomalies-will-find-you--or-you-can-find-them-first">Cost anomalies will find you — or you can find them first&lt;/h3>
&lt;br>
&lt;p>The scariest cost event isn&amp;rsquo;t the gradual creep. It&amp;rsquo;s the single bad query or misconfigured pipeline that doubles your weekly bill overnight. I&amp;rsquo;ve seen a single Snowflake query with a missing WHERE clause scan an entire 2TB table repeatedly inside a loop — burning through hundreds of credits in an hour.&lt;/p>
&lt;p>On Snowflake, set up resource monitors as a basic guardrail:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a resource monitor with alerts and hard stop
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> RESOURCE MONITOR monthly_budget
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WITH&lt;/span> CREDIT_QUOTA &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">5000&lt;/span> &lt;span style="color:#75715e">-- adjust to your monthly budget
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> FREQUENCY &lt;span style="color:#f92672">=&lt;/span> MONTHLY
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> START_TIMESTAMP &lt;span style="color:#f92672">=&lt;/span> IMMEDIATELY
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TRIGGERS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#ae81ff">75&lt;/span> PERCENT &lt;span style="color:#66d9ef">DO&lt;/span> &lt;span style="color:#66d9ef">NOTIFY&lt;/span> &lt;span style="color:#75715e">-- email alert at 75%
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#ae81ff">90&lt;/span> PERCENT &lt;span style="color:#66d9ef">DO&lt;/span> &lt;span style="color:#66d9ef">NOTIFY&lt;/span> &lt;span style="color:#75715e">-- email alert at 90%
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span> PERCENT &lt;span style="color:#66d9ef">DO&lt;/span> SUSPEND; &lt;span style="color:#75715e">-- hard stop at 100%
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Apply it to a warehouse
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">ALTER&lt;/span> WAREHOUSE transform_wh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SET&lt;/span> RESOURCE_MONITOR &lt;span style="color:#f92672">=&lt;/span> monthly_budget;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>On AWS, enable Cost Anomaly Detection in the AWS Cost Management console. It uses ML to detect unusual spending patterns and sends alerts via SNS. For Snowflake-specific monitoring, a lightweight approach is a scheduled task that checks daily credit consumption against a rolling average:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a simple anomaly detection view
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> &lt;span style="color:#66d9ef">VIEW&lt;/span> monitoring.daily_cost_anomalies &lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WITH&lt;/span> daily_usage &lt;span style="color:#66d9ef">AS&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DATE_TRUNC(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, start_time) &lt;span style="color:#66d9ef">AS&lt;/span> usage_date,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SUM&lt;/span>(credits_used) &lt;span style="color:#66d9ef">AS&lt;/span> daily_credits
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> snowflake.account_usage.warehouse_metering_history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_time &lt;span style="color:#f92672">&amp;gt;=&lt;/span> DATEADD(&lt;span style="color:#e6db74">&amp;#39;day&amp;#39;&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">60&lt;/span>, &lt;span style="color:#66d9ef">CURRENT_TIMESTAMP&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> usage_date, warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>averages &lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AVG&lt;/span>(daily_credits) &lt;span style="color:#66d9ef">AS&lt;/span> avg_daily_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> STDDEV(daily_credits) &lt;span style="color:#66d9ef">AS&lt;/span> stddev_daily_credits
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> daily_usage
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> usage_date &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">GROUP&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> du.usage_date,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> du.warehouse_name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> du.daily_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a.avg_daily_credits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUND((du.daily_credits &lt;span style="color:#f92672">-&lt;/span> a.avg_daily_credits) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">NULLIF&lt;/span>(a.stddev_daily_credits, &lt;span style="color:#ae81ff">0&lt;/span>), &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> z_score
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> daily_usage du
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">JOIN&lt;/span> averages a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ON&lt;/span> du.warehouse_name &lt;span style="color:#f92672">=&lt;/span> a.warehouse_name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">WHERE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> du.usage_date &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">CURRENT_DATE&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">AND&lt;/span> (du.daily_credits &lt;span style="color:#f92672">-&lt;/span> a.avg_daily_credits) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#66d9ef">NULLIF&lt;/span>(a.stddev_daily_credits, &lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">BY&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> z_score &lt;span style="color:#66d9ef">DESC&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A z-score above 2 means today&amp;rsquo;s spend is more than two standard deviations above the 60-day average. That&amp;rsquo;s worth investigating. Pipe this into a Teams or Slack alert via an AWS Lambda function and you&amp;rsquo;ve got same-day cost anomaly detection for the price of a few lines of SQL.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-your-data-model-costs-you">What your data model costs you&lt;/h3>
&lt;br>
&lt;p>This one&amp;rsquo;s subtle but powerful. The way you model your data directly affects how much compute you burn on every query.&lt;/p>
&lt;p>In columnar warehouses like Snowflake, wide denormalised tables query faster than star schemas with multiple JOINs — because each JOIN has overhead, and Snowflake is optimised for scanning columns from flat structures. But wide tables cost more to &lt;em>maintain&lt;/em> because updating a single dimension attribute means rewriting many rows in the fact table.&lt;/p>
&lt;p>The practical approach is a layered architecture:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Staging models&lt;/strong> (views or ephemeral): Minimal transformation, column selection, type casting. Cheap to run, no storage cost.&lt;/li>
&lt;li>&lt;strong>Intermediate models&lt;/strong> (tables or incremental): Business logic, deduplication, SCD handling. Materialised because they&amp;rsquo;re referenced by multiple downstream models.&lt;/li>
&lt;li>&lt;strong>Mart models&lt;/strong> (incremental or table): Wide, denormalised, optimised for consumer queries. These are what analysts and BI tools actually hit.&lt;/li>
&lt;/ul>
&lt;p>The cost trap is materialising too much too early. Every table materialisation means Snowflake stores and maintains that data. Every &lt;code>dbt run&lt;/code> rebuilds it. If an intermediate model is only referenced by one downstream model, make it ephemeral or a view — let Snowflake inline it at query time.&lt;/p>
&lt;p>Check your dbt DAG for this pattern: a staging model materialised as a table, referenced by a single intermediate model, which is also materialised as a table, referenced by a single mart. That&amp;rsquo;s three materialisations where one (the mart) would suffice. The staging and intermediate models can be views or ephemeral — the compute happens once when the mart builds, not three separate times.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="coming-back-to-why-this-matters">Coming back to why this matters&lt;/h3>
&lt;br>
&lt;p>I started this article with a story about not being able to explain my own platform&amp;rsquo;s costs. That wasn&amp;rsquo;t a technical failure. It was a leadership failure. I&amp;rsquo;d built something valuable and then neglected the part that kept it funded.&lt;/p>
&lt;p>The data engineers I respect most aren&amp;rsquo;t the ones who build the most elegant pipelines. They&amp;rsquo;re the ones who can walk into a budget conversation and say: &amp;ldquo;Here&amp;rsquo;s what we spend. Here&amp;rsquo;s what we get for it. Here&amp;rsquo;s what I&amp;rsquo;d cut, and here&amp;rsquo;s what I&amp;rsquo;d invest more in.&amp;rdquo; That&amp;rsquo;s the kind of clarity that earns trust — and trust is what keeps data teams alive when the inevitable cost-cutting conversations happen.&lt;/p>
&lt;p>You don&amp;rsquo;t need a FinOps certification or an expensive monitoring tool to get there. You need the queries in this article, a quarterly habit of reviewing them, and the willingness to kill the zombies nobody wants to admit are dead.&lt;/p>
&lt;p>Start with the 15-minute audit. Tag your queries. Set up a resource monitor. Do those three things this week and you&amp;rsquo;ll know more about your platform&amp;rsquo;s economics than most data teams learn in a year.&lt;/p>
&lt;p>The cheapest query is the one you never need to run. But the most valuable skill is knowing which queries are worth paying for.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Platforms &amp; Cloud</category><category>Snowflake</category><category>AWS</category><category>Cost Optimisation</category><category>FinOps</category><category>dbt</category><category>Data Platform</category></item><item><title>The 2026 Data Engineering Strategy Nobody's Writing (But Everyone Needs)</title><link>https://ghostinthedata.info/posts/2026/2026-01-15-data-engineering-strategy/</link><pubDate>Thu, 15 Jan 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-01-15-data-engineering-strategy/</guid><author>Chris Hillman</author><description>While teams obsess over AI tools and cost optimisation, we're ignoring the talent pipeline crisis that will define success through 2030. Here's what your planning memo should say instead.</description><content:encoded>&lt;p>What if I told you the biggest threat to your data platform isn&amp;rsquo;t technology—it&amp;rsquo;s that we&amp;rsquo;ve stopped building the next generation of engineers who&amp;rsquo;ll run it?&lt;/p>
&lt;br>
&lt;p>Not the latest database that promises to solve everything. Not whether you picked the right orchestrator.&lt;/p>
&lt;p>The real crisis is that we&amp;rsquo;ve systematically broken our talent pipeline. And in 2026, that decision is going to start costing us in ways that no amount of tooling can fix.&lt;/p>
&lt;p>But let&amp;rsquo;s back up. Because if you&amp;rsquo;re in planning mode right now—building roadmaps, setting budgets, arguing about which technologies to bet on—you&amp;rsquo;re probably focused on the wrong things. Everyone is.&lt;/p>
&lt;p>Here&amp;rsquo;s what I believe actually matters for 2026—and what will determine whether you&amp;rsquo;re thriving or scrambling by 2030.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="were-creating-the-next-talent-crisison-purpose">We&amp;rsquo;re Creating the Next Talent Crisis—On Purpose&lt;/h2>
&lt;br>
&lt;p>This was a conversation:&lt;/p>
&lt;p>&amp;ldquo;We need more engineers. Senior people who can hit the ground running.&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;Agreed. Let&amp;rsquo;s post the role at six years minimum experience.&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;Why aren&amp;rsquo;t we getting qualified candidates?&amp;rdquo;&lt;/p>
&lt;p>&amp;ldquo;Must be a talent shortage. Everyone wants data engineers these days.&amp;rdquo;&lt;/p>
&lt;p>It&amp;rsquo;s not a shortage. It&amp;rsquo;s a manufactured crisis.&lt;/p>
&lt;p>Entry-level data engineering positions represent just 2% of job postings. Two percent. Meanwhile, roles requiring 6+ years of experience make up nearly 20% of openings. We&amp;rsquo;ve created an impossible paradox: the industry demands experienced engineers while systematically refusing to create them.&lt;/p>
&lt;p>And here&amp;rsquo;s the thing—data engineering skills aren&amp;rsquo;t taught in computer science programs. You don&amp;rsquo;t learn dimensional modelling or pipeline orchestration or data quality frameworks in a classroom. These are skills built through experience, through making mistakes, through having a senior engineer look at your code and say &amp;ldquo;okay, but what happens when this table has a million rows instead of a thousand?&amp;rdquo;&lt;/p>
&lt;p>I&amp;rsquo;ve watched this play out across every team I&amp;rsquo;ve worked with. Companies want plug-and-play professionals, but they won&amp;rsquo;t invest in training. Fresh graduates realise quickly that &amp;ldquo;junior data engineer&amp;rdquo; jobs are about as common as unicorn sightings. The roles exist, technically. They&amp;rsquo;re just not being hired for.&lt;/p>
&lt;p>The retention numbers tell the same story from the other end. Surveys show that 95% of current data engineers report experiencing burnout. Seventy percent are likely to seek new jobs within 12 months. Four out of five are considering leaving the career entirely.&lt;/p>
&lt;p>So we&amp;rsquo;re not hiring juniors, and we&amp;rsquo;re burning out the seniors we have.&lt;/p>
&lt;p>The irony is that hiring juniors actually helps senior engineers. Juniors handle the repetitive stuff—the data quality checks you&amp;rsquo;ve written a hundred times, the standard ETL patterns you could code in your sleep, the documentation that needs doing but never gets prioritised. This frees up senior time for architecture decisions, complex problem-solving, and the high-value work that actually requires experience.&lt;/p>
&lt;p>But more importantly, juniors represent the institutional knowledge that walks out the door when that burned-out senior finally quits. And right now, we&amp;rsquo;re choosing short-term convenience over long-term sustainability.&lt;/p>
&lt;p>When I started in this field, there were 10 candidates per data engineering job listing. Now? You&amp;rsquo;re lucky to get 2.5. For context, web developers see 10 candidates per job. Marketing managers? Try 53.&lt;/p>
&lt;p>This isn&amp;rsquo;t a future problem. It&amp;rsquo;s a 2026 problem. And by 2030, projections suggest 10.5 million unfilled data and analytics positions globally.&lt;/p>
&lt;p>Your 2030 self is going to be really mad about this decision.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="ai-changes-everything-except-what-actually-matters">AI Changes Everything (Except What Actually Matters)&lt;/h2>
&lt;br>
&lt;p>And this is where many teams think AI will save them.&lt;/p>
&lt;p>A junior engineer on a team needed to build a data quality framework for their pipeline. In 2020, this would have meant three hours of my time walking through the approach, another two hours of their time implementing it, and probably a day of debugging edge cases.&lt;/p>
&lt;p>Instead, she spent 20 minutes with Claude. Described the problem, got three different approaches, validated the logic, tested the implementations, and asked for feedback on edge cases I hadn&amp;rsquo;t even thought to mention. By the time she pinged me, she had working code and specific questions about production deployment.&lt;/p>
&lt;p>This is the reality of AI in data engineering in 2026. It&amp;rsquo;s not replacing engineers. It&amp;rsquo;s fundamentally changing what &amp;ldquo;junior&amp;rdquo; means.&lt;/p>
&lt;p>GitHub Copilot has over 15 million users now. Ninety percent of Fortune 100 companies have adopted it. Developers report 51% faster coding speed—and that matches what I see in practice. The text-to-SQL market has matured to where straightforward queries just work. Tools like Vanna.AI and the native integrations in Databricks and Snowflake handle the routine stuff competently.&lt;/p>
&lt;p>But here&amp;rsquo;s what the adoption stats don&amp;rsquo;t tell you: AI is a force multiplier for experience, not a replacement for it.&lt;/p>
&lt;p>I&amp;rsquo;ve seen junior engineers use AI to cover gaps in their understanding—generating code they can&amp;rsquo;t debug, creating pipelines they can&amp;rsquo;t explain, building on top of patterns they don&amp;rsquo;t actually grasp. And I&amp;rsquo;ve seen senior engineers use it to remove friction and accelerate decisions they already know how to make.&lt;/p>
&lt;p>The difference matters. A lot.&lt;/p>
&lt;p>Some organisations now require 85% test coverage for AI-assisted code versus 70% for human-written code. They flag high-AI-content pull requests for additional security review. A few even implement &amp;ldquo;AI-free Fridays&amp;rdquo; to prevent skill atrophy. Because the risk isn&amp;rsquo;t that AI writes bad code—it&amp;rsquo;s that engineers stop learning how to recognise when it does.&lt;/p>
&lt;p>The Spider 2.0 benchmark—a standard test for enterprise-level SQL generation—is instructive here. GPT-4 solves only 6% of enterprise-level SQL questions. Six percent. Not because the models are bad, but because enterprise reality is messy. Tables with a thousand columns, sparse data that doesn&amp;rsquo;t match the schema documentation, business logic buried in five years of accumulated stored procedures that nobody fully understands anymore.&lt;/p>
&lt;p>AI is phenomenal at solving clean, well-defined problems. Enterprise data engineering is rarely clean or well-defined.&lt;/p>
&lt;h3 id="the-hive-mind-advantage-you-can-actually-use">The Hive Mind Advantage (You Can Actually Use)&lt;/h3>
&lt;p>Here&amp;rsquo;s something that doesn&amp;rsquo;t get talked about enough: when an AI learns something—like how to write performant SQL, or recognise a common anti-pattern—every instance of that AI learns it simultaneously.&lt;/p>
&lt;p>Compare that to human learning. If I figure out a better approach to incremental loads, I share it with my team. Maybe it spreads to 5-10 people. Maybe someone writes a blog post and it reaches a few hundred. But it takes &lt;em>years&lt;/em> for a best practice to become industry standard.&lt;/p>
&lt;p>AI doesn&amp;rsquo;t have that limitation. When Claude or GPT gets better at something, every developer using it gets better at it on the same day.&lt;/p>
&lt;p>This is where the real opportunity lies for 2026: not fighting against AI or using it as a crutch, but deliberately leveraging that collective learning capability:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Peer review assistance&lt;/strong>: AI can spot patterns across your entire codebase that no single engineer could hold in their head. It&amp;rsquo;s seen millions of pipelines fail in similar ways—use that knowledge.&lt;/li>
&lt;li>&lt;strong>Best practice enforcement&lt;/strong>: Standards that would require constant human vigilance can be automated. Style guides, naming conventions, common performance pitfalls—AI catches these at scale.&lt;/li>
&lt;li>&lt;strong>Accelerated learning&lt;/strong>: Juniors can ask &amp;ldquo;why is this pattern better?&amp;rdquo; and get explanations based on millions of examples (specific to your industry, or tapped into real work examples), not just what their one mentor happens to know. The AI has seen every permutation of every mistake.&lt;/li>
&lt;/ul>
&lt;p>The engineers who thrive won&amp;rsquo;t be the ones competing against AI. They&amp;rsquo;ll be the ones who figured out how to use that hive-mind capability to augment their own judgment.&lt;/p>
&lt;p>So yes, AI should be in your 2026 strategy. But if you&amp;rsquo;re counting on it to solve your junior engineer problem? You&amp;rsquo;re in for an expensive surprise. Because AI might help juniors be productive faster, but it doesn&amp;rsquo;t teach them judgment and critical thinking. And in data engineering, this is one of those soft skills that separates the engineers who ship reliable systems from the ones who generate incidents.&lt;/p>
&lt;p>The question isn&amp;rsquo;t &amp;ldquo;should we use AI&amp;rdquo;—of course you should. The question is &amp;ldquo;how do we use AI without creating a generation of engineers who can&amp;rsquo;t function without it?&amp;rdquo;&lt;/p>
&lt;p>I don&amp;rsquo;t have a perfect answer to that. But I know it starts with still hiring and training juniors, even though it&amp;rsquo;s harder and slower than we&amp;rsquo;d like.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="the-180000-mistake-hiding-in-your-architecture">The $180,000 Mistake Hiding in Your Architecture&lt;/h2>
&lt;br>
&lt;p>Here&amp;rsquo;s a story that&amp;rsquo;s going to sound familiar.&lt;/p>
&lt;p>I got asked to review a friends data platform that was spending a lot of compute. The team was frustrated. &amp;ldquo;We&amp;rsquo;re growing fast, costs keep climbing, and we can&amp;rsquo;t figure out where the money&amp;rsquo;s going.&amp;rdquo;&lt;/p>
&lt;p>So I looked at their queries. Ninety percent of them scanned less than 100MB of data.&lt;/p>
&lt;p>Not gigabytes. Not terabytes. Megabytes. The kind of data that fits comfortably in your laptop&amp;rsquo;s memory.&lt;/p>
&lt;p>They were running a distributed cloud data warehouse—with all the complexity, cost, and operational overhead that entails—to process files that could run on a Raspberry Pi.&lt;/p>
&lt;p>This is the emperor-has-no-clothes moment of 2026: most &amp;ldquo;big data&amp;rdquo; isn&amp;rsquo;t actually big.&lt;/p>
&lt;p>Jordan Tigani figured this out a few years ago. He&amp;rsquo;s the former Google BigQuery founding engineer, so he&amp;rsquo;s not some random person with an axe to grind. He looked at actual BigQuery usage patterns and found that 90% of queries process less than 100MB. Among instances with supposedly &amp;ldquo;big data,&amp;rdquo; 98% of queries scan less than 1TB.&lt;/p>
&lt;p>His conclusion: &amp;ldquo;The data cataclysm that had been predicted hasn&amp;rsquo;t come to pass. Data sizes may have gotten marginally larger, but hardware has gotten bigger at an even faster rate.&amp;rdquo;&lt;/p>
&lt;p>DuckDB is the practical realisation of this insight. It&amp;rsquo;s an embedded columnar database that runs inside Python, R, or just the command line. No server required. No cluster management. When it runs out of memory, it automatically spills to disk. It natively handles Parquet, CSV, and JSON.&lt;/p>
&lt;p>For most data engineering work, this means you can develop locally without cloud dependencies, test rapidly in CI/CD without warehouse costs, and handle production workloads up to hundreds of gigabytes on a single machine.&lt;/p>
&lt;p>The cost implications are striking. After moving appropriate workloads to DuckDB, teams regularly see 80-90% cost reduction. One team processed pipeline work that was taking 8 hours and got it down to 8 minutes. On cheaper infrastructure.&lt;/p>
&lt;p>Polars complements this story nicely—a Rust-based DataFrame library that gives you programmatic workflows where DuckDB gives you SQL. Together, they represent a genuine paradigm shift from &amp;ldquo;scale out everything&amp;rdquo; to &amp;ldquo;right-size your architecture.&amp;rdquo;&lt;/p>
&lt;p>You still need distributed systems for genuinely big data, for complex concurrent workloads, for petabyte-scale scanning. But the skill that matters isn&amp;rsquo;t knowing DuckDB or Polars—tools change. The skill is architectural judgment: knowing when a single powerful machine beats a distributed cluster. Knowing when to optimise for cost versus scale versus simplicity.&lt;/p>
&lt;p>This is what separates senior engineers from junior ones. And it&amp;rsquo;s why AI can&amp;rsquo;t replace architectural judgment—the models are trained on a world where &amp;ldquo;big data&amp;rdquo; meant Hadoop clusters and distributed everything. They don&amp;rsquo;t know that world is ending.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="what-your-2026-planning-should-actually-address">What Your 2026 Planning Should Actually Address&lt;/h2>
&lt;br>
&lt;p>If you&amp;rsquo;re in planning meetings right now, here&amp;rsquo;s what&amp;rsquo;s probably on your list:&lt;/p>
&lt;ul>
&lt;li>Which AI tools to adopt&lt;/li>
&lt;li>Whether to migrate to a new data warehouse&lt;/li>
&lt;li>Cost optimisation initiatives&lt;/li>
&lt;li>Team headcount requests&lt;/li>
&lt;/ul>
&lt;p>And here&amp;rsquo;s what should be on your list but probably isn&amp;rsquo;t:&lt;/p>
&lt;p>&lt;strong>First: How are we creating the next generation of engineers?&lt;/strong>&lt;/p>
&lt;p>Not &amp;ldquo;when we have time&amp;rdquo; or &amp;ldquo;once we&amp;rsquo;re fully staffed.&amp;rdquo; Now. Budget for at least one junior engineer per three seniors. Build mentorship into performance reviews. Dedicate senior time to teaching, and treat that as valuable work, not a distraction from &amp;ldquo;real&amp;rdquo; work.&lt;/p>
&lt;p>Your 2027 self will thank you. Your 2030 self will wonder why this wasn&amp;rsquo;t obvious to everyone.&lt;/p>
&lt;p>&lt;strong>Second: Where are we using distributed systems unnecessarily?&lt;/strong>&lt;/p>
&lt;p>Audit your infrastructure. Find the places where you&amp;rsquo;re using Spark to process files that fit in memory, or Snowflake for queries that scan megabytes. Not to eliminate distributed systems—they&amp;rsquo;re essential for genuinely big data—but to right-size your architecture.&lt;/p>
&lt;p>Every workload that moves from a distributed system to a well-tuned single machine is potentially 80-90% cost savings. That adds up fast.&lt;/p>
&lt;p>&lt;strong>Third: How are we integrating AI without creating skill atrophy?&lt;/strong>&lt;/p>
&lt;p>Use AI. Absolutely use it. But have a plan for ensuring your team still knows how to think without it. Maybe that&amp;rsquo;s higher test coverage requirements for AI-assisted code. Maybe it&amp;rsquo;s regular code reviews focused on understanding, not just correctness. Maybe it&amp;rsquo;s explicit training on recognising when AI is confidently wrong.&lt;/p>
&lt;p>Whatever it is, don&amp;rsquo;t just adopt AI and hope for the best.&lt;/p>
&lt;p>&lt;strong>Fourth: What are we doing about retention?&lt;/strong>&lt;/p>
&lt;p>If 95% of data engineers are burned out and 70% are job-hunting, what makes you think yours are different? This isn&amp;rsquo;t about ping-pong tables or free snacks. It&amp;rsquo;s about sustainable workloads, clear career progression, meaningful work, and not being on-call for systems that could be more reliable with better architecture.&lt;/p>
&lt;p>Every senior engineer who leaves takes years of institutional knowledge with them. In a world where we&amp;rsquo;re not hiring juniors to replace them, that knowledge is gone permanently.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="the-2030-horizon-why-this-matters-now">The 2030 Horizon: Why This Matters Now&lt;/h2>
&lt;br>
&lt;p>Everything I&amp;rsquo;ve described isn&amp;rsquo;t just about surviving 2026. It&amp;rsquo;s about positioning for where this industry is headed.&lt;/p>
&lt;p>By 2030, projections suggest demand for data professionals will grow by 36%. The global AI market will hit approximately $2 trillion. And those 10.5 million unfilled positions I mentioned earlier? That&amp;rsquo;s the scale of the gap we&amp;rsquo;re creating right now, with every junior engineer we don&amp;rsquo;t hire and every senior we burn out.&lt;/p>
&lt;p>The consensus from industry analysts is clear: AI will transform data engineering workflows, but it won&amp;rsquo;t replace the humans who design, implement, and maintain those systems. What will change is the nature of the work.&lt;/p>
&lt;p>Data engineers will transition from technical executors to strategic leaders. The routine tasks—basic ETL, straightforward data cleansing, simple pipeline monitoring—will increasingly be handled by AI assistants. What remains is the work that requires judgment: understanding business context, making architectural trade-offs, ensuring data quality for AI applications, and building systems that are reliable at scale.&lt;/p>
&lt;p>Here&amp;rsquo;s the paradox that most people miss: AI applications are hungry for huge, well-prepared datasets. Every chatbot, every recommendation engine, every automated decision system needs data pipelines feeding it. The same AI revolution that automates some of our work multiplies the demand for the infrastructure we build.&lt;/p>
&lt;p>The question is whether you&amp;rsquo;ll have the team to build it.&lt;/p>
&lt;p>Organisations that invest in talent pipelines today—hiring juniors, retaining seniors, building mentorship cultures—will have the workforce to capture this opportunity. Organisations that don&amp;rsquo;t will be competing for an ever-shrinking pool of experienced engineers, paying premium salaries for people who could have been developed internally.&lt;/p>
&lt;p>The technology decisions you make in 2026 are reversible. Switch databases, change orchestrators, adopt new tools—these are tactical choices you can adjust. But the talent decisions compound. Every junior you don&amp;rsquo;t hire today is a mid-level engineer you won&amp;rsquo;t have in 2028 and a senior you won&amp;rsquo;t have in 2030.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h2 id="what-actually-matters">What Actually Matters&lt;/h2>
&lt;br>
&lt;p>The data engineering landscape in 2026 is simultaneously more accessible and more demanding than ever before. AI tools, managed services, and mature open source have lowered the barriers to building data systems. But the systems we need to build are more complex, the scale challenges are more varied, and the skills required to make good architectural decisions have broadened significantly.&lt;/p>
&lt;p>The teams that thrive in this environment—and position themselves for 2030—won&amp;rsquo;t be the ones with the fanciest tools or the biggest budgets. They&amp;rsquo;ll be the ones who:&lt;/p>
&lt;ul>
&lt;li>Build sustainable talent pipelines instead of fighting for a shrinking pool of senior engineers&lt;/li>
&lt;li>Right-size their architectures instead of defaulting to distributed systems for everything&lt;/li>
&lt;li>Use AI as a force multiplier—including leveraging its collective learning capability—instead of treating it as a crutch&lt;/li>
&lt;li>Create environments where people want to stay instead of burning through talent&lt;/li>
&lt;/ul>
&lt;p>None of this is easy. But it&amp;rsquo;s necessary.&lt;/p>
&lt;p>Because here&amp;rsquo;s the thing: the talent pipeline crisis, the cost optimisation opportunities, the AI integration challenges—these aren&amp;rsquo;t going away. They&amp;rsquo;re getting worse. The organisations that address them proactively in 2026 will have a massive advantage. The ones that don&amp;rsquo;t will spend 2027 wondering why they can&amp;rsquo;t hire anyone, why their costs keep climbing, and why their best engineers keep leaving.&lt;/p>
&lt;p>The choice is yours. But choose soon.&lt;/p>
&lt;p>The biggest risk to your data platform in 2026 isn&amp;rsquo;t technology. It&amp;rsquo;s whether you&amp;rsquo;ll have anyone left who knows how to run it.&lt;/p>
&lt;p>And by 2030, everyone will wish they&amp;rsquo;d acted now.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Career</category><category>Strategy</category><category>Team Building</category><category>Cost Optimisation</category><category>DuckDB</category><category>AI Tools</category><category>Career Development</category><category>2026 Trends</category><category>Future of Work</category></item></channel></rss>