DatabricksZero to Advanced

The standards a team agrees on so that anyone can read, run and trust the code. Most topics are short rules with a good and a bad example. The topics that fit a checklist end with one.

  • The Best practices page (in the course map) holds the one-line checklists. This module explains the reasoning and gives examples
  • Practice code runs on orders (Module 4):
Python
from pyspark.sql import DataFrame, functions as F

catalog = spark.sql("SELECT current_catalog()").first()[0]
spark.sql(f"USE SCHEMA {catalog}.dbx_course")
22.01Concept

Databricks Well-Architected Framework

By the end: use the Databricks well-architected framework as a review lens.

Pillar Standard to hold
Operational excellence Code in Git, CI/CD, infrastructure as code, standard compute, monitoring
Security, privacy, compliance Least privilege, secrets in scopes, isolation, auditing
Reliability Delta, idempotent jobs, retries, checkpoints, tested recovery
Performance efficiency Autoscaling or serverless, good table layout, efficient queries
Cost optimization Right compute, tags, budgets, job compute for jobs
Data and AI governance Unity Catalog, lineage, quality rules, owners
Interoperability and usability Open formats, open interfaces, self-service
  • From the docs, operational excellence means: version control, CI/CD, environment isolation by workspace or catalog, managed tables, IaC, monitoring
  • Reliability means: Delta for ACID, retry policies and maximum runtimes on jobs, quality checks at ingest, tested recovery (checkpoints, time travel, repair runs)
  • Use the pillars as headings in design reviews and post-incident reviews (Module 19.12)
22.02Hands-on on Free Edition

Clean Code Principles for PySpark

By the end: write clean, readable PySpark.

Before:

Python
d = spark.table("orders")
d = d.filter("status = 'completed' or status = 'shipped'")
d = d.withColumn("r", d.quantity * 2)
d = d.groupBy("customer_id").agg(F.sum("r").alias("x"))

After:

Python
REVENUE_STATUSES = ["completed", "shipped"]

def completed_quantity_by_customer(orders: DataFrame) -> DataFrame:
    """Total ordered quantity per customer, counting only revenue-bearing orders."""
    return (
        orders
        .filter(F.col("status").isin(REVENUE_STATUSES))
        .groupBy("customer_id")
        .agg(F.sum("quantity").alias("total_quantity"))
    )

result = completed_quantity_by_customer(spark.table("orders"))
Rule Why
Names say what it is (total_quantity, not x) Readers don't decode
One chain per step, one transformation per line Easy to read and diff
Functions with a DataFrame in and out Testable and reusable
No magic values: constants with names One place to change
Docstring with grain and meaning Intent survives
F.col("...") for expressions, select/withColumns over long withColumn loops Clarity and speed
No collect(), toPandas() on big data Memory and speed
Comments say why, not what The code says what
  • Format with a tool (black/ruff format) and lint (ruff): style is not a debate
  • Small functions: if you can't name it in a few words, split it
22.03Concept

SOLID Principles in Data Engineering

By the end: apply SOLID principles to data code.

Principle In a pipeline
Single responsibility A function reads or transforms or writes. Not all three
Open/closed Add behaviour by adding a new step or config entry, not by editing a working function
Liskov substitution Any source (CSV, Delta, API) that implements the reader interface can replace another
Interface segregation Small interfaces: a writer doesn't need to know how to read
Dependency inversion Pass the table names, paths and clients in, don't hard-code them inside
Python
# dependency inversion: the function gets what it needs
def build_daily(orders: DataFrame, products: DataFrame) -> DataFrame:
    return (orders.join(products, "product_id")
                  .groupBy(F.to_date("order_ts").alias("order_date"))
                  .agg(F.round(F.sum(F.col("quantity") * F.col("unit_price")), 2).alias("revenue")))

# the caller decides where the data comes from and goes to
daily = build_daily(spark.table("orders"), spark.table("products"))
  • The practical test: can I unit-test this function with two tiny DataFrames? If yes, the design is probably sound
  • Don't over-engineer: patterns are for repeated problems
22.04Hands-on on Free Edition

Modular Pipeline Design

By the end: design modular pipelines.

Entry pointreads config and parameters, calls the steps {accent} Stepsreadtransformvalidatewrite {soft} Shared librarytransformations, helpers, schemas
Python
def read_orders(spark, table: str) -> DataFrame:
    return spark.table(table)

def transform(orders: DataFrame) -> DataFrame:
    return completed_quantity_by_customer(orders)

def validate(df: DataFrame) -> DataFrame:
    assert df.filter("total_quantity <= 0").count() == 0, "non-positive totals"
    return df

def write(df: DataFrame, table: str) -> None:
    df.write.mode("overwrite").saveAsTable(table)

def run(spark, source: str, target: str) -> None:
    write(validate(transform(read_orders(spark, source))), target)

run(spark, "orders", "std_customer_quantity")
Layer Contains Tested by
Entry point Wiring only Integration test
Steps Read, transform, validate, write Unit and integration tests
Library Shared pure functions Unit tests
  • A new source or target changes one function
  • Packages, wheels and project layout: Module 26
