<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Laura Chicovis </title>
    <description>The latest articles on DEV Community by Laura Chicovis  (@laura_cristinachicovisd).</description>
    <link>https://dev.to/laura_cristinachicovisd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4081810%2F86dee10b-34bd-4ddd-bf84-ad526f124b30.jpg</url>
      <title>DEV Community: Laura Chicovis </title>
      <link>https://dev.to/laura_cristinachicovisd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/laura_cristinachicovisd"/>
    <language>en</language>
    <item>
      <title>Partitioning, clustering, and BI Engine: measuring what each one saves in BigQuery</title>
      <dc:creator>Laura Chicovis </dc:creator>
      <pubDate>Thu, 27 Aug 2026 13:18:52 +0000</pubDate>
      <link>https://dev.to/laura_cristinachicovisd/partitioning-clustering-and-bi-engine-measuring-what-each-one-saves-in-bigquery-2m59</link>
      <guid>https://dev.to/laura_cristinachicovisd/partitioning-clustering-and-bi-engine-measuring-what-each-one-saves-in-bigquery-2m59</guid>
      <description>&lt;p&gt;By the end of this walkthrough you will have two versions of the same table, a repeatable way to price any query before running it, and a query that tells you exactly how many bytes each version billed you. No estimates, no vendor benchmark, just the numbers your own project reports.&lt;/p&gt;

&lt;p&gt;I keep running into the same situation: someone turns on partitioning, the bill does not move, and the conclusion becomes "partitioning does not work here". Usually partitioning worked fine and the queries were never written to use it. The only way to settle that is to measure both sides.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Google Cloud project with billing enabled and the BigQuery API on.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;bq&lt;/code&gt; CLI from the Google Cloud SDK, or the BigQuery console if you prefer clicking. Every snippet below is standard GoogleSQL, so both work.&lt;/li&gt;
&lt;li&gt;Permissions to create datasets and tables, plus &lt;code&gt;bigquery.jobs.list&lt;/code&gt; if you want the billing history query at the end.&lt;/li&gt;
&lt;li&gt;Around 5 to 10 GiB of scanning budget. On-demand pricing gives you the first 1 TiB per month free and charges $6.25 per TiB after that in the US multi region (Google Cloud, BigQuery pricing documentation), so this exercise costs cents, not dollars.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One warning before you start: the queries that build the optimized table are themselves billed. Dry run them first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bq mk &lt;span class="nt"&gt;--location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;US &lt;span class="nt"&gt;--dataset&lt;/span&gt; bq_cost_lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Build the baseline
&lt;/h2&gt;

&lt;p&gt;I use the public Stack Overflow dataset because everyone can reproduce it. This copies a slice of it into a plain table with no partitioning and no clustering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`bq_cost_lab.questions_plain`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;creation_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;owner_user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;view_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tags&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`bigquery-public-data.stackoverflow.posts_questions`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;creation_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2018-01-01'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note what I did not copy. The &lt;code&gt;body&lt;/code&gt; column holds the full HTML of every question and it is the single most expensive column in that table. BigQuery charges according to the data processed in the columns you select, even when you set an explicit LIMIT (Google Cloud, BigQuery pricing documentation), so column selection is the cheapest optimization available and it costs nothing to implement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Price the query before you run it
&lt;/h2&gt;

&lt;p&gt;The dry run flag returns the byte estimate without executing anything and without charging you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bq query &lt;span class="nt"&gt;--use_legacy_sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--dry_run&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;'SELECT owner_user_id, COUNT(*) AS questions
 FROM `bq_cost_lab.questions_plain`
 WHERE DATE(creation_date) BETWEEN "2018-03-01" AND "2018-03-07"
 GROUP BY owner_user_id'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write down the number it gives you. That is your baseline: &lt;code&gt;[CONFIRMAR: rodar o dry run e anotar o valor observado]&lt;/code&gt; bytes. Divide it by 1,099,511,627,776 to get TiB, then multiply by 6.25 for the dollar figure. The date filter in that query saves you nothing yet, because a plain table has no partitions to skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Partition on the column you actually filter by
&lt;/h2&gt;

