What you will be able to do
- Explain what Databricks system tables are, how access to them is granted, and what they cost to query
- Attribute DBU usage in system.billing.usage to jobs, pipelines, warehouses and identities using usage_metadata, identity_metadata and billing_origin_product
- Write usage aggregations that stay correct when Databricks issues RETRACTION and RESTATEMENT records, and convert DBUs to list cost
- Cross-reference billing records with system.compute tables to attribute cost to clusters, owners and node roles
Key concept
System tables — A read-only, Databricks-hosted set of tables in the system catalog that records your account's operational data: billing, compute, audit and job activity. Unity Catalog governs them and you query them with SQL, so cost analysis, auditing and workload monitoring all come down to joining these tables.
1.What system tables are and who can read them
Cost questions, security questions and job-health questions in Databricks usually have the same answer: query the system catalog. System tables are a Databricks-hosted analytical store that the documentation says you can use to monitor costs, audit security events, track compute and job performance, and observe data and AI workloads. Each schema has its own job. system.billing covers cost, system.compute covers cluster and warehouse configuration and utilization, system.access covers audit events, and system.lakeflow covers jobs and pipelines.
Unity Catalog governs access. Your account needs at least one Unity Catalog-enabled workspace, and the tables can only be read from one. They still contain data from every workspace in the account, including workspaces that don't use Unity Catalog. They are read-only. Users who hold both the account admin and metastore admin roles can read them by default. Everyone else needs three grants from an admin: USE CATALOG on system, plus USE SCHEMA and SELECT on the schemas they need.
Scope also depends on region. Most tables hold data for the workspaces in your metastore's region. system.billing.usage and system.billing.list_prices are global, so you can see account-wide spend from any region.
Checkpoint 1 of 5· Check yourself
A data engineer who is neither an account admin nor a metastore admin needs to query system.billing.usage. Which set of privileges must an admin grant?
System tables are Unity Catalog objects, so a reader needs catalog, schema and SELECT privileges. Because the tables are read-only, write privileges such as MODIFY don't apply.
“USE CATALOG on the system catalog, USE SCHEMA on the system schemas, and SELECT on the system schemas.”Source: docs.databricks.com
Sources1
2.Attributing spend with system.billing.usage
system.billing.usage has one row for each piece of billable usage. A row records a quantity of units (usually DBUs) for a SKU over a time window, plus metadata showing what generated it. Cost analysis means grouping these rows along one of the columns below.
| Column | What it tells you |
|---|---|
| usage_date | Date of the record; use it for faster aggregation by date |
| sku_name | The SKU billed, e.g. STANDARD_ALL_PURPOSE_COMPUTE |
| usage_quantity / usage_unit | How many units (e.g. DBU) were consumed |
| usage_metadata | IDs of the resources involved: cluster_id, job_id, job_run_id, warehouse_id, dlt_pipeline_id, notebook_id |
| identity_metadata | run_as, owned_by (SQL warehouses), created_by (Apps, agents) |
| billing_origin_product | The product that originated the usage, e.g. JOBS, DLT, SQL, ALL_PURPOSE |
| product_features | Feature details such as is_serverless, is_photon, jobs_tier, dlt_tier |
| custom_tags | Your custom tags, e.g. an env tag |
| record_type | ORIGINAL, RETRACTION or RESTATEMENT |
Each record populates only some of the usage_metadata fields, depending on the compute type. The difference that matters most is that job_id and job_run_id are filled in for serverless jobs and for jobs on job compute, but not for jobs that run on all-purpose compute. Usage from a job running on all-purpose compute has a cluster_id and no job_id, so a cost-per-job report built on job_id will miss it. In the same way, dlt_pipeline_id and dlt_update_id identify Lakeflow pipeline usage, including features built on pipelines such as materialized views and Lakeflow Connect.
SKUs alone can mislead. Several products bill under the same SKU: data quality monitoring, predictive optimization and serverless workflows all bill under the serverless jobs SKU. To tell them apart, group by billing_origin_product and product_features instead of sku_name.
Checkpoint 2 of 5· Match them up
Match each billing_origin_product value to the usage it represents
Tap a term, then the definition that fits it.
billing_origin_product names the originating product even when several products share a SKU. That makes it the column to group by when you break down cost by workload type.
“the billing_origin_product and product_features columns provide more insight into the specific product and features associated with the usage”Source: docs.databricks.com
Checkpoint 3 of 5· Exam question
A FinOps engineer must attribute each dollar of Databricks Jobs compute to the specific job run that generated it, joining `system.billing.usage` with `system.lakeflow.job_run_timeline`. The engineer starts from this skeleton: ```sql SELECT u.usage_quantity, u.sku_name, rt.job_id, rt.run_id FROM system.billing.usage u JOIN system.lakeflow.job_run_timeline rt ON <JOIN_CONDITION> ``` Which `<JOIN_CONDITION>` correctly attributes each usage record to the job run that produced it?
Correct answer: A — `u.workspace_id = rt.workspace_id AND u.usage_metadata.job_id = rt.job_id AND u.usage_metadata.job_run_id = rt.run_id`, aligning billing records to the run's own workspace and identifiers.
- A. This condition matches on workspace, then narrows using the job and run identifiers that `usage_metadata` carries for Jobs compute, so usage rows are tied to the exact run that generated them rather than any run of the same job. This mirrors Databricks' documented pattern for attributing billing usage to Lakeflow job runs.
- B. `usage_metadata.cluster_id` identifies the compute resource, not the job that scheduled it, so equating it to a job identifier produces a join that almost never matches and silently drops most usage rows from the report.
- C. `sku_name` is a pricing category string like a warehouse or all-purpose compute tier, not a job identifier; comparing it to `job_id` cannot uniquely identify a run and would incorrectly fan out usage across every job sharing that SKU.
- D. `identity_metadata.run_as` records who or what triggered the workload, which is useful for auditing but is not a job or run identifier, so this join fails to isolate usage for a specific job run and can match unrelated runs triggered by the same principal.
Sources2
3.Corrections, record availability and turning DBUs into cost
system.billing.usage never edits rows. When a field is wrong, Databricks appends two records. The first is a RETRACTION, identical to the original except that usage_quantity is negated. The second is a RESTATEMENT with the corrected values. If the original record should never have existed, only a retraction is added. A correct total therefore has to sum across all record types. Filtering to ORIGINAL alone double-counts nothing but keeps the wrong values.
Timing matters for dashboards as well. Records typically arrive within 12 hours, but that applies only to ORIGINAL records. Retractions and restatements can arrive later, so recent totals can still change.
SELECT
usage_metadata.job_id, usage_start_time, usage_end_time,
SUM(usage_quantity) as usage_quantity
FROM system.billing.usage
GROUP BY ALL
HAVING usage_quantity != 0Checkpoint 4 of 5· Fill the gap
Which aggregate makes this query return corrected usage even after retractions and restatements?
SELECT
usage_metadata.job_id, usage_start_time, usage_end_time,
? (usage_quantity) as usage_quantity
FROM system.billing.usage
GROUP BY ALL
HAVING usage_quantity != 0A retraction carries the negative of the original quantity. Summing cancels the original and leaves only the restatement's value. MAX, AVG and COUNT don't cancel anything.
Source: docs.databricks.comDBUs aren't money. To get list cost, join system.billing.usage to system.billing.list_prices, which keeps a historical log of SKU pricing with a new row each time a price changes. Databricks' job-cost example joins on cloud and sku_name and requires usage_start_time to fall inside the price's price_start_time/price_end_time window. A null price_end_time marks the current price. The example then multiplies usage_quantity by pricing.default to get list_cost, and filters billing_origin_product = 'JOBS' to keep only job spend.
list_prices gets a new row every time a SKU price changes, so a SKU can have several rows. The time-window condition picks the price that applied when the usage happened. Without it, each usage row would match every historical price and the cost would be multiplied.
4.Attributing cost to clusters, owners and nodes with system.compute
For classic compute, a billing row gives you a cluster_id and a node_type, but node_type doesn't say whether the node was a driver or a worker. If the driver and workers use the same instance type, their usage is combined into one record. If they use different types, the usage appears as separate records. To work out which role cost what, join usage_metadata.cluster_id to the compute tables. system.compute.clusters records each cluster's driver_node_type and worker_node_type, and system.compute.node_timeline has a driver boolean for every node.
Identity has a similar subtlety. On all-purpose compute, identity_metadata.run_as is the identity that created the compute resource, which isn't necessarily its owner. To charge usage back to owners, join to system.compute.clusters.owned_by.
| Table | What it records |
|---|---|
| system.compute.clusters | Slow-changing dimension table with the full history of compute configurations for any cluster, including driver_node_type, worker_node_type, owned_by |
| system.compute.node_timeline | Utilization metrics of all-purpose and jobs compute: driver, cpu_user_percent, cpu_wait_percent, mem_used_percent (90-day free retention) |
| system.compute.warehouses | Full history of configurations for any SQL warehouse |
| system.compute.warehouse_events | SQL warehouse events such as starting, stopping, scaling up and down |
node_timeline is also where cost meets efficiency. Its CPU and memory percentages show whether a costly cluster was actually busy. A high cpu_wait_percent means the CPU spent its time waiting for I/O. Low mem_used_percent on a large node type suggests the cluster is oversized.
Checkpoint 5 of 5· Check yourself
You build a chargeback report that groups all-purpose compute usage by identity_metadata.run_as. A manager complains that the costs aren't landing on the cluster owners. What fixes the report?
On all-purpose compute, run_as is the creator rather than the owner. In identity_metadata, owned_by applies only to SQL warehouses. The owner comes from system.compute.clusters.owned_by.
“To attribute usage to the owner, join to system.compute.clusters.owned_by using usage_metadata.cluster_id.”Source: docs.databricks.com
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.usage_metadata.job_id is populated for every job, so grouping by job_id captures all job spend.Why is that wrong?
job_id and job_run_id are populated only for serverless jobs and jobs on job compute. Jobs on all-purpose compute have only a cluster_id.
Covered in Attributing spend with system.billing.usage
2.Every billing record, corrections included, arrives within 12 hours, so yesterday's totals are final.Why is that wrong?
The 12-hour availability applies only to ORIGINAL records. Retractions and restatements can arrive later and change totals that were already summed.
Covered in Corrections, record availability and turning DBUs into cost
3.identity_metadata.run_as on all-purpose compute identifies the cluster owner.Why is that wrong?
On all-purpose compute, run_as is the identity that created the compute resource. For the owner, join to system.compute.clusters.owned_by.
Covered in Attributing cost to clusters, owners and nodes with system.compute
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Use them to monitor costs, audit security events, track compute and job performance, and observe data and AI workloads.”
↩︎ What system tables are and who can read them“System tables include data from all workspaces in your account, but they can only be accessed from a Unity Catalog-enabled workspace.”
↩︎ What system tables are and who can read them“Users with both the account admin and metastore admin roles have access to system tables by default.”
↩︎ What system tables are and who can read them“System tables are read-only and cannot be modified.”
↩︎ What system tables are and who can read them“A historical log of SKU pricing. A record gets added each time there is a change to a SKU price.”
↩︎ Corrections, record availability and turning DBUs into cost“Captures the utilization metrics of your all-purpose and jobs compute resources.”
↩︎ Attributing cost to clusters, owners and nodes with system.compute“System tables provide a Databricks-hosted analytical store of your account's operational data.”
↩︎ Key concept“System tables are free to use. You are only charged for the compute used to query them.”
↩︎ Prediction“USE CATALOG on the system catalog, USE SCHEMA on the system schemas, and SELECT on the system schemas.”
↩︎ Checkpoint - 2.
“Serverless jobs and jobs run on job compute (does not populate for jobs run on all-purpose compute)”
↩︎ Attributing spend with system.billing.usage“data quality monitoring, predictive optimization, and serverless workflows are all billed under the same serverless jobs SKU.”
↩︎ Attributing spend with system.billing.usage“A retraction record negates the original incorrect record, then a restatement record includes the corrected information.”
↩︎ Corrections, record availability and turning DBUs into cost“Records are typically available within 12 hours.”
↩︎ Corrections, record availability and turning DBUs into cost“To attribute usage to a specific node role, you can cross-reference usage_metadata.cluster_id in the usage table with data from the compute system tables.”
↩︎ Attributing cost to clusters, owners and nodes with system.compute“Serverless jobs and jobs run on job compute (does not populate for jobs run on all-purpose compute)”
↩︎ Exam trap 1“This availability applies only to records where record_type is ORIGINAL.”
↩︎ Exam trap 2“The identity that created the compute resource. This is not necessarily the compute owner.”
↩︎ Exam trap 3“the billing_origin_product and product_features columns provide more insight into the specific product and features associated with the usage”
↩︎ Checkpoint“To attribute usage to the owner, join to system.compute.clusters.owned_by using usage_metadata.cluster_id.”
↩︎ Checkpoint - 3.
“t1.usage_quantity * list_prices.pricing.default AS list_cost”
↩︎ Corrections, record availability and turning DBUs into cost - 4.
“Percentage of time the CPU spent waiting for I/O.”
↩︎ Attributing cost to clusters, owners and nodes with system.compute