DatabricksZero to Advanced

Know that production runs are healthy, and fix them fast when they aren't. You build a small run log, freshness checks and a runbook, the things every pipeline needs.

  • Free Edition has no account console, so budgets and account-level system tables may be unavailable. Where that matters, topics say so
  • Practice needs dbx_course.orders (Module 4) and a place for logs:
Python
from pyspark.sql import functions as F

catalog = spark.sql("SELECT current_catalog()").first()[0]
spark.sql(f"USE SCHEMA {catalog}.dbx_course")
spark.sql("""
  CREATE TABLE IF NOT EXISTS ops_run_log (
    run_ts TIMESTAMP, job STRING, step STRING, status STRING,
    rows_in BIGINT, rows_out BIGINT, rows_bad BIGINT, message STRING)
""")
16.01Concept

Production Pipeline Architecture

By the end: draw the layers of a production pipeline and where monitoring plugs in.

SourcesIngestTransformServeConsumers
Each stage reports run status, row counts, quality and freshness.
Layer What to watch
Orchestration (jobs) Run status, duration, retries
Data Row counts, quality rules, freshness
Compute Failures, memory, queueing
Cost Usage per job and team
Access Who read or changed what
  • A production pipeline = code + schedule + alerts + logs + runbook
  • Design for failure: every step is idempotent, restartable, and reports what it did
  • Separate data problems (bad input) from system problems (cluster, network, bug)
  • Decide who gets paged for each

Remember

A pipeline nobody monitors is a pipeline that is broken and nobody knows yet.

16.02Hands-on on Free Edition

Job Monitoring and Alerts

By the end: alert on job failures, slow runs and missing runs.

On the job (Module 10.9): add email notifications for failure, and a duration warning with an expected duration.

Alert on Why
Failure Something is broken
Duration over expected Slower: data growth, skew, contention
No run happened A missing run fails silently (check freshness, topic 7)
Success with 0 rows Technically fine, actually broken
  • Alert on the last retry, not every retry ("mute until last retry")
  • A good alert says what, where, how bad, and a link to the run
  • Route to a channel or group, not a person
  • Too many alerts = ignored alerts. Tune them

Alerts on data with SQL alerts: a query on a schedule + a condition:

SQL
%sql
SELECT count(*) AS stale_hours
FROM (SELECT timestampdiff(HOUR, max(order_ts), current_timestamp()) AS h FROM orders)
WHERE h > 24
  • A SQL alert runs the query on a schedule and notifies when the condition is met. It can also run as a job task, so later tasks can branch on the result

Free Edition

Email notifications work. Slack, Teams and webhook destinations need an admin-created destination.

16.03Concept

System Tables

By the end: use system tables to monitor jobs, usage and access across the account.

Table Tells you
system.lakeflow.jobs, job_tasks Job and task definitions (history)
system.lakeflow.job_run_timeline Every run: start, end, result_state, termination_code
system.lakeflow.job_task_run_timeline Same, per task
system.billing.usage Usage and cost by job, product and tag
system.access.audit Who did what
system.query.history SQL queries
SQL
%sql
-- failed runs in the last 7 days
SELECT job_id, run_id, result_state, termination_code, period_start_time
FROM system.lakeflow.job_run_timeline
WHERE period_start_time >= current_date() - INTERVAL 7 DAYS
  AND result_state IN ('FAILED', 'ERROR', 'TIMED_OUT')
ORDER BY period_start_time DESC
  • Read-only, free to query (you pay for the compute that runs the query)
  • Retention is typically 365 days per table
  • You need USE and SELECT on the system schemas. By default, only account and metastore admins
  • Timeline tables hold one row per period of a run: take the latest per run_id

Free Edition

Whether system.* is available to you isn't confirmed. Try SHOW SCHEMAS IN system. If it fails, topic 4 gives you the same view from your own tables.

16.04Hands-on on Free Edition

Pipeline Observability

