Audit a Databricks environment
Use this guide for Azure, AWS, or GCP Databricks. The commands read metadata and system tables. They do not change the environment. Authentication is the only non-read action.
Audit report template#
Complete this report after the audit. Replace each italic placeholder with an audit result.
Environment: tenant / workspaces audited Date: YYYY-MM-DD Auditor: name Coverage: areas reviewed · environments reviewed · exclusions and reasons
Bottom line: 2–3 sentences — overall posture and the single most important thing to fix.
Scorecard (RAG by Well-Architected pillar)#
| Pillar | Status | Findings (H/M/L) | Headline issue |
|---|---|---|---|
| Operational excellence | 🟢/🟡/🔴 | 0/0/0 | e.g. 40% of jobs hand-created, not IaC |
| Security, privacy & compliance | 🟢/🟡/🔴 | 0/0/0 | e.g. app runs as shared SP, no per-user auth |
| Reliability | 🟢/🟡/🔴 | 0/0/0 | e.g. 6 prod jobs have no failure alert |
| Performance efficiency | 🟢/🟡/🔴 | 0/0/0 | e.g. 70% of DBUs still on classic compute |
| Cost optimization | 🟢/🟡/🔴 | 0/0/0 | e.g. 3 clusters with no auto-termination |
| Data & AI governance | 🟢/🟡/🔴 | 0/0/0 | e.g. hive_metastore still in active use |
| Interoperability & usability | 🟢/🟡/🔴 | 0/0/0 | e.g. apps lack error/empty states |
🟢 no material issues · 🟡 issues to address · 🔴 urgent / business risk
Top 5 findings (severity-ranked)#
| # | Severity | Pillar | Finding (resource ID) | Impact | Fix | Effort |
|---|---|---|---|---|---|---|
| 1 | High | Cost | job 12345 on all-purpose cluster | ~2–4× cost | move to job cluster | S |
Immediate and long-term actions#
- Quick wins (≤1 day): e.g. enable auto-termination; add failure webhooks; stop abandoned apps
- Strategic (needs a project): e.g. migrate hive_metastore → UC; adopt DABs for all resources
Coverage gaps and limits#
- Products not present: …
- Checks blocked by missing permissions or disabled system tables: …
Scope#
Use the audit to assess inventory, cost, jobs, pipelines, deployments, serverless compute, Model Serving, Vector Search, Apps, and Lakebase. Record each finding with evidence and a resource ID.
Well-Architected Lakehouse framework#
Every check below maps to a pillar of the official Databricks Well-Architected Lakehouse
Framework (learn.microsoft.com/azure/databricks/lakehouse-architecture/well-architected), so
findings are grounded in Databricks' own guidance rather than opinion. The seven pillars:
| Pillar | What the audit checks for it |
|---|---|
| Operational excellence | IaC/DABs coverage, drift, alerting, monitoring, capacity/quota limits |
| Security, privacy & compliance | auth, least-privilege grants, secret handling, network exposure |
| Reliability | failure rates, retries, timeouts, HA, sync freshness, health states |
| Performance efficiency | serverless adoption (the pillar's #1 principle), Photon, right-sizing |
| Cost optimization | idle/always-on compute, scale-to-zero, tagging/chargeback, tier choice |
| Data & AI governance | UC migration, model versioning, lineage, grants |
| Interoperability & usability | app UX states, discoverability (BROWSE) |
Prerequisites#
- [ ] Databricks CLI ≥ v0.292.0 (
databricks --version) - [ ] An authenticated profile (
databricks auth profilesshowsValid: YES) - [ ] Ideally account-admin read + workspace-admin read. Partial access still works — note gaps.
- [ ] Unity Catalog system tables enabled (
system.billing,system.compute,system.lakeflow). If disabled, the SQL steps fall back to slower per-resource CLI loops (noted inline).
Command conventions#
- Replace
$Pwith the profile under audit;$ACCTwith the account-console profile. - Audit each environment and the Databricks account.
- Names are literal. Never normalize hyphens to underscores. Backtick-quote any name part with a
hyphen in SQL:
`my-catalog`.schema.table. - ⚠️ Unity Catalog commands take POSITIONAL args, not flags.
schemas list <CATALOG>—schemas list --catalog-name Xdoes not exist and will fail. - Capture resource IDs with every finding so it stays actionable later.
Required permissions#
Databricks access spans four independent permission planes: account roles, workspace object
ACLs, Unity Catalog privileges, and system-table grants. This audit reads only metadata and
system tables — never business data — so SELECT on user tables is never required.
| Area | What you read | Least-privilege grant | Simplest blanket role |
|---|---|---|---|
| Authentication | your identity | workspace user (can log in) | — |
| Account inventory | workspaces, metastores, account users/groups | (no granular read role exists) | Account admin |
| Workspace resources | clusters, jobs, pipelines, warehouses, pools, policies, serving | CAN_VIEW per object |
Workspace admin (per workspace) |
| Unity Catalog metadata | catalogs, schemas, and tables | BROWSE on catalogs (no USE/SELECT needed) |
Metastore admin |
| Unity Catalog storage | external locations and storage credentials | BROWSE on each external location |
Metastore admin |
| Cost | system.billing, system.compute |
USE CATALOG on system + USE SCHEMA + SELECT on those schemas |
account admin and metastore admin |
| SQL queries | execute queries | CAN_USE on a SQL warehouse |
— |
| Job reliability | system.lakeflow and job configuration |
SELECT on system.lakeflow; CAN_VIEW per job |
Workspace admin |
| Pipeline reliability | pipeline configuration, events, and source | CAN_VIEW per pipeline; CAN_READ on source files |
Workspace admin |
System table requirements:
- An account admin must have enabled the relevant schemas (
system.billing,system.compute,system.lakeflow, andsystem.accessfor audit logs). These schemas are not all on by default. - The metastore must be on Unity Catalog Privilege Model v1.0, and you must query from a UC-enabled workspace.
- System tables only contain data for workspaces in the same cloud region. To audit a workspace in another region, run the queries from a workspace in that region.
Recommended "auditor" grant bundle (read-only, least privilege):
- Workspace admin (read) in each environment workspace — the practical way to
CAN_VIEWevery job, pipeline, cluster, and notebook at once. USE CATALOGonsystem+USE SCHEMA+SELECTonsystem.billing,system.compute,system.lakeflow, andsystem.access.CAN_USEon one SQL warehouse (to run the queries).BROWSEon all catalogs — or metastore admin if you also need external locations / storage credentials (A3).- Account admin — required only for the account inventory. If access is not available, record the gap and audit each workspace.
Grant to a group, not your user. Per UC best practice (and consistent with this audit's own findings), assign the bundle to an
auditorsgroup. Whoever grants it must themselves be both an account admin and a metastore admin.
Audit procedure#
Authenticate#
databricks auth login --host <WORKSPACE_URL> --profile $P
databricks current-user me --profile $P
Expected: OAuth flow completes; current-user me returns your identity JSON.
If it fails: configuration does not support OAuth tokens → re-run the login to convert a
PAT/azure-cli profile. On Azure with auth_type = azure-cli, run az login --tenant <TENANT_ID>
first — the Databricks profile borrows that identity.
Inventory#
Account resources#
databricks account workspaces list --profile $ACCT
databricks account metastores list --profile $ACCT
databricks account groups list --profile $ACCT
databricks account users list --profile $ACCT
Look for: more metastores than regions; users granted directly instead of via groups; orphaned
or empty workspaces; duplicate workspace configs.
If it fails: subcommand names vary by CLI build — confirm with databricks account --help. No
account-admin access → skip the account inventory and record the coverage gap.
Workspace resources#
databricks clusters list --profile $P
databricks warehouses list --profile $P
databricks instance-pools list --profile $P
databricks cluster-policies list --profile $P
databricks jobs list --profile $P
databricks pipelines list --profile $P
databricks serving-endpoints list --profile $P
Expected: one block per resource type; capture counts + IDs.
If it fails: databricks sql-warehouses list does not exist — use warehouses list.
Unity Catalog resources#
databricks catalogs list --profile $P
databricks schemas list <CATALOG> --profile $P
databricks tables list <CATALOG> <SCHEMA> --profile $P
databricks external-locations list --profile $P
databricks storage-credentials list --profile $P
Look for: a hive_metastore catalog still in active use (un-migrated legacy data — major
governance gap); external locations with broad access; storage credentials shared too widely.
Cost and capacity#
Prefer system tables — one query beats iterating the CLI over every resource. Get the warehouse first:
databricks experimental aitools tools get-default-warehouse --profile $P
DBU usage by SKU#
databricks experimental aitools tools query "SELECT sku_name, usage_unit, ROUND(SUM(usage_quantity),1) AS dbus FROM system.billing.usage WHERE usage_date >= DATE_SUB(CURRENT_DATE,30) GROUP BY 1,2 ORDER BY dbus DESC LIMIT 20" --profile $P
This query shows the primary sources of DBU usage. If it returns TABLE_OR_VIEW_NOT_FOUND, record
the missing billing schema and use the cluster configuration fallback.
Idle and always-on clusters#
databricks experimental aitools tools query "SELECT cluster_name, auto_termination_minutes, worker_node_type, num_workers FROM system.compute.clusters WHERE delete_time IS NULL ORDER BY auto_termination_minutes DESC" --profile $P
Red flag: auto_termination_minutes = 0 or NULL → burning DBUs 24/7. Top remediation target.
Cluster configuration fallback#
databricks clusters list --profile $P --output json
Scan for autotermination_minutes (0/missing), spark_version (outdated runtime = security + perf
debt), oversized node_type_id.
Active incident triage (first 15 minutes)#
Use this path before you restart a failed job or pipeline. Preserve evidence first.
- Record the UTC time, workspace, workload name, resource ID, and the person who reported it.
- Record the last successful run, the failed run ID, and the exact failure time.
- Capture the current job or pipeline configuration and note changes since the last success.
- Capture the exact error before you retry:
- Jobs:
databricks jobs list-runs --job-id <ID> --limit 10 --profile $P --output json. - Pipelines:databricks pipelines list-pipeline-events <PIPELINE_ID> --max-results 50 --profile $P --output json. - Check whether the failure affects one workload or a shared dependency, then continue with the job or pipeline sections. Record every command and result in the findings table.
Do not restart until you capture the failed run ID, error, and current configuration. A retry can replace the useful failure context.
Job reliability#
The job timeline contains hourly slices. The result_state value exists only on
the final slice. Count final slices as runs, and combine all slices before you
calculate a run duration.
Source: https://learn.microsoft.com/azure/databricks/admin/system-tables/jobs
Job failure rates#
databricks experimental aitools tools query "SELECT job_id, COUNT(*) AS runs, COUNT_IF(result_state <> 'SUCCEEDED') AS failures, ROUND(100.0 * COUNT_IF(result_state <> 'SUCCEEDED') / COUNT(*), 1) AS fail_pct FROM system.lakeflow.job_run_timeline WHERE period_start_time >= DATE_SUB(CURRENT_DATE, 30) AND result_state IS NOT NULL GROUP BY 1 HAVING failures > 0 ORDER BY fail_pct DESC, failures DESC LIMIT 30" --profile $P
Worst offenders by failure rate. Column names vary by region — if one errors, run
databricks experimental aitools tools discover-schema system.lakeflow.job_run_timeline --profile $P.
Job duration outliers#
databricks experimental aitools tools query "WITH runs AS (SELECT job_id, run_id, (UNIX_TIMESTAMP(MAX(period_end_time)) - UNIX_TIMESTAMP(MIN(period_start_time))) / 60.0 AS duration_min FROM system.lakeflow.job_run_timeline WHERE period_start_time >= DATE_SUB(CURRENT_DATE, 30) AND period_end_time IS NOT NULL GROUP BY job_id, run_id) SELECT job_id, ROUND(AVG(duration_min), 1) AS avg_min, ROUND(MAX(duration_min), 1) AS max_min, COUNT(*) AS runs FROM runs GROUP BY job_id ORDER BY max_min DESC LIMIT 20" --profile $P
Review condition: max_min ≫ avg_min can identify runs without a suitable timeout.
Job configuration review#
For each high-risk job from the previous queries:
databricks jobs get <JOB_ID> --profile $P --output json
Review each job against the following configuration checks:
| Check | Red flag in JSON | Effect |
|---|---|---|
| Compute type | task has existing_cluster_id (all-purpose) instead of new_cluster/serverless |
~2–4× cost; resource contention; slow on cold start |
| Cluster reuse | every task defines its own new_cluster instead of a shared job_cluster_key |
Repeated 3–5 min cluster spin-ups per task |
| Failure alerts | email_notifications / webhook_notifications on_failure empty |
Silent failures — nobody knows it broke |
| Retries | task max_retries = 0 on a networked task |
A transient error causes a full job failure |
| Timeout | no timeout_seconds at job or task level |
Hung run burns compute indefinitely (see C2) |
| Health rules | no health.rules (e.g. RUN_DURATION_SECONDS threshold) |
No early warning on slow drift |
| Schedule | schedule.pause_status = PAUSED unexpectedly, or wrong timezone_id |
Job silently not running / firing at the wrong hour |
| Trigger fit | continuous used where a schedule/trigger would do |
Always-on compute cost |
| Concurrency | max_concurrent_runs = 1 on a job that overlaps its own schedule |
Skipped/queued runs pile up |
| Permissions | CAN_MANAGE granted to individual user_name instead of group_name |
Unauditable access; breaks when people leave |
| Naming | name lacks an env prefix (e.g. [prod]) |
Cross-env confusion in multi-workspace setups |
Quick jq to surface the two most common offenders:
databricks jobs get <JOB_ID> --profile $P --output json | jq '{name, tasks: [.settings.tasks[] | {task_key, uses_all_purpose: (.existing_cluster_id != null), retries: .max_retries}], on_failure: .settings.email_notifications.on_failure}'
Recent run fallback#
databricks jobs list --profile $P
databricks jobs list-runs --job-id <ID> --limit 25 --profile $P
Pipeline reliability#
Pipeline inventory and health#
databricks pipelines list --profile $P
databricks pipelines get <PIPELINE_ID> --profile $P --output json
Score each pipeline against the table below:
| Check | Red flag | Effect |
|---|---|---|
| Run mode | continuous: true where triggered would suffice |
Always-on compute cost |
| Dev mode | development: true on a production pipeline |
Clusters do not auto-terminate between updates |
| Serverless | serverless: false while expecting incremental MV refresh |
MVs fall back to full recompute every run |
| Photon | photon: false |
Leaves significant performance on the table |
| Channel | channel: PREVIEW in prod |
Unpinned runtime behavior in production |
| Notifications | notifications empty |
Failed updates go unnoticed |
| Edition | edition: ADVANCED when expectations are not used, or the reverse |
Unused tier or missing data-quality control |
Pipeline failures#
databricks pipelines list-pipeline-events <PIPELINE_ID> --max-results 50 --profile $P
⚠️ Read the real cause from error.exceptions[0].message, NOT the top-level .message
(which only says "Update X is FAILED"):
databricks pipelines list-pipeline-events <PIPELINE_ID> --max-results 50 --profile $P --output json | jq -r '.events[] | select(.level=="ERROR") | .error.exceptions[0].message' | sort | uniq -c | sort -rn
Note: a pipeline stuck
INITIALIZINGon serverless is a normal cold start (a few minutes), not a failure. Do not report it as an error.
Pipeline source review#
Pull the pipeline's source notebooks/files and scan. Legacy DLT syntax is migration debt and a reliability/perf risk — flag every hit:
| Pattern (grep / rg) | Meaning | Migrate to |
|---|---|---|
import dlt |
Legacy DLT module | from pyspark import pipelines as dp |
@dlt.table, @dlt.view |
Legacy decorators | @dp.table, @dp.temporary_view |
dlt.apply_changes |
Legacy CDC | dp.create_auto_cdc_flow |
LIVE. prefix (SQL) |
Deprecated, errors in modern pipelines | bare name / STREAM(name) |
CREATE LIVE TABLE / CREATE STREAMING LIVE TABLE |
Legacy DDL | CREATE OR REFRESH MATERIALIZED VIEW / ... STREAMING TABLE |
CREATE OR REPLACE ... STREAMING TABLE |
Invalid in SDP | CREATE OR REFRESH ... |
UNION across streaming sources |
Anti-pattern | multiple Append Flows (@dp.append_flow) |
aggregation (GROUP BY/SUM) on a streaming table |
Streaming tables are append-only; aggregates do not recompute | Materialized view with a batch read |
no EXPECT / @dp.expect* anywhere |
No data-quality gate | add Expectations (warn/drop/fail) |
PARTITIONED BY + ZORDER |
Legacy layout | CLUSTER BY (Liquid Clustering) |
rg -n "import dlt|@dlt\.|dlt\.apply_changes|LIVE\.|CREATE LIVE TABLE|CREATE STREAMING LIVE TABLE|CREATE OR REPLACE (STREAMING TABLE|MATERIALIZED VIEW)" <pipeline_src_dir>
Shared reliability controls#
- Alerting coverage: cross-reference C3 (jobs) + D1 (pipelines) — what fraction of production workloads have any failure notification? A low number is usually the single biggest finding.
- Full-refresh exposure: note any pipeline/job that triggers a pipeline full refresh on a schedule — it reprocesses streaming sources from scratch and can cause data loss.
- Runtime/version drift: outdated
spark_version(jobs) andchannel: PREVIEW(pipelines) → security + reproducibility risk. - Tagging: are clusters/jobs/warehouses tagged (team/cost-center)? Untagged = no cost attribution.
Deployment and IaC controls#
Run from each bundle root (the directory containing
databricks.yml). The audit question: is this environment managed as code, or hand-clicked?
databricks bundle validate --strict --profile $P
databricks bundle validate --strict -t <TARGET> --profile $P
databricks bundle summary -t <TARGET> --profile $P
Compare the bundle resources with the live inventory to find manually created resources:
databricks bundle summary -t <TARGET> --profile $P # IaC-managed resources
databricks jobs list --profile $P # everything that exists
# anything in `jobs list` but absent from `bundle summary` was created by hand
| Red flag | Signal | Pillar impact |
|---|---|---|
Resource in UI but not in bundle summary |
exists in jobs/pipelines list, absent from summary |
No source of truth; lost on rebuild |
| Single target / shared profile across environments | one target:, development and production share workspace.profile |
A development deployment can change production |
Missing mode: production on prod target |
prod target lacks it (or runs development) |
Schedules paused/mangled; guardrails skipped |
No permissions: / grants: block |
resource YAML omits it | Ungoverned, unauditable ACLs |
| Secrets hardcoded | literal tokens in databricks.yml/app.yaml |
Secrets in source control |
| Drift after deploy | UI edits diverge from bundle summary |
Next deploy silently reverts fixes |
| Hardcoded catalog/schema/warehouse | literals instead of ${var...} |
The deployment cannot move across environments |
Command notes: There is no bundle list-targets command. Read targets from the
databricks.yml file. Confirm available commands with databricks <group> --help.
Serverless migration opportunities#
"Use serverless architectures" is the first principle of the Performance Efficiency pillar. This section identifies classic compute that is a serverless candidate.
databricks experimental aitools tools query "SELECT sku_name, usage_type, ROUND(SUM(usage_quantity),1) AS dbus FROM system.billing.usage WHERE usage_date >= DATE_SUB(CURRENT_DATE,90) GROUP BY 1,2 ORDER BY dbus DESC" --profile $P
Workloads whose usage_type is not SERVERLESS_COMPUTE are the candidate spend. Then per
job/pipeline, the migration signal is in the spec JSON:
databricks jobs get <JOB_ID> --profile $P --output json # job_clusters/new_cluster = classic; environments/environment_key = serverless
databricks pipelines get <PIPELINE_ID> --profile $P --output json
Candidates: all-purpose clusters backing notebook/Python work; jobs on classic job_clusters;
spark_jar_task on classic; outdated DBR (13.x/14.x); no scale-to-zero.
Compatibility limits: RDD/SparkContext APIs, %scala/%r cells,
custom Spark data-source JARs, unsupported spark.conf.set(...), DBFS mounts / Hive Metastore,
continuous-trigger streaming, ML libs not pre-installed on serverless.
Caveat: Confirm the system.compute.clusters columns in the target environment before you
use them.
Model Serving#
databricks serving-endpoints list --profile $P -o json
databricks serving-endpoints get <NAME> --profile $P -o json
databricks serving-endpoints get <NAME> --profile $P -o json | jq '.config.served_entities[] | {entity_name, entity_version, scale_to_zero_enabled, workload_size, min_provisioned_throughput, max_provisioned_throughput}'
databricks serving-endpoints get-permissions <ENDPOINT_ID> --profile $P # takes ID, not name
databricks serving-endpoints export-metrics <NAME> --profile $P # Prometheus metrics
| Red flag | Signal |
|---|---|
| Always-on compute | scale_to_zero_enabled: false on low-traffic/dev endpoint |
| Over-provisioned PT | min_provisioned_throughput ≈ max, set high |
| No rate limits | no AI Gateway rate-limit block |
| Stale model | entity_version pinned old while newer @prod exists |
| No monitoring | no inference tables; export-metrics empty |
| Bad traffic split | traffic_config not summing to 100% |
| Broad permissions | CAN_QUERY/CAN_MANAGE to users/account users |
| Broken deploy | state.ready != READY / config_update stuck |
Cost: system.billing.usage WHERE billing_origin_product = 'MODEL_SERVING'.
Caveat: The Databricks CLI has no command to list Unity Catalog model aliases. The endpoint
response supplies entity_name and entity_version for the served model.
Vector Search#
databricks vector-search-endpoints list-endpoints --profile $P
databricks vector-search-endpoints get-endpoint <ENDPOINT_NAME> --profile $P
databricks vector-search-indexes list-indexes <ENDPOINT_NAME> --profile $P
databricks vector-search-indexes get-index <CATALOG>.<SCHEMA>.<INDEX> --profile $P
| Red flag | Signal |
|---|---|
| Idle always-on endpoint | ONLINE with num_indexes low/0, no query activity |
| Stale/failed sync | status.ready=False, error in status.message, indexed_row_count ≪ source rows |
| Wrong refresh mode | pipeline_type: CONTINUOUS on a batch source |
| Embedding drift | self-managed embedding_vector_columns vs managed source |
| Wrong tier | STANDARD (~7× cost) where Storage-Optimized fits |
| Source lacks CDF | deletions and updates do not propagate (verify Delta properties) |
Health states to watch on get-endpoint: YELLOW_STATE, RED_STATE, OFFLINE, stuck PROVISIONING.
Caveat: The public documentation does not identify the VECTOR_SEARCH billing SKU or its
system table mappings. Confirm the value before you report cost numbers.
Databricks Apps#
databricks apps list --profile $P -o json # verify with --help
databricks apps get <APP_NAME> --profile $P -o json # app_status.state, url, compute, resources[], service_principal
databricks apps logs <APP_NAME> --profile $P # OAuth auth only — silently fails on PAT
| Red flag | Signal |
|---|---|
| No per-user auth | reads user data but runs as shared SP (no user_api_scopes/OBO) |
| Over-broad SP grants | resources[] show wide UC/warehouse/secret access |
| Plaintext secrets | env literals instead of valueFrom: secret |
| Always-on/oversized | Large (1.0 DBU/h) where Medium (0.5) suffices |
| Crash loop | app_status.state != RUNNING; logs show post-deploy PERMISSION_DENIED |
| Stale/abandoned | old RUNNING apps, no recent deploys; near 100-app/workspace cap |
Limits: 100 apps/workspace; 10-min start; 120s proxy timeout (504 not in app logs); ephemeral
FS — only stdout/stderr survive.
UX (from databricks-app-design): missing loading/empty/error states; Genie/AI surfaces that hide
the generated SQL, execution identity, or "verify" disclaimer.
Lakebase#
CLI group is
postgres(notlakebase); needs CLI ≥ v0.294.0. Discover first — the surface is narrow:databricks postgres -h.
databricks postgres list-branches projects/<PROJECT_ID> --profile $P
databricks postgres list-endpoints projects/<PROJECT_ID>/branches/<BRANCH_ID> --profile $P
databricks postgres list-databases projects/<PROJECT_ID>/branches/<BRANCH_ID> --profile $P
databricks postgres get-endpoint projects/<PROJECT_ID>/branches/<BRANCH_ID>/endpoints/<ENDPOINT_ID> --profile $P
databricks postgres get-synced-table "synced_tables/<CATALOG>.<SCHEMA>.<TABLE>" --profile $P
| Red flag | Signal |
|---|---|
| Over-provisioned | min_cu ≈ max_cu on low-traffic project |
| No scale-to-zero | always-on compute on dev/CI branches |
| Stale/failed sync | old refresh timestamp / failed pipeline in get-synced-table |
| No HA in prod | single endpoint, no secondaries |
| Public exposure | reachable without private networking; Data API on public schema, no RLS |
| Static credentials | committed postgresql://user:pass@... vs 1-hr OAuth; no sslmode=require |
| Too many branches | many no_expiry branches; near 10/project or 8 TB/branch; no PITR plan |
Command notes: There is no documented list-projects, get-project, or list-synced-tables
command — enumerate via the Python SDK (w.postgres), REST /database/, or Catalog Explorer UI.
Provisioned-era instances use the separate databricks database group. Never run
generate-database-credential during an audit — it mints a live DB token.
Cost: CU-hours (~2 GB RAM/CU) plus the synchronized table pipeline cost. Both appear in
system.billing.usage. Confirm the exact SKU in the target environment.
Review the results#
- [ ] One completed inventory block per environment + the account
- [ ] The spend and job failure queries returned data, or the fallback checks ran
- [ ] Each high-risk job and pipeline has a configuration review
- [ ] Product-specific checks include only products that exist in the environment
- [ ] Each finding tagged with its Well-Architected pillar (so the report rolls up by pillar)
- [ ] Findings captured with resource IDs
Findings table#
| # | Environment | Resource (ID) | Area | Severity | Finding | Recommended fix |
|---|---|---|---|---|---|---|
| 1 | prod | job 12345 | C3 | High | No on_failure alert + runs on all-purpose cluster |
Add webhook alert; move to job cluster |
Troubleshooting#
| Symptom | Likely cause | Fix |
|---|---|---|
| All commands fail with credentials error | Session expired | Authenticate the profile again |
TABLE_OR_VIEW_NOT_FOUND on system.* |
System tables not enabled | Use CLI fallbacks (B3/C4); flag as a finding |
schemas list rejects --catalog-name |
Positional-arg command | Use schemas list <CATALOG> |
| Parse error on a catalog/table name | Hyphen in name | Backtick-quote it |
Pipeline events .message is unhelpful |
Wrong field | Read error.exceptions[0].message |
Pipeline stuck INITIALIZING |
Serverless cold start | Wait — not a failure |
| PERMISSION_DENIED | Not admin in that workspace | Note the coverage gap; continue |