&lt;p&gt;Partitioning splits the table into physical blocks, and a qualifying filter on the partitioning column lets BigQuery scan the matching partitions and skip the rest, a process the documentation calls pruning (Google Cloud, introduction to partitioned tables).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`bq_cost_lab.questions_tuned`&lt;/span&gt;
&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creation_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;CLUSTER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;owner_user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;
&lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;require_partition_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`bq_cost_lab.questions_plain`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three decisions are packed into those four lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily granularity, not hourly.&lt;/strong&gt; A partitioned table is capped at 10,000 partitions &lt;code&gt;[CONFIRMAR: conferir o número atual na página Quotas and limits, o limite subiu de 4.000 em 2024]&lt;/code&gt; (Google Cloud, BigQuery quotas and limits). Daily partitions give you 27 years of runway. Hourly partitions give you 416 days, and hitting that ceiling in production is a migration, not a config change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;require_partition_filter = TRUE&lt;/code&gt;.&lt;/strong&gt; This rejects any query that does not filter on the partitioning column (Google Cloud, managing partitioned tables). It is one line, it is reversible with an ALTER statement, and it is the difference between a table that saves money and a table that saves money until the first person forgets the WHERE clause. Turn it on unless you have a specific reason not to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The partition filter has to be a constant expression.&lt;/strong&gt; Filtering on a column that is not the partition key prunes nothing, and neither does a filter whose value BigQuery cannot resolve before execution. This is where most disappointed partitioning stories end.&lt;/p&gt;

&lt;p&gt;Partitioning also has a storage side that rarely gets mentioned. Long term storage drops the rate by roughly half after 90 days without modification, and each partition of a partitioned table is evaluated separately for that discount (Google Cloud, BigQuery pricing documentation). On a plain table, one late arriving row resets the timer for everything. On a partitioned table, it resets one day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Cluster in the order you filter
&lt;/h2&gt;

&lt;p&gt;Clustering sorts storage blocks by the values in the clustered columns, and queries that filter or aggregate on those columns scan only the relevant blocks instead of the full table or partition (Google Cloud, introduction to clustered tables).&lt;/p&gt;

&lt;p&gt;You get up to four clustering columns, and the order determines the sort order, so the most frequently filtered column goes first (Google Cloud, creating clustered tables). In my snippet, &lt;code&gt;owner_user_id&lt;/code&gt; comes before &lt;code&gt;score&lt;/code&gt; because equality filters on user are common and selective, while score usually shows up as a range filter on top of that. Reverse them and the same query prunes worse.&lt;/p&gt;

&lt;p&gt;Now the part that surprises people. Run a dry run against the clustered table and compare it to the previous one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# replace 12345 with an owner_user_id that exists in your slice&lt;/span&gt;
bq query &lt;span class="nt"&gt;--use_legacy_sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--dry_run&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;'SELECT owner_user_id, COUNT(*) AS questions
 FROM `bq_cost_lab.questions_tuned`
 WHERE DATE(creation_date) BETWEEN "2018-03-01" AND "2018-03-07"
   AND owner_user_id = 12345
 GROUP BY owner_user_id'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The estimate drops because of partition pruning, but it does not reflect the clustering at all. When you query a clustered table you do not get an accurate cost estimate before execution, because the number of storage blocks to scan is not known until the query runs, and the final cost is based on the blocks actually scanned (Google Cloud, introduction to clustered tables). The dry run on a clustered table is an upper bound. Treating it as the answer is how teams conclude that clustering did nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Verify with billed bytes, not estimates
&lt;/h2&gt;

&lt;p&gt;Run both versions for real, then ask the metadata what happened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;creation_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;cache_hit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;total_bytes_processed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;total_bytes_billed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bytes_billed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;POW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;approx_usd&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`region-us`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFORMATION_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JOBS_BY_PROJECT&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;creation_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;statement_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SELECT'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;creation_time&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;total_bytes_billed&lt;/code&gt; is the number that reaches your invoice. Two details make it diverge from the estimate in ways worth knowing: charges are rounded up with a minimum of 10 MB per table referenced and 10 MB per query, and you are not charged for queries that fail or that return cached results (Google Cloud, BigQuery pricing documentation). If &lt;code&gt;cache_hit&lt;/code&gt; is true, you measured nothing. Change a literal and run it again.&lt;/p&gt;