By the end: write a run log and read pipeline metrics.

Python
def log_run(job, step, status, rows_in=None, rows_out=None, rows_bad=None, message=""):
    (spark.createDataFrame(
        [(job, step, status, rows_in, rows_out, rows_bad, message)],
        "job STRING, step STRING, status STRING, rows_in LONG, rows_out LONG, rows_bad LONG, message STRING")
     .withColumn("run_ts", F.current_timestamp())
     .select("run_ts", "job", "step", "status", "rows_in", "rows_out", "rows_bad", "message")
     .write.mode("append").saveAsTable("ops_run_log"))

src = spark.table("orders")
n_in = src.count()
good = src.filter("customer_id IS NOT NULL AND quantity > 0")
n_out = good.count()
log_run("orders_daily", "clean", "SUCCESS", n_in, n_out, n_in - n_out)
SQL
%sql
SELECT * FROM ops_run_log ORDER BY run_ts DESC LIMIT 10
Log Records
Run log (this table) Row counts, status, one row per step
Event log (pipelines) Flow progress and quality (Module 9.12)
Streaming progress lastProgress per micro-batch (Module 11)
System tables Platform-wide runs, cost, access
  • Record in, out and rejected rows per step, plus a run id ({{job.run_id}})
  • Build a dashboard on the run log: counts over time reveal drift
  • Pipeline observability = counts + quality + freshness + lineage (lineage: Module 6.8)
16.05Hands-on on Free Edition

Logging and Error Classification

By the end: log properly and classify errors.

Python
import logging

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
log = logging.getLogger("orders_daily")

class DataQualityError(Exception): ...
class SourceUnavailableError(Exception): ...

def step_clean():
    try:
        df = spark.table("orders")
        bad = df.filter("quantity <= 0").count()
        if bad:
            raise DataQualityError(f"{bad} rows with non-positive quantity")
        log.info("clean ok: %d rows", df.count())
    except DataQualityError as e:
        log.error("data problem: %s", e)
        log_run("orders_daily", "clean", "DATA_ERROR", message=str(e))
        raise
Class Example Action
Transient Network blip, quota, busy compute Retry
Data Bad values, schema change Quarantine or stop, notify the data owner
Code Bug, wrong column Fix, redeploy
Config / access Missing permission, bad secret Ops fixes
  • Use log levels: INFO for progress, WARNING for oddities, ERROR for failures
  • Include identifiers (job, run id, table, date): not passwords or personal data
  • Raise a specific error and let the job fail. Never swallow exceptions
  • Job logs and notebook output are on the run page; print and logging both appear
16.06Hands-on on Free Edition

Data Quality Monitoring

By the end: monitor data quality over time, not only at load time.

Python
checks = spark.sql("""
  SELECT current_timestamp() AS checked_at, 'orders' AS table_name,
         count(*) AS row_count,
         count(*) FILTER (WHERE customer_id IS NULL) AS null_customer,
         count(*) FILTER (WHERE quantity IS NULL)    AS null_quantity,
         count(DISTINCT order_id)                    AS distinct_orders
  FROM orders
""")
checks.write.mode("append").saveAsTable("ops_dq_history")
display(spark.table("ops_dq_history").orderBy("checked_at"))
Metric Spot
Row count per load Sudden drop or spike
Null rate per column A source stopped sending a field
Distinct keys / duplicates Double loads
Value distribution A code that changed
  • Compare today with the recent normal, not only with a fixed number
  • Anomaly detection (Data quality monitoring) can learn each table's usual freshness and completeness and flag outliers; data profiling keeps statistics and drift over time
  • Those services run on serverless compute and are billed: check availability and cost before enabling

Alert on change, not only on limits

A 40% drop in row count is a problem even if 10,000 rows is still "over the minimum".

16.07Hands-on on Free Edition

SLA, SLO, and Freshness Monitoring

By the end: define SLAs, SLOs and freshness checks.

