Snowflake and Databricks FinOps fails when it is run as a monthly cost review. Both platforms bill by consumption, so cost, performance and reliability are the same signal read three ways: a warehouse that queues is slow and expensive, a job that retries five times is unreliable and burns five times the DBUs. This post describes the seven controls we put in place on every Snowflake and Databricks estate we operate, with the system views, the SQL and the alert thresholds that make each one measurable.
Snowflake and Databricks FinOps needs no proprietary tooling to start. Everything below runs against documented telemetry: Snowflake SNOWFLAKE.ACCOUNT_USAGE views and Databricks Unity Catalog system tables. Nothing depends on a paid observability product, although every query here can feed one. Figures quoted as thresholds are the starting values we use; treat them as illustrative and calibrate against your own workload.
Why Snowflake and Databricks FinOps Is One Control Loop, Not Three Dashboards
On a fixed-capacity database, a bad plan costs latency. On a consumption-billed platform it costs money at the same moment, because the warehouse or cluster stays up longer to execute it. That coupling is the whole argument for treating FinOps, performance engineering and reliability as one discipline with one owner per workload.
The failure pattern we see most often is organisational rather than technical: finance owns the invoice, platform engineering owns the warehouses, and data engineering owns the pipelines. Each has a dashboard; none has a threshold that pages someone. Snowflake and Databricks FinOps only works when every credit and every DBU maps to a cost centre, every cost centre has a budget, and every budget has an alert with a human on the other end.
The seven Snowflake and Databricks FinOps controls below are ordered the way we implement them. Attribution comes first because nothing else can be enforced without it.
The Telemetry Surface Behind Snowflake and Databricks FinOps
Snowflake side: SNOWFLAKE.ACCOUNT_USAGE
Snowflake exposes spend in WAREHOUSE_METERING_HISTORY (credits per warehouse per hour), METERING_DAILY_HISTORY (all service types, including serverless and cloud services) and QUERY_ATTRIBUTION_HISTORY, which attributes warehouse credits to individual queries. Performance lives in QUERY_HISTORY (spill, pruning, queue and compilation columns per query) and WAREHOUSE_LOAD_HISTORY (running and queued load in five-minute buckets).
QUERY_HISTORY lags up to 45 minutes and WAREHOUSE_METERING_HISTORY up to 3 hours. Use INFORMATION_SCHEMA table functions for near-real-time checks and ACCOUNT_USAGE for trends and alerts that tolerate the lag. Never alert on a window that is inside the documented latency.Databricks side: Unity Catalog system tables
Databricks exposes billing in system.billing.usage (DBUs per SKU per workspace per hour, with cluster, warehouse and job identifiers) and system.billing.list_prices. Query-level performance is in system.query.history for SQL warehouses, warehouse configuration in system.compute.warehouses, and job execution in system.lakeflow.job_run_timeline and system.lakeflow.job_task_run_timeline. System tables must be enabled per schema by an account admin; availability of individual schemas varies by cloud and release, so confirm against your account before building on one.
Control 1: Attribute Every Credit and DBU Before You Optimize Anything
Attribution is the precondition for every other Snowflake and Databricks FinOps control. On Snowflake we require a query tag on every session opened by a service principal, a warehouse per team where the workload justifies it, and object tags on warehouses for the cost centre. On Databricks we require custom tags on clusters, SQL warehouses and job clusters, enforced through cluster policies so that an untagged cluster cannot start.
The Snowflake query below joins per-query attributed credits to the query tag and reports the top cost centres for the last seven days. Because QUERY_ATTRIBUTION_HISTORY excludes idle time, the sum will be lower than WAREHOUSE_METERING_HISTORY; the gap is your idle cost and is measured separately in Control 2.
-- Snowflake: attributed credits by query tag, last 7 days
SELECT
COALESCE(NULLIF(q.query_tag, ''), 'UNTAGGED') AS cost_centre,
COUNT(*) AS queries,
ROUND(SUM(a.credits_attributed_compute), 2) AS credits_compute,
ROUND(SUM(a.credits_attributed_compute)
/ NULLIF(COUNT(*), 0), 4) AS credits_per_query
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_ATTRIBUTION_HISTORY a
JOIN SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY q
ON q.query_id = a.query_id
WHERE a.start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
GROUP BY 1
ORDER BY credits_compute DESC;The Databricks side of Snowflake and Databricks FinOps attribution reads system.billing.usage, joins the SKU list price and groups by a custom tag. Prices are per SKU and change over time, so the join is on the price validity window rather than a single current price.
-- Databricks: DBUs and list-price cost by cost-centre tag, last 7 days
SELECT
COALESCE(u.custom_tags['cost_centre'], 'UNTAGGED') AS cost_centre,
u.sku_name,
ROUND(SUM(u.usage_quantity), 2) AS dbus,
ROUND(SUM(u.usage_quantity * p.pricing.default), 2) AS list_price_usd
FROM system.billing.usage u
JOIN system.billing.list_prices p
ON p.sku_name = u.sku_name
AND p.cloud = u.cloud
AND u.usage_start_time >= p.price_start_time
AND (p.price_end_time IS NULL OR u.usage_start_time < p.price_end_time)
WHERE u.usage_date >= CURRENT_DATE() - INTERVAL 7 DAYS
GROUP BY 1, 2
ORDER BY list_price_usd DESC;The Snowflake and Databricks FinOps alert for this control is simple: the UNTAGGED row must be below a fixed share of total spend. We start at 5% and tighten it once the tagging policy has been live for a quarter. Anything above that share means the rest of the programme is reporting on a partial picture.
Control 2: Measure Idle Time and Suspend It in Snowflake and Databricks FinOps
Idle compute is the largest avoidable line on most invoices we audit, and the first Snowflake and Databricks FinOps win on almost every estate. On Snowflake, a warehouse bills for a minimum of 60 seconds each time it resumes and then per second while running, whether or not a query is executing. The difference between metered credits and attributed credits is a direct measurement of that idle cost per warehouse.
-- Snowflake: idle share per warehouse = metered credits minus attributed credits
WITH metered AS (
SELECT warehouse_name,
SUM(credits_used_compute) AS credits_metered
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
GROUP BY 1
),
attributed AS (
SELECT warehouse_name,
SUM(credits_attributed_compute) AS credits_attributed
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_ATTRIBUTION_HISTORY
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
GROUP BY 1
)
SELECT m.warehouse_name,
ROUND(m.credits_metered, 2) AS credits_metered,
ROUND(COALESCE(a.credits_attributed, 0), 2) AS credits_attributed,
ROUND(1 - COALESCE(a.credits_attributed, 0)
/ NULLIF(m.credits_metered, 0), 3) AS idle_share
FROM metered m
LEFT JOIN attributed a USING (warehouse_name)
ORDER BY idle_share DESC;Snowflake and Databricks FinOps thresholds differ by workload class. For a warehouse serving interactive BI, an idle share of 20–30% is normal because auto-suspend must stay long enough to keep the local cache warm between clicks. For a warehouse serving scheduled ELT, anything above 10% means AUTO_SUSPEND is too long or the schedule leaves gaps that resume the warehouse for a single short statement.
-- Snowflake: tighten auto-suspend on an ELT warehouse (seconds); takes effect immediately
ALTER WAREHOUSE wh_elt_prod
SET AUTO_SUSPEND = 60
AUTO_RESUME = TRUE;On the Databricks side of Snowflake and Databricks FinOps, the analogue is the SQL warehouse auto-stop and the all-purpose cluster auto-termination. Serverless SQL warehouses start fast enough that an auto-stop of 5–10 minutes is safe for BI; classic and pro warehouses need longer because of the cold-start penalty. Job clusters should never idle: they exist for the run and terminate with it. The query below reports warehouses whose auto-stop is above 15 minutes so that the exception list is explicit.
-- Databricks: SQL warehouses with a long auto-stop setting (current configuration)
SELECT warehouse_name,
warehouse_type,
warehouse_size,
auto_stop_minutes,
min_clusters,
max_clusters
FROM system.compute.warehouses
WHERE delete_time IS NULL
AND (auto_stop_minutes = 0 OR auto_stop_minutes > 15)
ORDER BY auto_stop_minutes DESC;Control 3: Right-Size Warehouses From Queueing, Not From Wall-Clock
Wall-clock latency alone cannot tell you whether to scale up or scale out, which is why Snowflake and Databricks FinOps sizing starts from load telemetry. On Snowflake the discriminator is WAREHOUSE_LOAD_HISTORY: avg_queued_load above zero for sustained periods means concurrency is the constraint and a multi-cluster warehouse (or a second warehouse for a different workload class) is the fix. avg_running_load consistently below about 0.3 on a warehouse that still bills full hours means the size can step down. Remote spill on individual queries, covered in Control 4, is the signal to step size up.
-- Snowflake: hours with queueing per warehouse, last 7 days (5-minute buckets)
SELECT warehouse_name,
DATE_TRUNC('hour', start_time) AS hour,
ROUND(AVG(avg_running), 2) AS avg_running_load,
ROUND(AVG(avg_queued_load), 2) AS avg_queued_load,
ROUND(MAX(avg_queued_load), 2) AS max_queued_load
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_LOAD_HISTORY
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
GROUP BY 1, 2
HAVING AVG(avg_queued_load) > 0.5
ORDER BY avg_queued_load DESC;On Databricks SQL warehouses the same Snowflake and Databricks FinOps decision reads from system.query.history: waiting_for_compute_duration_ms and waiting_at_capacity_duration_ms separate cold-start waits (fix with a higher minimum cluster count or serverless) from capacity waits (fix with a higher maximum cluster count). Execution time that dominates with no waiting points to the query itself, not the warehouse.
-- Databricks: where does query time go per warehouse, last 7 days
SELECT compute.warehouse_id AS warehouse_id,
COUNT(*) AS queries,
ROUND(PERCENTILE(total_duration_ms, 0.95) / 1000, 1) AS p95_total_s,
ROUND(AVG(waiting_for_compute_duration_ms) / 1000, 1) AS avg_wait_compute_s,
ROUND(AVG(waiting_at_capacity_duration_ms) / 1000, 1) AS avg_wait_capacity_s,
ROUND(AVG(execution_duration_ms) / 1000, 1) AS avg_execution_s
FROM system.query.history
WHERE start_time >= CURRENT_TIMESTAMP() - INTERVAL 7 DAYS
AND compute.warehouse_id IS NOT NULL
GROUP BY 1
ORDER BY avg_wait_capacity_s DESC;The rule that keeps this from becoming a guessing game: change one dimension per cycle, wait for a full business cycle of telemetry, and verify that queue time fell without credits per query rising. Snowflake and Databricks FinOps programmes that resize weekly on intuition end up with warehouses that are both larger and slower than they started.
Control 4: Spill and Poor Pruning, the Two Silent Snowflake and Databricks FinOps Multipliers
In Snowflake and Databricks FinOps work, a query that spills to remote storage on Snowflake is paying for compute while it waits on object storage; the fix is almost always the query or the clustering, not a bigger warehouse. QUERY_HISTORY reports bytes_spilled_to_local_storage, bytes_spilled_to_remote_storage, and the pruning ratio via partitions_scanned against partitions_total.
-- Snowflake: heaviest spilling and worst-pruning query patterns, last 7 days
SELECT query_parameterized_hash,
ANY_VALUE(warehouse_name) AS warehouse_name,
COUNT(*) AS executions,
ROUND(SUM(bytes_spilled_to_remote_storage) / POWER(1024, 3), 1) AS gb_spilled_remote,
ROUND(AVG(partitions_scanned / NULLIF(partitions_total, 0)), 3) AS avg_scan_ratio,
ROUND(AVG(total_elapsed_time) / 1000, 1) AS avg_elapsed_s
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
AND execution_status = 'SUCCESS'
AND warehouse_size IS NOT NULL
GROUP BY 1
HAVING SUM(bytes_spilled_to_remote_storage) > 0
OR AVG(partitions_scanned / NULLIF(partitions_total, 0)) > 0.8
ORDER BY gb_spilled_remote DESC, avg_scan_ratio DESC
LIMIT 25;Grouping by query_parameterized_hash matters for Snowflake and Databricks FinOps: a dashboard tile that runs 4,000 times a day with a small spill costs more than one analyst query with a large one. A scan ratio near 1.0 on a large table means the filter predicate does not align with the micro-partition clustering; the remedy is a clustering key on the filter column, or a rewrite so the predicate is sargable, before any resize.
On Databricks the equivalent Snowflake and Databricks FinOps signals live in the query profile (spill bytes, files pruned versus files read) and, for Delta tables, in table history and DESCRIBE DETAIL. Liquid clustering on the columns that dominate filters and joins is the structural fix; Photon changes the execution cost but not the I/O volume, so pruning must be fixed first. The statement below is the safe, incremental form: it sets clustering for future writes and lets OPTIMIZE apply it.
-- Databricks: cluster a Delta table on its dominant filter columns (Databricks Runtime 15.2+)
-- Test on a copy first; OPTIMIZE rewrites files and consumes DBUs proportional to table size.
ALTER TABLE gold.fact_orders
CLUSTER BY (order_date, customer_region);
OPTIMIZE gold.fact_orders;
-- Verify: fewer files read for the representative predicate
EXPLAIN
SELECT SUM(net_amount)
FROM gold.fact_orders
WHERE order_date >= DATE '2026-09-01'
AND customer_region = 'EMEA';Control 5: Reliability SLOs That Show Up in Snowflake and Databricks FinOps Numbers
Reliability in Snowflake and Databricks FinOps terms is mostly pipeline reliability. Four SLOs cover nearly every estate we operate: freshness of each gold table, success rate per task or job, p95 run duration, and cost per successful run. The last one is what makes a retry storm visible as spend rather than as a footnote in a job log.
Snowflake tasks report to TASK_HISTORY, the Snowflake and Databricks FinOps source for scheduled-work reliability; the query below computes success rate and p95 duration per task over 30 days. Serverless tasks also appear in SERVERLESS_TASK_HISTORY with their own credits, which is where cost per run comes from.
-- Snowflake: task success rate and p95 duration, rolling 30 days
SELECT database_name,
schema_name,
name AS task_name,
COUNT(*) AS runs,
ROUND(SUM(IFF(state = 'SUCCEEDED', 1, 0)) / COUNT(*), 4) AS success_rate,
ROUND(APPROX_PERCENTILE(
TIMESTAMPDIFF('second', query_start_time, completed_time), 0.95), 0)
AS p95_duration_s,
MAX(completed_time) AS last_completed
FROM SNOWFLAKE.ACCOUNT_USAGE.TASK_HISTORY
WHERE scheduled_time >= DATEADD('day', -30, CURRENT_TIMESTAMP())
AND state IN ('SUCCEEDED', 'FAILED', 'CANCELLED')
GROUP BY 1, 2, 3
ORDER BY success_rate ASC, p95_duration_s DESC;Databricks jobs report to system.lakeflow.job_run_timeline, the matching Snowflake and Databricks FinOps source on the lakehouse side. Joining it to system.billing.usage on the job identifier in usage_metadata yields DBUs per run and therefore cost per successful run, which is the metric we put on the platform owner's monthly review.
-- Databricks: success rate and DBUs per successful run, rolling 30 days
WITH runs AS (
SELECT job_id,
run_id,
MAX(result_state) AS result_state,
MIN(period_start_time) AS started,
MAX(period_end_time) AS ended
FROM system.lakeflow.job_run_timeline
WHERE period_start_time >= CURRENT_TIMESTAMP() - INTERVAL 30 DAYS
GROUP BY 1, 2
),
cost AS (
SELECT usage_metadata.job_id AS job_id,
usage_metadata.job_run_id AS run_id,
SUM(usage_quantity) AS dbus
FROM system.billing.usage
WHERE usage_start_time >= CURRENT_TIMESTAMP() - INTERVAL 30 DAYS
AND usage_metadata.job_id IS NOT NULL
GROUP BY 1, 2
)
SELECT r.job_id,
COUNT(*) AS runs,
ROUND(SUM(CASE WHEN r.result_state = 'SUCCEEDED' THEN 1 ELSE 0 END)
/ COUNT(*), 4) AS success_rate,
ROUND(PERCENTILE(UNIX_TIMESTAMP(r.ended) - UNIX_TIMESTAMP(r.started), 0.95), 0)
AS p95_duration_s,
ROUND(SUM(c.dbus), 2) AS dbus_total,
ROUND(SUM(c.dbus)
/ NULLIF(SUM(CASE WHEN r.result_state = 'SUCCEEDED' THEN 1 ELSE 0 END), 0), 3)
AS dbus_per_success
FROM runs r
LEFT JOIN cost c USING (job_id, run_id)
GROUP BY 1
ORDER BY dbus_per_success DESC;The SLO targets themselves are business decisions, not platform defaults, and Snowflake and Databricks FinOps does not set them. What the platform team owns is the error budget: when a pipeline has burned its monthly budget of failed or late runs, new feature work on that pipeline stops until the cause is fixed. That rule, enforced, does more for reliability than any amount of retry configuration.
Control 6: Snowflake and Databricks FinOps Alerting That Pages a Person, Not a Channel
Both platforms can alert from SQL without an external scheduler, which keeps Snowflake and Databricks FinOps alerting inside the platform boundary. Snowflake alerts run a condition on a schedule and execute an action when rows are returned; Databricks SQL alerts evaluate a saved query on a schedule and notify a destination. The rule we apply to both: an alert either pages an owner or it is deleted. Alerts that post into a channel nobody watches are cost without control.
The Snowflake Snowflake and Databricks FinOps alert below fires when any cost centre exceeds its daily credit budget, read from a small budget table the finance owner maintains. It runs hourly on an XS warehouse; alerts can also run serverless if you prefer not to dedicate a warehouse.
-- Snowflake: budget table maintained by finance
CREATE TABLE IF NOT EXISTS finops.ctl.cost_centre_budget (
cost_centre VARCHAR NOT NULL,
daily_credit_limit NUMBER(10,2) NOT NULL,
owner_email VARCHAR NOT NULL,
CONSTRAINT pk_cost_centre_budget PRIMARY KEY (cost_centre)
);
-- Snowflake: alert when yesterday's attributed credits exceed the budget
CREATE OR REPLACE ALERT finops.ctl.alert_daily_budget
WAREHOUSE = wh_finops_xs
SCHEDULE = '60 MINUTE'
IF (EXISTS (
SELECT 1
FROM (
SELECT COALESCE(NULLIF(q.query_tag, ''), 'UNTAGGED') AS cost_centre,
SUM(a.credits_attributed_compute) AS credits
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_ATTRIBUTION_HISTORY a
JOIN SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY q ON q.query_id = a.query_id
WHERE a.start_time >= DATEADD('day', -1, CURRENT_TIMESTAMP())
GROUP BY 1
) s
JOIN finops.ctl.cost_centre_budget b USING (cost_centre)
WHERE s.credits > b.daily_credit_limit
))
THEN
CALL SYSTEM$SEND_EMAIL(
'finops_email_integration',
'finops-oncall@example.com',
'Snowflake daily credit budget exceeded',
'One or more cost centres exceeded their daily credit limit. Query alert_daily_budget history for detail.'
);
ALTER ALERT finops.ctl.alert_daily_budget RESUME;Snowflake also ships resource monitors and budgets as first-class Snowflake and Databricks FinOps objects. A resource monitor is the hard guardrail: it can suspend warehouses at a credit quota. We attach one per warehouse group with notification at 75% and 90%, and suspension at 100% only for non-production groups, because an automatic suspension of a production warehouse is itself an incident.
-- Snowflake: resource monitor as a hard guardrail on a non-production warehouse group
CREATE OR REPLACE RESOURCE MONITOR rm_nonprod_monthly
WITH CREDIT_QUOTA = 500
FREQUENCY = MONTHLY
START_TIMESTAMP = IMMEDIATELY
TRIGGERS ON 75 PERCENT DO NOTIFY
ON 90 PERCENT DO NOTIFY
ON 100 PERCENT DO SUSPEND;
ALTER WAREHOUSE wh_dev_xs SET RESOURCE_MONITOR = rm_nonprod_monthly;On Databricks, Snowflake and Databricks FinOps budget policies attach tags to serverless usage for attribution, and account-level budgets send notifications when spend crosses a threshold; neither stops compute, so the hard guardrail is the cluster policy that caps node counts, instance types and auto-termination. For query-level alerting, a SQL alert on system.query.history catches the regression before the invoice does. The payload below creates one through the SQL Alerts API and can be committed to version control with the rest of the platform configuration.
{
"display_name": "SQL warehouse capacity wait above 30s (p95, 1h)",
"query_id": "<saved_query_id>",
"condition": {
"op": "GREATER_THAN",
"operand": { "column": { "name": "p95_wait_capacity_s" } },
"threshold": { "value": { "double_value": 30 } }
},
"schedule": { "quartz_cron_schedule": "0 0 * * * ?", "timezone_id": "UTC" },
"notify_on_ok": true,
"custom_subject": "Databricks SQL warehouse queueing regression",
"custom_body": "p95 capacity wait exceeded 30 seconds in the last hour. Check max_clusters and running query mix."
}Control 7: Guardrails That Make Snowflake and Databricks FinOps Self-Enforcing
The first six Snowflake and Databricks FinOps controls detect. The seventh prevents. Guardrails are the settings that stop a well-meaning engineer from creating a 4X-Large warehouse with no auto-suspend or a 200-node all-purpose cluster with no termination. They belong in code, reviewed like any other infrastructure change.
| Guardrail | Snowflake | Databricks | Why it matters |
|---|---|---|---|
| Mandatory attribution | Object tags on warehouses; session QUERY_TAG set by every service account | Cluster policy requiring custom_tags.cost_centre | Control 1 cannot report on untagged spend |
| Idle ceiling | AUTO_SUSPEND maximum per warehouse class | auto_stop_minutes and autotermination_minutes maxima in policy | Control 2 becomes a policy check, not a weekly hunt |
| Size ceiling | Grant CREATE WAREHOUSE only to a provisioning role; sizes above Large need review | Policy allowlist for node types and max_workers | Stops scale-up as the default response to slowness |
| Hard spend stop | Resource monitor with SUSPEND on non-production | Cluster policy caps; budgets notify only | Bounds the blast radius of a runaway job |
| Query timeout | STATEMENT_TIMEOUT_IN_SECONDS per warehouse | Warehouse statement timeout; job timeout_seconds | A hung query on a per-second meter is pure cost |
Every Snowflake and Databricks FinOps guardrail carries a rollback: the previous parameter value recorded in the change, and a named exception process for the workload that legitimately needs more. Guardrails without an exception path get disabled quietly, which is worse than never having them.
Snowflake Versus Databricks: Where Snowflake and Databricks FinOps Differs in Practice
The Snowflake and Databricks FinOps controls are the same on both platforms; the levers differ. Snowflake separates compute into warehouses with a fixed size ladder, so sizing decisions are coarse and per-warehouse, and the platform's own objects (alerts, resource monitors, budgets) do most of the enforcement. Databricks offers more compute shapes (serverless SQL, classic and pro warehouses, job clusters, all-purpose clusters), so attribution and policy are more work but the optimisation surface is wider.
| Dimension | Snowflake | Databricks |
|---|---|---|
| Billing unit | Credits per warehouse-second (60 s minimum on resume), plus serverless and cloud services | DBUs per SKU per second, priced by SKU and cloud |
| Spend telemetry | ACCOUNT_USAGE metering and attribution views, up to 3 h latency | system.billing.usage, typically hourly granularity |
| Query telemetry | QUERY_HISTORY with spill, pruning, queue columns | system.query.history for SQL warehouses; query profile for detail |
| Scale-out lever | Multi-cluster warehouses (Enterprise edition and above) | max_clusters on SQL warehouses; autoscaling on clusters |
| Data layout lever | Clustering keys and automatic clustering (billed serverless) | Liquid clustering, OPTIMIZE, Photon |
| Hard stop | Resource monitor SUSPEND | Cluster policy caps; budgets are notify-only |
| Native alerting | CREATE ALERT, budgets, notification integrations | SQL alerts, budget policies, account budgets |
Neither platform is the cheaper one in general, and Snowflake and Databricks FinOps is not a platform comparison. In our engagements the estate that costs less is the one where attribution reached 95% first, because that is the estate where every other control had data to act on.
Where Snowflake and Databricks FinOps Controls Do Not Apply, and What We Did Not Measure
- Snowflake and Databricks FinOps thresholds in this post (5% untagged, 0.5 queued load, 30 s capacity wait, 15-minute auto-stop) are the starting values we use; they are illustrative, not benchmarks, and must be calibrated against your workload.
- System table and view availability changes by release and by cloud. Confirm each view or table named here against your account's current documentation before building an alert on it; column names in Databricks system tables in particular have changed between previews.
- We have not published throughput or latency measurements here because the platforms, regions and workloads vary too widely for a single number to be honest. Every claim above is a mechanism you can verify with the query beside it.
- Enterprise-edition features on Snowflake (multi-cluster warehouses, some governance objects) and serverless availability on Databricks depend on edition, cloud and region.
- As with every change we make to a production data platform: test in a non-production account first, record the current value before altering a parameter, and keep a documented rollback and a tested restore posture.
Put Snowflake and Databricks FinOps Under One Owner
MinervaDB runs Snowflake and Databricks estates for enterprises that want cost, performance and reliability measured from the same telemetry and reviewed on the same cadence. Our Database FinOps practice implements the seven controls in this post, reports every saving against the actual invoice, and hands back the queries, alerts and policies as versioned code. Talk to a principal architect about where your estate stands on attribution today.
Book a FinOps AssessmentReferences
- Snowflake documentation: ACCOUNT_USAGE schema, including view latency notes
- Snowflake documentation: Alerts and notifications
- Snowflake documentation: Resource monitors
- Databricks documentation: System tables reference
- Databricks documentation: Billable usage system table
- Databricks documentation: Liquid clustering for Delta tables
- FinOps Foundation: FinOps Framework
Running this in production?
MinervaDB provides Databricks Consulting, Data Analytics Platform Engineering, Snowflake Consulting and Data Warehousing Support with 24x7 coverage and a 15-minute S1 response. Talk to an engineer.