22.05Concept

Separation of Concerns

By the end: separate concerns.

Logicwhat the data meanspure functionsI/Owhere data comes from and goesread and write functionsConfigvalues per environmentfiles, parametersOrchestrationwhen and in what orderjobs, bundles
  • Business logic doesn't know about tables, paths or schedules
  • I/O code doesn't contain business rules
  • Environment values (catalog, paths, secrets) are never inside the logic
  • Scheduling lives in the job definition, not in sleep loops in notebooks
  • Platform setup (compute, grants) is in infrastructure code, not in the pipeline
  • Separate concerns = each can change and be tested alone

The 'one big notebook'

Reads, cleans, joins, writes, plots and sends email in 600 lines. Can't test, reuse or review it. Split it.

22.06Concept

Data Quality Standards

By the end: set data quality standards.

Standard Example
Every table has an owner COMMENT or tag: owner = sales-data
Rules per layer Bronze: shape and arrival. Silver: types, keys, dedupe. Gold: business rules, reconciliation
Every rule has an action Warn, drop (and quarantine), or fail
Quarantine, never drop silently Rejected rows kept with a reason
Quality is measured and kept Counts of failures per run (Module 16.6)
SLAs Freshness and completeness targets, alerted
Contracts Schema, meaning and owner agreed with consumers
Constraints where enforceable NOT NULL, CHECK on Delta tables
SQL
%sql
ALTER TABLE orders_delta SET TBLPROPERTIES ('quality.owner' = 'sales-data', 'quality.sla_hours' = '24')
  • A data quality score ("98.7% of rows passed") is a useful trend metric, never the whole story
  • Quality rules are code: reviewed, tested, versioned (Module 14)
  • Fix problems at the source where you can
22.07Concept

Naming and Folder Conventions

By the end: agree on naming and folder conventions.

Object Convention Example
Catalog Environment or domain dev, prod, sales
Schema Domain and layer sales_bronze, sales_silver, sales_gold
Table Singular or plural, consistent, lowercase snake_case orders, dim_customer, fact_orders
Column snake_case, specific names order_ts, customer_id, is_valid
Keys <entity>_id, surrogate <entity>_sk customer_sk
Booleans is_ / has_ is_current
Timestamps / dates _ts, _date suffix, UTC created_ts
Metadata columns Underscore prefix _ingested_at, _source
Jobs <project>-<purpose>-<env> orders-daily-prod
repo/databricks.yml, resources/src/<package>/transformations, io, configtests/unit/, integration/notebooks/01_ingest, 02_clean (numbered in reading order)
  • Lowercase and underscores: avoids quoting and clashes
  • Use UTC for timestamps, and say so
  • Consistent beats clever. Write the rules in one page and put it in the repo
  • Rename with a view or alias and a deprecation period
22.08Concept

Schema and Table Design Guidelines

By the end: follow schema and table design guidelines.

Guideline Why
Delta tables in Unity Catalog, managed by default Governance, automatic optimization
Choose the grain first and write it down Prevents double counting
Right types: DATE, TIMESTAMP, DECIMAL(p,s) for money, not strings or floats Correct maths, smaller data
Nullable only when meaningful Cleaner logic
Use NOT NULL and CHECK for rules you can enforce Bad writes fail
Stable keys, surrogate keys for dimensions Joins survive source changes
COMMENTs on tables and columns, tags for sensitivity Discovery and governance
Avoid wide, deeply nested tables when queries need a few fields Scan cost
Put the columns you filter and join on early (or list them for stats) Better data skipping
Store raw JSON in bronze as string or VARIANT, then parse to columns Schema changes don't break ingestion
  • Never edit the underlying files of a Delta table
  • Use CREATE OR REPLACE TABLE to rebuild, not delete and recreate
  • Don't use Spark caching on tables: it loses skipping and can show stale data
22.09Concept

Partitioning and Clustering Guidelines

By the end: choose partitioning and clustering by the guideline.

Liquid clusteringrecommended for new tableschange keys any timehandles high cardinality and skewPartitioningrare, very large tableslow-cardinality columnrewrite to change
  • Databricks recommends liquid clustering for new tables, together with managed tables and predictive optimization
  • Clustering keys: the columns you filter on most (up to 4). Not compatible with partitioning or ZORDER
  • Partition only very large tables and low-cardinality columns (such as date) where each partition is still large
  • Over-partitioning (thousands of tiny partitions) is worse than none
  • Run OPTIMIZE regularly or let predictive optimization do it. OPTIMIZE compacts files. VACUUM removes old ones
  • Don't call REFRESH TABLE or manage partitions by hand: filter with WHERE
Table Use
Small (under a few GB) No partitioning, optional clustering
Large, filtered by date and customer CLUSTER BY (order_date, customer_id)
Huge, always filtered by one low-cardinality column Partition by it, maybe cluster inside
22.10Concept

Observability Standards

By the end: meet observability standards.