Term Meaning Example
SLI What you measure Hours since the last load
SLO Your target Revenue table fresher than 6 hours, 99% of days
SLA A promise to consumers Dashboard data ready by 08:00
SQL
%sql
SELECT 'orders' AS table_name,
       max(order_ts) AS last_event,
       timestampdiff(HOUR, max(order_ts), current_timestamp()) AS hours_stale,
       timestampdiff(HOUR, max(order_ts), current_timestamp()) <= 24 AS within_slo
FROM orders
  • Freshness: max(event_time) or the table's last commit (DESCRIBE HISTORY)
  • Completeness: expected row count or expected partitions
  • Set an error budget: the number of misses you accept per month. Over budget = fix reliability before adding features
  • Put the SLO in the job description and in the alert
  • Our practice data is static, so within_slo is false. Live data would pass
16.08Hands-on on Free Edition

Troubleshooting Failed Jobs

By the end: troubleshoot a failed job step by step.

AlertFailed taskRead errorReproduceFix
Start at the symptom and work inwards.
Check Where
Which task failed? Run graph / matrix view
The actual error Task output, stack trace (read from the bottom)
What changed? Recent commits, schema or data changes, new volume
Is it data? Query the input rows for the failing key
Is it compute? Queue time, memory messages, quota
Rerun safely Repair run (Module 10.12)
  • Reproduce in a notebook with the same parameters
  • Read the last lines of a Python traceback first, then the line in your code
  • Common causes: a schema change, null in a new place, a permission change, a changed file name, a dependency version change
  • Write down the cause and fix. Add a test or a check so it can't recur (Module 14)

Fix the class of problem

The fix isn't done until a test or check exists that would have caught it.

16.09Concept

Incident Management

By the end: handle an incident from detection to review.

DetectTriageseverityMitigatestop the damageFixReview
Stabilise first, find the root cause second.
Step Do
Detect An alert, or a consumer complaint (then fix the missing alert)
Triage Who is affected? How bad? Name an incident owner
Mitigate Pause downstream jobs, mark data as delayed, restore the last good version
Fix Correct the cause, rerun, verify counts
Communicate Tell consumers early: what, impact, next update
Review A blameless write-up: timeline, cause, what will change
  • Use Delta time travel and RESTORE to recover a bad write (Module 5.10)
  • Keep a list of who owns what (on-call, data owners)
  • Reviews ask "why did our system allow this?" not "who did it?"
16.10Hands-on on Free Edition

Retry and Recovery Strategies

By the end: choose retry and recovery strategies by failure type.

Failure Strategy
Transient (network, quota) Retry with a delay and a limit
Bad record Quarantine the row, continue
Bad batch Stop, fix, repair run
Stream failure Restart from checkpoint (Module 11.14)
Downstream outage Retry later, keep upstream results
Wrong data written Restore a version, rerun
Python
import time

def with_retries(fn, attempts=3, delay=10):
    for i in range(1, attempts + 1):
        try:
            return fn()
        except SourceUnavailableError as e:
            log.warning("attempt %d/%d failed: %s", i, attempts, e)
            if i == attempts:
                raise
            time.sleep(delay * i)         # increasing wait
  • Retry only transient errors. Retrying a data error just fails slower
  • Set retries in one layer (job or code), not both (Module 28)
  • Retries need idempotent steps: MERGE, overwrite by range
16.11Hands-on on Free Edition

Backfill and Reprocessing

By the end: reprocess data safely.

  • Backfill: loading a range that was missed or is new. Reprocess: redoing a range with fixed logic
  • Same code path as daily runs, with a date range parameter (Module 8.13, Module 10.13)
Step Check
1. Decide the range Which dates, which tables, what depends on them
2. Try in a copy A test schema or a DEEP CLONE of the table
3. Run in chunks Small ranges, easy to restart
4. Reconcile Counts and sums against the source (Module 8.14)
5. Switch over Deploy the result, tell consumers
SQL
%sql
CREATE OR REPLACE TABLE orders_backfill_copy DEEP CLONE orders
  • Pause downstream jobs while you reprocess, or consumers see half-done data
  • Record the backfill in the run log: what range, why, who
  • Keep the original data until the new result is verified

