Your warehouse bill doubled and nobody changed the data volume
Warehouse spend almost never grows because of storage. It grows because a schedule, a cache or a refresh strategy changed quietly, and nobody connected the two.
The conversation usually arrives in the same shape. Finance has flagged the warehouse line in the monthly review, the number is roughly double what it was a quarter ago, and the head of data has a meeting booked to explain it. Nobody onboarded a new source system. Nobody backfilled ten years of history. The tables are about the same size they were. And the bill went up anyway.
This is not a mystery, but it is genuinely hard to see from the invoice, because the invoice bills you in credits or DBUs and your team thinks in models, dashboards and schedules. The bill is denominated in one unit and the causes live in another. Nothing in the vendor console translates between them for you, so people reach for the only lever they recognise, which is warehouse size, and they either shrink it and break their SLAs or leave it alone and keep paying.
The useful thing to know before you open anything is that storage is almost never the story. Compressed columnar storage on object storage is cheap enough that a company with a serious storage bill usually has a specific and obvious reason for it, like unbounded time-travel retention or a table cloned nightly for three years. Compute is the story. Compute grows in three ways: things run more often, things run longer, or things that used to be avoided are no longer being avoided.
Start with the query history, not with the models
Every serious warehouse keeps a query history with, at minimum, a timestamp, a user or role, a warehouse, an elapsed time, and some measure of bytes or partitions scanned. That table is the only honest account of where the money went. Everything else is a theory.
Group it by day and by role first, not by query. You are looking for the week the shape changed. Costs rarely creep. They step. A step on a Tuesday three weeks ago is a deployment, a schedule change, a new dashboard or a config flag. A gradual slope across months is usually genuine growth and needs a different conversation entirely, one about unit economics rather than waste.
Once you have the week, group by role or service account within it. A handful of consumers usually dominates: the service account running model builds, the BI tool's service account, a reverse-ETL or activation account, and ad-hoc human queries. Which of those moved tells you which of the sections below you are actually reading.
The four things that are usually actually happening
- A schedule got tighter Someone asked for fresher numbers and a job moved from hourly to every fifteen minutes. That is four times the compute for the same data, agreed in a Slack thread by someone who did not know the multiplier. This is the most common cause and the easiest to reverse.
- Caching stopped working A BI tool that was serving most dashboard loads from cache starts missing, because a default filter changed, or a dataset was switched to live query, or somebody added a current-timestamp predicate that makes every query textually unique. Result caches match on exact query text. One dynamic timestamp defeats them completely.
- An incremental model became a full refresh Either explicitly, because a schema change forced a rebuild and the flag was never turned back off, or silently, because the incremental predicate stopped matching rows and the model quietly began rebuilding. The dbt run still succeeds. The tests still pass. The runtime climbs and nothing anywhere warns you.
- Auto-suspend got loosened Somebody was irritated by cold-start latency and pushed the idle timeout from a minute to fifteen. On a warehouse hit sporadically through the working day, you are now paying for the gaps between queries as well as the queries.
Costs rarely creep. They step. Find the week the shape changed and you have usually found the change.
The pattern, stated plainly
An order of investigation that does not waste a day
- 01Daily cost by warehouse and by roleFind the step. Note the date. If there is no step, stop here and go and have the growth conversation instead, because you do not have a waste problem.
- 02Diff what shipped that weekGit log on the modelling repo, the orchestration config and the BI tool's own change history across the same date range. Cross-reference them. The commit is usually sitting right there, and it is usually small.
- 03Rank models by total compute, not by runtimeA model that takes ninety seconds and runs every fifteen minutes costs more than one that takes twenty minutes once, overnight. Multiply duration by frequency before you rank, or you will spend the afternoon optimising the wrong model.
- 04Check the incrementals are still incrementalFor each model at the top of that ranking, compare rows written per run against rows in the target table. If the ratio is close to one, it is doing a full rebuild whatever the config file says.
- 05Check what the BI tool does on a single dashboard loadOne dashboard that fires a stack of independent queries when it opens, from a tool whose cache is missing, loaded by a good part of the company every morning, is a real line item. Look at query counts per dashboard, not only at cost per query.
The saving is usually deletion, not tuning
Here is the uncomfortable part, and the reason this is not a list of query optimisation tips. Working through this with a team, the largest single line is often work that produces something nobody looks at. A dashboard built for a quarterly review two years ago that still refreshes hourly. A mart built for a team that has since been reorganised out of existence. A chain of aggregate tables feeding an export that lands in an inbox nobody opens.
Tuning that work makes it cheaper. Deleting it makes it free. The reason teams tune instead of deleting is not technical. Deleting requires somebody to say out loud that a thing they built is not being used, and to carry the risk of being wrong about that. Tuning carries no such social cost. So estates quietly accumulate efficiently executed pointless work, and everyone involved has behaved reasonably at every step.
The mechanical part is easy. Most BI tools log dashboard views. Join view counts against refresh schedules and the compute cost of the models underneath, and you get a list ordered by cost per view. The top of that list is usually absurd. The hard part is the fortnight of asking people whether they still need things, and then the discipline to switch off the ones where the answer is silence.
A softer route when deletion is politically impossible
If you cannot get permission to delete, get permission to slow down. Move the suspect assets from hourly to daily, or daily to weekly, and announce that you have done it. If the cadence drops and nobody complains within a month, you have your evidence, and the conversation about switching it off entirely becomes much easier because nobody is being asked to take a risk any more. If somebody does complain on the first morning, you have learned something useful and it cost you almost nothing.
Where this approach is the wrong one
If the bill is genuinely small in the context of the business, this is not worth a fortnight of senior engineering time. A team of three spending two weeks to recover a saving that small has spent more than it saved, and has spent it out of the most constrained budget in the company. Cost work earns its place when the number is large enough that the recovered spend funds something specific, or when the growth rate rather than the absolute number is what worries you.
Equally, if the real problem is structural, that every mart rebuilds from raw because there is no intermediate layer and no incremental strategy anywhere, then a cost investigation will keep telling you the same thing in slightly different words. That is a rebuild, not an audit, and it should be scoped and funded as one. Running the investigation first is still worth it, because it produces the evidence you will need to justify the rebuild to the people holding the budget.
The end state to aim for is not a lower bill next month. It is that the next time somebody tightens a schedule, changes a refresh strategy or ships a model that quietly full-refreshes, the cost of that decision is visible to the person making it within a day, in the same review where the rest of the change is discussed. Cost stops being a quarterly surprise at the point it becomes an ordinary property of a change, like runtime, or test coverage, or who owns it.