databricks Best Practices
This guide outlines the essential best practices for developing on Databricks. Adhere to these rules to ensure your code is maintainable, performant, and secure.
1. Code Organization and Structure
Leverage Git folders and Databricks Asset Bundles for all projects. Treat notebooks as version-controlled code, not isolated scripts. Extract reusable logic into Python modules.
Version Control: Always use Git folders for notebooks and source code.
❌ BAD: Storing notebooks directly in Workspace without Git integration.
✅ GOOD:
# In a Git-synced notebook (e.g., /Repos/user/my-repo/notebooks/my_pipeline.py)
# This notebook is version-controlled and can import local modules.
from ..src.utils import process_data
df = spark.read.table("raw_data")
processed_df = process_data(df)
processed_df.write.mode("overwrite").saveAsTable("processed_data")
Module Extraction: For any logic beyond simple notebook orchestration, extract it into Python modules (.py files) within your Git repository.
❌ BAD:
# In a notebook cell
def complex_transformation(df):
# 50+ lines of transformation logic
return df
✅ GOOD:
# /Repos/user/my-repo/src/transformations.py
def complex_transformation(df):
# Modular, testable logic
return df
# In a notebook
from src.transformations import complex_transformation
df = complex_transformation(spark.read.table("staging"))
Project Structure with Bundles: Use Databricks Asset Bundles to define and deploy your entire project (jobs, pipelines, models, notebooks, infrastructure) as a single, versioned unit.
✅ GOOD:
# databricks-bundle.yml
bundle:
name: my-data-pipeline
resources:
jobs:
my_etl_job:
name: My ETL Job
tasks:
- task_key: process_data
notebook_task:
notebook_path: ./notebooks/main_pipeline.py
new_cluster:
spark_version: "14.3.x-scala2.12"
node_type_id: "Standard_DS3_v2"
num_workers: 3
Pin Dependencies: Always pin Python package versions in requirements.txt for reproducibility, especially for serverless compute.
❌ BAD:
pandas
numpy
✅ GOOD:
pandas==2.2.3
numpy==1.26.4
2. Common Patterns and Anti-patterns
Leverage Databricks-native services for all heavy lifting. Avoid using Databricks Apps compute for data processing.
Offload Heavy Processing: Databricks Apps compute is for UI rendering. Use Databricks SQL for ad-hoc queries, Lakeflow Jobs for batch pipelines, and Model Serving for AI inference.
❌ BAD:
# In a Databricks App (e.g., Flask app)
df = spark.sql("SELECT * FROM large_table").toPandas() # Pulls large data to app compute
processed_data = df.groupby('col').sum() # Heavy processing on app compute
✅ GOOD:
# In a Databricks App
# Use Databricks SQL for queries
from databricks.sdk.service.sql import StatementExecutionAPI
# ... authenticate ...
statement_execution = StatementExecutionAPI(api_client)
result = statement_execution.execute_statement(
warehouse_id="your_sql_warehouse_id",
statement="SELECT SUM(col) FROM large_table GROUP BY col",
# ... handle async results ...
)
# For batch processing, trigger a Lakeflow Job
# For AI inference, call a Model Serving endpoint
Serverless Compute Compatibility: Ensure data is in Unity Catalog, use Databricks Runtime 14.3+, and avoid JARs.
❌ BAD:
# Attempting to use a custom JAR for a data source on serverless
spark.sparkContext.addJar("s3://my-bucket/custom-connector.jar")
df = spark.read.format("com.example.CustomSource").load(...)
✅ GOOD:
# Use native ingestion methods for serverless
# For cloud storage:
df = spark.readStream.format("cloudFiles") \
.option("cloudFiles.format", "json") \
.load("s3://my-bucket/raw_data")
# For external databases (query federation):
df = spark.read.table("my_catalog.external_schema.external_table")
3. Performance Considerations
Optimize resource usage and data access patterns.
Databricks Apps Startup: Keep initialization lightweight. Load heavy resources only when needed.
❌ BAD:
# app.py
import large_model_library
model = large_model_library.load_model("path/to/model") # Blocks startup
✅ GOOD:
# app.py
model = None
def get_model():
nonlocal model
if model is None:
import large_model_library
model = large_model_library.load_model("path/to/model") # Lazy load
return model
In-Memory Caching (Apps): Cache frequently used data or API responses.
✅ GOOD:
from functools import lru_cache
@lru_cache(maxsize=128)
def get_cached_data(key):
# Expensive operation, e.g., query Databricks SQL
return fetch_data_from_warehouse(key)
4. Common Pitfalls and Gotchas
Avoid common missteps that lead to instability or security vulnerabilities.
Graceful Shutdown (Databricks Apps): Implement SIGTERM handling to shut down within 15 seconds.
❌ BAD: No SIGTERM handler, app gets SIGKILL.
✅ GOOD:
import signal
import sys
import time
def signal_handler(signum, frame):
print("SIGTERM received, initiating graceful shutdown...")
# Perform cleanup, close connections, etc.
sys.exit(0)
signal.signal(signal.SIGTERM, signal_handler)
# ... your app logic ...
Logging (Databricks Apps): Log to stdout and stderr only.
❌ BAD: with open("app.log", "a") as f: f.write(...)
✅ GOOD: print("Log message"); import logging; logging.info("Log message")
Secrets Management: Never expose raw secrets. Use valueFrom in app config.
❌ BAD: env: MY_API_KEY: "super-secret-value"
✅ GOOD:
env:
MY_API_KEY:
valueFrom:
secret:
name: "my-scope/my-api-key"
Privileged Operations (Databricks Apps): Apps run as non-privileged users.
❌ BAD: Attempting apt-get install or root access.
✅ GOOD: Use Python/Node.js package managers (PyPI, npm).
5. Error Handling
Implement robust error handling and observability.
Global Exception Handling (Databricks Apps): Prevent crashes, return proper HTTP errors.
❌ BAD: Uncaught exceptions exposing stack traces.
✅ GOOD:
from flask import Flask, jsonify
app = Flask(__name__)
@app.errorhandler(Exception)
def handle_exception(e):
# Log the full exception for internal debugging
app.logger.error(f"Unhandled exception: {e}", exc_info=True)
return jsonify(error="An unexpected error occurred"), 500
Observability: Enable monitoring for Lakeflow Jobs and Pipelines.
✅ GOOD: Configure job logging and alerts within Databricks.
6. Request/Response Patterns
Use Databricks SDKs/APIs and secure data exchange.
Interoperability: Use the unified Databricks REST API or higher-level SDKs (Python, Java, Go, R) for cross-system integration.
❌ BAD: Custom HTTP clients for Databricks API calls.
✅ GOOD:
from databricks.sdk import WorkspaceClient
w = WorkspaceClient()
jobs = w.jobs.list()
for job in jobs:
print(job.settings.name)
Parameterized SQL: Prevent SQL injection.
❌ BAD: cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'")
✅ GOOD: cursor.execute("SELECT * FROM users WHERE name = %s", (user_input,))
Asynchronous Requests (Apps for Long-Running Operations): Avoid synchronous waits.
✅ GOOD:
# Initial request to start a job
job_run = w.jobs.run_now(job_id="my_long_job")
run_id = job_run.run_id
# Periodically poll for status
while w.jobs.get_run(run_id).state.life_cycle_state not in ["TERMINATED", "SKIPPED"]:
time.sleep(10)
7. Testing Approaches
Embrace automated testing for reliability.
Unit Testing: Unit test extracted Python modules locally or in CI/CD.
✅ GOOD:
# test_transformations.py
import pytest
from src.transformations import complex_transformation
from pyspark.sql import SparkSession
@pytest.fixture(scope="session")
def spark():
return SparkSession.builder.appName("pytest-spark").getOrCreate()
def test_complex_transformation(spark):
data = [("A", 1), ("B", 2)]
input_df = spark.createDataFrame(data, ["col1", "col2"])
output_df = complex_transformation(input_df)
assert output_df.count() == 2
# ... more assertions ...
CI/CD Pipeline: Implement a full CI/CD pipeline using Databricks Asset Bundles, GitHub Actions, Azure DevOps, etc.
✅ GOOD:
# .github/workflows/databricks_ci.yml
name: Databricks CI/CD
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: databricks/setup-cli@v0
- run: databricks bundle deploy -t production # Deploy using the bundle
env:
DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }}
DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }}
1---2name: databricks3description: [Applies to: **/*] Definitive guidelines for developing robust, performant, and secure applications and data pipelines on Databricks using modern best practices and native services.4---56# databricks Best Practices78This guide outlines the essential best practices for developing on Databricks. Adhere to these rules to ensure your code is maintainable, performant, and secure.910## 1. Code Organization and Structure1112**Leverage Git folders and Databricks Asset Bundles for all projects.** Treat notebooks as version-controlled code, not isolated scripts. Extract reusable logic into Python modules.1314* **Version Control**: Always use Git folders for notebooks and source code.15 ❌ BAD: Storing notebooks directly in Workspace without Git integration.16 ✅ GOOD:17 ```python18 # In a Git-synced notebook (e.g., /Repos/user/my-repo/notebooks/my_pipeline.py)19 # This notebook is version-controlled and can import local modules.20 from ..src.utils import process_data2122 df = spark.read.table("raw_data")23 processed_df = process_data(df)24 processed_df.write.mode("overwrite").saveAsTable("processed_data")25 ```2627* **Module Extraction**: For any logic beyond simple notebook orchestration, extract it into Python modules (`.py` files) within your Git repository.28 ❌ BAD:29 ```python30 # In a notebook cell31 def complex_transformation(df):32 # 50+ lines of transformation logic33 return df34 ```35 ✅ GOOD:36 ```python37 # /Repos/user/my-repo/src/transformations.py38 def complex_transformation(df):39 # Modular, testable logic40 return df4142 # In a notebook43 from src.transformations import complex_transformation44 df = complex_transformation(spark.read.table("staging"))45 ```4647* **Project Structure with Bundles**: Use Databricks Asset Bundles to define and deploy your entire project (jobs, pipelines, models, notebooks, infrastructure) as a single, versioned unit.48 ✅ GOOD:49 ```yaml50 # databricks-bundle.yml51 bundle:52 name: my-data-pipeline53 resources:54 jobs:55 my_etl_job:56 name: My ETL Job57 tasks:58 - task_key: process_data59 notebook_task:60 notebook_path: ./notebooks/main_pipeline.py61 new_cluster:62 spark_version: "14.3.x-scala2.12"63 node_type_id: "Standard_DS3_v2"64 num_workers: 365 ```6667* **Pin Dependencies**: Always pin Python package versions in `requirements.txt` for reproducibility, especially for serverless compute.68 ❌ BAD:69 ```70 pandas71 numpy72 ```73 ✅ GOOD:74 ```75 pandas==2.2.376 numpy==1.26.477 ```7879## 2. Common Patterns and Anti-patterns8081**Leverage Databricks-native services for all heavy lifting.** Avoid using Databricks Apps compute for data processing.8283* **Offload Heavy Processing**: Databricks Apps compute is for UI rendering. Use Databricks SQL for ad-hoc queries, Lakeflow Jobs for batch pipelines, and Model Serving for AI inference.84 ❌ BAD:85 ```python86 # In a Databricks App (e.g., Flask app)87 df = spark.sql("SELECT * FROM large_table").toPandas() # Pulls large data to app compute88 processed_data = df.groupby('col').sum() # Heavy processing on app compute89 ```90 ✅ GOOD:91 ```python92 # In a Databricks App93 # Use Databricks SQL for queries94 from databricks.sdk.service.sql import StatementExecutionAPI95 # ... authenticate ...96 statement_execution = StatementExecutionAPI(api_client)97 result = statement_execution.execute_statement(98 warehouse_id="your_sql_warehouse_id",99 statement="SELECT SUM(col) FROM large_table GROUP BY col",100 # ... handle async results ...101 )102 # For batch processing, trigger a Lakeflow Job103 # For AI inference, call a Model Serving endpoint104 ```105106* **Serverless Compute Compatibility**: Ensure data is in Unity Catalog, use Databricks Runtime 14.3+, and avoid JARs.107 ❌ BAD:108 ```python109 # Attempting to use a custom JAR for a data source on serverless110 spark.sparkContext.addJar("s3://my-bucket/custom-connector.jar")111 df = spark.read.format("com.example.CustomSource").load(...)112 ```113 ✅ GOOD:114 ```python115 # Use native ingestion methods for serverless116 # For cloud storage:117 df = spark.readStream.format("cloudFiles") \118 .option("cloudFiles.format", "json") \119 .load("s3://my-bucket/raw_data")120121 # For external databases (query federation):122 df = spark.read.table("my_catalog.external_schema.external_table")123 ```124125## 3. Performance Considerations126127**Optimize resource usage and data access patterns.**128129* **Databricks Apps Startup**: Keep initialization lightweight. Load heavy resources only when needed.130 ❌ BAD:131 ```python132 # app.py133 import large_model_library134 model = large_model_library.load_model("path/to/model") # Blocks startup135 ```136 ✅ GOOD:137 ```python138 # app.py139 model = None140 def get_model():141 nonlocal model142 if model is None:143 import large_model_library144 model = large_model_library.load_model("path/to/model") # Lazy load145 return model146 ```147148* **In-Memory Caching (Apps)**: Cache frequently used data or API responses.149 ✅ GOOD:150 ```python151 from functools import lru_cache152153 @lru_cache(maxsize=128)154 def get_cached_data(key):155 # Expensive operation, e.g., query Databricks SQL156 return fetch_data_from_warehouse(key)157 ```158159## 4. Common Pitfalls and Gotchas160161**Avoid common missteps that lead to instability or security vulnerabilities.**162163* **Graceful Shutdown (Databricks Apps)**: Implement `SIGTERM` handling to shut down within 15 seconds.164 ❌ BAD: No `SIGTERM` handler, app gets `SIGKILL`.165 ✅ GOOD:166 ```python167 import signal168 import sys169 import time170171 def signal_handler(signum, frame):172 print("SIGTERM received, initiating graceful shutdown...")173 # Perform cleanup, close connections, etc.174 sys.exit(0)175176 signal.signal(signal.SIGTERM, signal_handler)177 # ... your app logic ...178 ```179180* **Logging (Databricks Apps)**: Log to `stdout` and `stderr` only.181 ❌ BAD: `with open("app.log", "a") as f: f.write(...)`182 ✅ GOOD: `print("Log message"); import logging; logging.info("Log message")`183184* **Secrets Management**: Never expose raw secrets. Use `valueFrom` in app config.185 ❌ BAD: `env: MY_API_KEY: "super-secret-value"`186 ✅ GOOD:187 ```yaml188 env:189 MY_API_KEY:190 valueFrom:191 secret:192 name: "my-scope/my-api-key"193 ```194195* **Privileged Operations (Databricks Apps)**: Apps run as non-privileged users.196 ❌ BAD: Attempting `apt-get install` or root access.197 ✅ GOOD: Use Python/Node.js package managers (PyPI, npm).198199## 5. Error Handling200201**Implement robust error handling and observability.**202203* **Global Exception Handling (Databricks Apps)**: Prevent crashes, return proper HTTP errors.204 ❌ BAD: Uncaught exceptions exposing stack traces.205 ✅ GOOD:206 ```python207 from flask import Flask, jsonify208 app = Flask(__name__)209210 @app.errorhandler(Exception)211 def handle_exception(e):212 # Log the full exception for internal debugging213 app.logger.error(f"Unhandled exception: {e}", exc_info=True)214 return jsonify(error="An unexpected error occurred"), 500215 ```216217* **Observability**: Enable monitoring for Lakeflow Jobs and Pipelines.218 ✅ GOOD: Configure job logging and alerts within Databricks.219220## 6. Request/Response Patterns221222**Use Databricks SDKs/APIs and secure data exchange.**223224* **Interoperability**: Use the unified Databricks REST API or higher-level SDKs (Python, Java, Go, R) for cross-system integration.225 ❌ BAD: Custom HTTP clients for Databricks API calls.226 ✅ GOOD:227 ```python228 from databricks.sdk import WorkspaceClient229 w = WorkspaceClient()230 jobs = w.jobs.list()231 for job in jobs:232 print(job.settings.name)233 ```234235* **Parameterized SQL**: Prevent SQL injection.236 ❌ BAD: `cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'")`237 ✅ GOOD: `cursor.execute("SELECT * FROM users WHERE name = %s", (user_input,))`238239* **Asynchronous Requests (Apps for Long-Running Operations)**: Avoid synchronous waits.240 ✅ GOOD:241 ```python242 # Initial request to start a job243 job_run = w.jobs.run_now(job_id="my_long_job")244 run_id = job_run.run_id245246 # Periodically poll for status247 while w.jobs.get_run(run_id).state.life_cycle_state not in ["TERMINATED", "SKIPPED"]:248 time.sleep(10)249 ```250251## 7. Testing Approaches252253**Embrace automated testing for reliability.**254255* **Unit Testing**: Unit test extracted Python modules locally or in CI/CD.256 ✅ GOOD:257 ```python258 # test_transformations.py259 import pytest260 from src.transformations import complex_transformation261 from pyspark.sql import SparkSession262263 @pytest.fixture(scope="session")264 def spark():265 return SparkSession.builder.appName("pytest-spark").getOrCreate()266267 def test_complex_transformation(spark):268 data = [("A", 1), ("B", 2)]269 input_df = spark.createDataFrame(data, ["col1", "col2"])270 output_df = complex_transformation(input_df)271 assert output_df.count() == 2272 # ... more assertions ...273 ```274275* **CI/CD Pipeline**: Implement a full CI/CD pipeline using Databricks Asset Bundles, GitHub Actions, Azure DevOps, etc.276 ✅ GOOD:277 ```yaml278 # .github/workflows/databricks_ci.yml279 name: Databricks CI/CD280 on: [push]281 jobs:282 deploy:283 runs-on: ubuntu-latest284 steps:285 - uses: actions/checkout@v4286 - uses: databricks/setup-cli@v0287 - run: databricks bundle deploy -t production # Deploy using the bundle288 env:289 DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }}290 DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }}291 ```