Backfill and streaming

A stream with a checkpoint won't re-read old data. Reprocess in batch from bronze, or use a one-time append flow in a pipeline.

16.12Concept

Audit Logging

By the end: use audit logs to answer who did what.

Question Source
Who read or changed a table? system.access.audit (and table history with DESCRIBE HISTORY)
Who ran this job? Job run details, system.lakeflow
Who changed permissions? Audit events from Unity Catalog
What data did a query touch? Lineage, system.query.history
SQL
%sql
-- recent Unity Catalog actions by a user
SELECT event_time, action_name, user_identity.email, request_params
FROM system.access.audit
WHERE event_date >= current_date() - INTERVAL 7 DAYS
  AND service_name = 'unityCatalog'
ORDER BY event_time DESC
LIMIT 50
  • Filter on event_date, not only event_time: much faster
  • Audit data is for investigation and compliance: keep who may query it small
  • On your own tables, DESCRIBE HISTORY shows every write with user and operation
SQL
%sql
DESCRIBE HISTORY orders_delta

Free Edition

System tables may not be available. DESCRIBE HISTORY always works for Delta tables you own.

16.13Concept

Operational Runbooks

By the end: write a runbook that someone at 3 a.m. can follow.

Text
Runbook: orders_daily

Purpose      Loads and cleans orders. Feeds the revenue dashboard.
Owner        data-eng (channel #data-eng-alerts)
Schedule     Daily 05:00 UTC. SLA: tables ready by 08:00.
Depends on   Landing files in /Volumes/.../orders. Table products.

Alerts
  FAILED          -> check the failed task, see "Common failures"
  Duration > 20m  -> check input volume, query profile

Common failures
  Schema changed      -> Auto Loader stopped: rerun (new column added) or full refresh
  Quarantine > 1%     -> notify the source owner, do NOT backfill yet
  Permission denied   -> check the service principal grants

Recovery
  1. Repair run (reruns failed task and downstream)
  2. Bad data written -> RESTORE TABLE ... TO VERSION AS OF n, then rerun
  3. Backfill: run the job with run_date range; reconcile counts

Escalation   On-call, then team lead after 30 minutes.
  • One page per pipeline, kept next to the code
  • Written for someone who didn't build it
  • Update it after every incident
  • Link it from the alert and the job description

A runbook is tested by use

If following it didn't work last time, fix the runbook now.

16.14Concept

Cost Monitoring and Budget Alerts

By the end: monitor cost and set budget alerts.

Tool Shows
system.billing.usage Usage by job, product, tag (source of truth)
Budgets (account console) Monthly spend thresholds with email alerts
Budget policies / usage policies Attribute serverless usage to teams or projects
Query history, job runs Which runs and queries cost the most
SQL
%sql
-- usage per job (sum handles correction records)
SELECT usage_metadata.job_id, usage_date, sum(usage_quantity) AS dbus
FROM system.billing.usage
WHERE usage_metadata.job_id IS NOT NULL
GROUP BY ALL
HAVING dbus != 0
ORDER BY usage_date DESC
  • Usage appears within about 12 hours, and budget emails can lag up to 24 hours
  • Budgets use list prices: no discounts or credits
  • Tag jobs and pipelines (team, project, env) so cost can be split (Module 18)
  • Review the top 10 jobs by cost every month

Free Edition

There is no account console, so budgets aren't available, and billing system tables may not exist. You have a daily compute quota instead.

Recap

What you can do now

  • Monitoring layers, alerts, system tables and run logs
  • Logging, error classes, quality and freshness monitoring
  • Troubleshooting, incidents, retries and backfills
  • Audit logs, runbooks and cost monitoring