Signal Standard
Logs Structured, with job, run id, table, date. No secrets or personal data
Metrics Rows in, out, rejected. Duration. Freshness. Per step, stored in a table
Traces / lineage Unity Catalog lineage, event logs, query history
Alerts Failure, slow run, missing run, quality, cost. To a channel, with a link to the run and runbook
Dashboards A pipeline health view per domain
Runbooks One per pipeline, updated after incidents
Ownership Every job and table has an owner and an on-call
  • Alert on symptoms people feel (stale dashboard) and causes you can act on (failed task)
  • Every alert has an action. If it doesn't, delete it
  • Retention: keep audit and run logs as long as compliance needs
  • Detail: Module 16
22.11Concept

Security and Governance Standards

By the end: meet security and governance standards.

  • Unity Catalog for all data. Three-level names always
  • Groups, not individuals, in grants. Least privilege. Owners are groups
  • Service principals run production jobs
  • Secrets in scopes (or a secret manager). Never in code, notebooks, logs, parameters
  • Separate environments by catalog, schema or workspace
  • Sensitive data tagged, masked or filtered at the table (row filters, column masks, ABAC)
  • Retention and deletion policies written down and implemented (DELETE, REORG, VACUUM)
  • Audit access and changes. Review grants regularly
  • Network controls where regulation needs them (private connectivity, egress control)
  • Governance changes go through code review
SQL
%sql
-- a standing review query: who holds powerful privileges on this schema?
SHOW GRANTS ON SCHEMA dbx_course

Default deny

Start with no access and add what each group needs. Removing access later is harder than not giving it.

22.12Concept

CI/CD and Version Control Standards

By the end: meet CI/CD and version control standards.

Standard Detail
Everything in Git Code, job and pipeline definitions, config, docs, tests
Branching Short-lived branches from main, pull requests, reviews
Protected main No direct pushes. Required checks and approvals
Commit messages Say why. Link the ticket
CI on every pull request Lint, unit tests, bundle validate, deploy to dev, integration tests
Build once, deploy many The same wheel or bundle to every environment
Deploy as a service principal Never a person's token
No manual edits in production If it isn't in Git, it will be overwritten
Releases Tags and semantic versions. Rollback = redeploy a tag
Secrets In the CI secret store, never in the repository
  • Pre-commit hooks for format, lint and secret scanning
  • Keep notebooks small and clean in Git: no outputs committed
  • Detail: Modules 14, 15, 26
22.13Concept

Production Readiness Checklist

By the end: check production readiness before launch.

Checklist. A pipeline is ready for production when each line is true:

Text
Code
 [ ] In Git, reviewed, tagged with a version
 [ ] Unit tests pass. Integration test run on test data
 [ ] No hard-coded paths, catalogs, secrets or user names
Data
 [ ] Output grain, schema and meaning documented. Table owner set
 [ ] Quality rules defined with actions. Quarantine in place
 [ ] Idempotent: a rerun gives the same result. Backfill tested
Operations
 [ ] Runs as a job with a service principal. Schedule and parameters set
 [ ] Retries, timeouts and failure notifications set
 [ ] Alerts for failure, duration and freshness
 [ ] Runbook written. On-call named
Governance and cost
 [ ] Permissions granted to groups, least privilege
 [ ] Sensitive columns tagged and protected
 [ ] Tags for cost attribution. Cost estimate known
Recovery
 [ ] Time travel / restore plan tested
 [ ] Rollback = previous tag redeployed. Tried once
  • Run the list in a review meeting, not alone
  • Keep it short enough to be used. Remove lines nobody checks
22.14Concept

Anti-Patterns and Common Mistakes

By the end: recognise common anti-patterns.

Anti-pattern Why it hurts Instead
One giant notebook Can't test or reuse Package and functions
%run chains and copy-pasted notebooks Hidden dependencies A package, imports
Hard-coded paths, catalogs, dates Breaks between environments Parameters and config
inferSchema on production CSV/JSON Types change run to run Explicit schemas, Auto Loader hints
collect() / toPandas() on big data Driver out of memory Keep it in Spark
Python UDFs for everything Slow, no pushdown Built-in functions, then pandas_udf
coalesce(1) on large writes One slow task Let the writer choose, OPTIMIZE later
Over-partitioning Millions of tiny files Liquid clustering
mode("overwrite") on the whole table by habit Wipes everything MERGE, replaceWhere
Appending without dedupe Duplicates on retry Idempotent writes
Swallowing exceptions Silent bad data Fail loudly, log the cause
Dropping bad rows silently Hidden data loss Quarantine with a reason
Production run by hand from a notebook Not repeatable Jobs from Git
A person's token in a job Dies when they leave Service principal
Secrets in code or widgets Leaks Secret scopes
Spark caching on serverless Raises an error Write to Delta
No monitoring until someone complains Late, costly Alerts on failure, freshness, cost

Remember

Most of these feel fast on day one and cost weeks later. A standard is a decision made once.

Recap

What you can do now

  • Framework and review lens, clean code, SOLID, modular design
  • Quality, naming, schema and layout standards
  • Observability, security, CI/CD standards
  • The production readiness checklist and anti-patterns