&lt;p&gt;Record the pair. Baseline billed bytes: &lt;code&gt;[CONFIRMAR: valor observado]&lt;/code&gt;. Tuned billed bytes: &lt;code&gt;[CONFIRMAR: valor observado]&lt;/code&gt;. That ratio is the only savings figure I would put in a report.&lt;/p&gt;

&lt;p&gt;This is the same argument the &lt;a href="https://bixtech.ai/what-is-observability/?utm_source=devto&amp;amp;utm_medium=guestpost&amp;amp;utm_campaign=okr_backlinks_q3_2026" rel="noopener noreferrer"&gt;observability&lt;/a&gt; people have been making for years with logs, metrics, and traces. Cost is one more signal your platform emits. If nobody queries it, nobody knows what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Decide whether BI Engine earns its keep
&lt;/h2&gt;

&lt;p&gt;BI Engine caches table data in memory to accelerate SQL. When it accelerates a query under on-demand pricing, the stage that reads table data is charged for zero scanned bytes (Google Cloud, BigQuery pricing documentation). That sounds like free money until you look at how it bills: $0.0416 per GiB hour, charged per project where you reserved capacity, whether or not anyone runs a query.&lt;/p&gt;

&lt;p&gt;So the math is a subscription against a metered service. A 10 GiB reservation runs about $0.42 per hour, close to $304 per month at 730 hours. At $6.25 per TiB, that is the equivalent of roughly 48 TiB of scanning. If the dashboards hitting that reservation do not scan near that volume, the reservation loses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- reserve capacity, then confirm queries are actually being accelerated&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;bi_engine_statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bi_engine_mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;total_bytes_billed&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`region-us`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFORMATION_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JOBS_BY_PROJECT&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;creation_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMP_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;bi_engine_statistics&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;creation_time&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;bi_engine_mode&lt;/code&gt; comes back as DISABLED or PARTIAL for most of your traffic, you are paying for memory that is not accelerating anything. Two things commonly cause that: the working set does not fit the reservation, or the queries use operations the accelerator does not cover.&lt;/p&gt;

&lt;p&gt;One detail that changes the calculation for larger shops: BigQuery editions commitments bundle BI Engine capacity at no extra cost, starting at 5 GiB for 100 slots and scaling to 100 GiB at 2,000 slots (Google Cloud, BigQuery pricing documentation). If you already hold a commitment, some of that capacity may be sitting unclaimed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not fix
&lt;/h2&gt;

&lt;p&gt;Partitioning, clustering, and BI Engine all reduce bytes scanned. None of them touch the two costs I see grow fastest.&lt;/p&gt;

&lt;p&gt;The first is storage on tables nobody queries. Active logical storage runs $23.55 per TiB per month in the US multi region (Google Cloud, BigQuery pricing documentation), and partition expiration is the cheapest cleanup available. The second is repeated scanning of the same aggregate by twelve dashboards, which is a materialized view problem rather than a partitioning one.&lt;/p&gt;

&lt;p&gt;There is a third one that no table setting solves. If the pipeline writes duplicates and someone rebuilds the table twice a week to fix them, you are paying for the rework, not for the analytics. That is a &lt;a href="https://bixtech.ai/what-is-data-quality/?utm_source=devto&amp;amp;utm_medium=guestpost&amp;amp;utm_campaign=okr_backlinks_q3_2026" rel="noopener noreferrer"&gt;data quality&lt;/a&gt; problem, and it gets solved upstream, not in the DDL.&lt;/p&gt;

&lt;p&gt;The habit that has saved me the most is boring: dry run first, check &lt;code&gt;total_bytes_billed&lt;/code&gt; after, and never accept a savings number that nobody measured. Everything above is just the mechanism.&lt;/p&gt;

&lt;p&gt;If you want the follow up, tell me in the comments whether materialized views or slot reservations are the harder call on your side. &lt;/p&gt;

</description>
      <category>bigquery</category>
      <category>dataengineering</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
