Snowflake Idle Warehouses: How AUTO_SUSPEND Tuning Cuts 20–30% Off Your Bill
A Snowflake warehouse doesn't bill you for the work it does. It bills you for the time it spends running — and "running" and "doing work" are not the same thing. A warehouse that executes a 4-second query and then sits powered-on for the next 10 minutes waiting for the next one is billing you for 10 minutes and 4 seconds of compute to deliver 4 seconds of value.
That gap is idle time, and it's Leak #1 in our breakdown of why your Snowflake bill is so high — the single most common reason a bill is higher than it should be. The cause is almost always one setting, left at its default, that almost nobody revisits: AUTO_SUSPEND.
This post shows you how to measure exactly how much of your spend is idle, using only SNOWFLAKE.ACCOUNT_USAGE, and how to tune AUTO_SUSPEND to eliminate it — including the one billing rule that makes "just set it as low as possible" a mistake.
Why idle time is the default state of a Snowflake warehouse
When you create a warehouse, AUTO_SUSPEND defaults to 600 seconds — ten minutes. That means after a query finishes, the warehouse keeps running, fully billable, for ten more minutes before it suspends itself. If another query arrives in that window, the clock resets.
For an interactive or BI workload, this is catastrophic for cost. Picture an analytics warehouse that someone queries every few minutes throughout the workday. Each query runs for a few seconds; the ten-minute timer keeps resetting before it ever expires. The warehouse effectively never suspends during business hours. You're paying for eight straight hours of compute to serve a few minutes of actual query execution.
Snowflake bills compute per-second, but with a 60-second minimum each time a warehouse starts. After that first minute it's pure per-second until the warehouse suspends. So the entire idle stretch between queries — every second the warehouse is powered on and doing nothing — lands on your bill at the full credit rate for that warehouse size. On a Large warehouse that's 8 credits per hour, running or idle.
The fix is to suspend the warehouse sooner. But to do it confidently, you first need to see how much idle you actually have.
Step 1: Measure idle time per warehouse
The clean way to measure idle is to compare how long each warehouse was running against how long it was actually executing queries. Running time comes from WAREHOUSE_EVENTS_HISTORY — every RESUME_CLUSTER paired with its following SUSPEND_CLUSTER is one running interval. Busy time comes from the sum of query execution time in QUERY_HISTORY. The difference is idle.
WITH events AS (
SELECT
warehouse_name,
cluster_number,
timestamp,
event_name,
LEAD(timestamp) OVER (PARTITION BY warehouse_name, cluster_number ORDER BY timestamp) AS next_ts,
LEAD(event_name) OVER (PARTITION BY warehouse_name, cluster_number ORDER BY timestamp) AS next_event
FROM snowflake.account_usage.warehouse_events_history
WHERE timestamp >= DATEADD('day', -30, CURRENT_DATE())
AND event_name IN ('RESUME_CLUSTER', 'SUSPEND_CLUSTER')
),
running AS (
SELECT
warehouse_name,
SUM(DATEDIFF('second', timestamp, next_ts)) AS running_seconds
FROM events
WHERE event_name = 'RESUME_CLUSTER'
AND next_event = 'SUSPEND_CLUSTER'
GROUP BY 1
),
busy AS (
SELECT
warehouse_name,
SUM(execution_time) / 1000 AS busy_seconds
FROM snowflake.account_usage.query_history
WHERE start_time >= DATEADD('day', -30, CURRENT_DATE())
AND warehouse_name IS NOT NULL
AND execution_time > 0
GROUP BY 1
)
SELECT
r.warehouse_name,
ROUND(r.running_seconds / 3600, 1) AS running_hours,
ROUND(COALESCE(b.busy_seconds, 0) / 3600, 1) AS busy_hours,
GREATEST(0, ROUND(100 * (1 - COALESCE(b.busy_seconds, 0) / NULLIF(r.running_seconds, 0)), 1)) AS idle_pct
FROM running r
LEFT JOIN busy b ON r.warehouse_name = b.warehouse_name
ORDER BY idle_pct DESC;Read it top-down. Any warehouse with a high idle_pct is running far longer than it's working — that's AUTO_SUSPEND waste, and the higher the running_hours, the more dollars are behind that percentage. A warehouse at 70% idle with hundreds of running hours is your first target.
One caveat to understand: because queries can run concurrently, summed execution time can occasionally exceed wall-clock running time on busy multi-cluster warehouses, which is why the query floors idle_pct at zero with GREATEST. Idle measurement is most meaningful on single-cluster, interactive warehouses — which is exactly where AUTO_SUSPEND waste tends to hide.
Step 2: Audit your current AUTO_SUSPEND settings
Now find out what each warehouse is actually set to. AUTO_SUSPEND isn't exposed in an ACCOUNT_USAGE view, so use SHOW WAREHOUSES and scan the result. Anything still at 300 seconds or higher is almost certainly leaving money on the table.
SHOW WAREHOUSES;
SELECT
"name" AS warehouse_name,
"size" AS warehouse_size,
"auto_suspend" AS auto_suspend_seconds,
"auto_resume" AS auto_resume
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
WHERE "auto_suspend" >= 300
OR "auto_resume" = 'false'
ORDER BY "auto_suspend" DESC;The auto_suspend column is in seconds — a value of 600 is the untouched default. Cross-reference this list against the high-idle warehouses from Step 1: a warehouse that's both high-idle and set to 600 seconds is a guaranteed win. Also flag any warehouse where auto_resume is false, since that combination can leave a warehouse suspended and queries failing to start, which sometimes leads people to disable auto-suspend entirely — the opposite of what you want.
Step 3: Put a dollar figure on the idle waste
To make the case for change, translate idle percentage into credits. This query joins each warehouse's 30-day credit consumption to its idle percentage, so you can see — and dollarize — roughly how much you're spending on compute that did nothing.
WITH events AS (
SELECT
warehouse_name,
cluster_number,
timestamp,
event_name,
LEAD(timestamp) OVER (PARTITION BY warehouse_name, cluster_number ORDER BY timestamp) AS next_ts,
LEAD(event_name) OVER (PARTITION BY warehouse_name, cluster_number ORDER BY timestamp) AS next_event
FROM snowflake.account_usage.warehouse_events_history
WHERE timestamp >= DATEADD('day', -30, CURRENT_DATE())
AND event_name IN ('RESUME_CLUSTER', 'SUSPEND_CLUSTER')
),
running AS (
SELECT warehouse_name, SUM(DATEDIFF('second', timestamp, next_ts)) AS running_seconds
FROM events
WHERE event_name = 'RESUME_CLUSTER' AND next_event = 'SUSPEND_CLUSTER'
GROUP BY 1
),
busy AS (
SELECT warehouse_name, SUM(execution_time) / 1000 AS busy_seconds
FROM snowflake.account_usage.query_history
WHERE start_time >= DATEADD('day', -30, CURRENT_DATE())
AND warehouse_name IS NOT NULL AND execution_time > 0
GROUP BY 1
),
spend AS (
SELECT warehouse_name, SUM(credits_used) AS credits_30d
FROM snowflake.account_usage.warehouse_metering_history
WHERE start_time >= DATEADD('day', -30, CURRENT_DATE())
GROUP BY 1
)
SELECT
s.warehouse_name,
s.credits_30d,
GREATEST(0, ROUND(100 * (1 - COALESCE(b.busy_seconds, 0) / NULLIF(r.running_seconds, 0)), 1)) AS idle_pct,
ROUND(s.credits_30d * GREATEST(0, 1 - COALESCE(b.busy_seconds, 0) / NULLIF(r.running_seconds, 0)), 1) AS estimated_idle_credits,
ROUND(s.credits_30d * GREATEST(0, 1 - COALESCE(b.busy_seconds, 0) / NULLIF(r.running_seconds, 0)) * 3.00, 2) AS estimated_idle_dollars
FROM spend s
LEFT JOIN running r ON s.warehouse_name = r.warehouse_name
LEFT JOIN busy b ON s.warehouse_name = b.warehouse_name
ORDER BY estimated_idle_credits DESC;Replace 3.00 with your effective credit price. The estimated_idle_dollars column is the headline number — the monthly spend attributable to warehouses sitting idle. It's usually large enough to make the fix an easy decision. Want to model the after-state across your whole account? Our Snowflake cost calculator lets you plug in scenarios.
The fix: tune AUTO_SUSPEND (and the rule that stops you overdoing it)
The change is one statement per warehouse, effective immediately and fully reversible:
ALTER WAREHOUSE analytics_wh SET AUTO_SUSPEND = 60;
ALTER WAREHOUSE analytics_wh SET AUTO_RESUME = TRUE;Sixty seconds is the right default for most interactive, BI, and ad-hoc warehouses. It suspends the warehouse a minute after the last query instead of ten minutes, and AUTO_RESUME = TRUE means the next query wakes it automatically with no manual intervention. For warehouses serving a steady stream of short queries, this single change routinely cuts compute 20–30%.
Here's the rule that keeps you from going too far: Snowflake bills a 60-second minimum every time a warehouse resumes. A warehouse that runs 30 seconds is billed for 60. One that runs 61 seconds, suspends, then resumes and runs 5 seconds is billed 60 + 1 + 60 = 121 seconds. So if you set AUTO_SUSPEND very low — say 5 or 10 seconds — on a warehouse that gets sporadic queries a minute or two apart, you can cause it to suspend and resume repeatedly, and each resume re-triggers that 60-second minimum. You can end up paying more than you would have by leaving it running. This is why "set it to 1 second" is wrong, and 60 seconds is the safe floor for most workloads.
There's a second tradeoff to weigh: the local data cache. When a warehouse suspends, it loses its warm cache, and the next query has to re-read from storage, which can be slightly slower. For most workloads the cost savings dwarf the occasional cache miss. But for a latency-sensitive dashboard that users hammer all day, you might choose a slightly longer suspend — 120 to 300 seconds — to keep the cache warm during active hours. Tune to the workload rather than applying one number everywhere.
Sensible starting points by workload type:
| Workload | Suggested AUTO_SUSPEND | Why |
|---|---|---|
| Interactive / ad-hoc / BI | 60 seconds | Short, bursty queries; idle is the main cost |
| ETL / batch transforms | 60–120 seconds | Back-to-back jobs; avoid suspend between steps |
| Latency-sensitive dashboards | 120–300 seconds | Keep cache warm during active hours |
| Dev / test / sandbox | 60 seconds | Infrequent use; suspend fast |
Where teams get AUTO_SUSPEND wrong
A few traps that quietly undo the savings.
Disabling auto-suspend "for performance." Someone gets tired of a cold-cache delay, sets AUTO_SUSPEND to a huge value or disables suspension, and the warehouse runs 24/7. This is the single most expensive misconfiguration in Snowflake. If a warehouse genuinely needs to stay warm, scope that to active hours with a resource monitor or a scheduled task — don't leave it running overnight and on weekends.
Setting it below 60 seconds. As covered above, the 60-second minimum-billing rule means ultra-aggressive suspend can increase cost through resume thrashing. Sixty seconds is the floor for almost everything.
Heartbeat queries that prevent suspension. Monitoring tools, BI connection-keepalives, and dashboard auto-refreshes can fire a tiny query every minute, resetting the suspend timer forever. The warehouse never sleeps. Check QUERY_HISTORY for suspiciously regular, trivial queries on a warehouse that won't suspend — that's usually the culprit.
Tasks and continuous pipelines holding a warehouse open. A scheduled task running every minute on a dedicated warehouse keeps it effectively always-on. For frequent, lightweight tasks, consider Snowflake's serverless compute instead of a dedicated warehouse, so you pay only for actual execution.
Forgetting that right-sizing and auto-suspend compound. A warehouse that's both oversized and idle is being overcharged twice. Fixing idle alone helps, but pair it with warehouse right-sizing — the smaller the warehouse, the cheaper every remaining idle second is, so the two fixes multiply rather than add.
Bottom line
Idle time is the default state of a Snowflake warehouse, and the default AUTO_SUSPEND of 600 seconds guarantees you pay for it. Measure it with the idle-percentage query, audit your current settings with SHOW WAREHOUSES, dollarize the waste, then drop AUTO_SUSPEND to 60 seconds for interactive workloads — staying at or above 60 to avoid resume-thrash billing. It's a one-line, fully reversible change that typically cuts compute 20–30% with no impact on anyone's queries. Combine it with right-sizing and you're attacking the two largest line items on the bill at once.
If you'd rather have it done for you — every warehouse's idle measured, every AUTO_SUSPEND value tuned to its workload, the savings calculated up front — that's exactly what a free Snowflake cost assessment delivers. You get a ranked, dollarized action list and decide what to apply.
---
This is part of our Snowflake cost-optimization series. Start with the pillar: [Why Is Your Snowflake Bill So High? The 4 Biggest Leaks](https://insignyx.com/blog/dc3a9a17-0010-40fe-87b7-71f413dd8e0d), read the companion piece on [warehouse right-sizing](https://insignyx.com/blog/snowflake-warehouse-right-sizing), or explore our [Snowflake cost optimization services](https://insignyx.com/services/snowflake-cost-optimization).
Follow Insignyx on LinkedIn
More on data, cloud & AI cost optimization.