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."4source: "cursor_mdc"5---67# databricks Best Practices89This guide outlines the essential best practices for developing on Databricks. Adhere to these rules to ensure your code is maintainable, performant, and secure.1011## 1. Code Organization and Structure1213**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.1415* **Version Control**: Always use Git folders for notebooks and source code.16 ❌ BAD: Storing notebooks directly in Workspace without Git integration.17 ✅ GOOD:18 ```python19 # In a Git-synced notebook (e.g., /Repos/user/my-repo/notebooks/my_pipeline.py)20 # This notebook is version-controlled and can import local modules.21 from ..src.utils import process_data2223 df = spark.read.table("raw_data")24 processed_df = process_data(df)25 processed_df.write.mode("overwrite").saveAsTable("processed_data")26 ```2728* **Module Extraction**: For any logic beyond simple notebook orchestration, extract it into Python modules (`.py` files) within your Git repository.29 ❌ BAD:30 ```python31 # In a notebook cell32 def complex_transformation(df):33 # 50+ lines of transformation logic34 return df35 ```36 ✅ GOOD:37 ```python38 # /Repos/user/my-repo/src/transformations.py39 def complex_transformation(df):40 # Modular, testable logic41 return df4243 # In a notebook44 from src.transformations import complex_transformation45 df = complex_transformation(spark.read.table("staging"))46 ```4748* **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.49 ✅ GOOD:50 ```yaml51 # databricks-bundle.yml52 bundle:53 name: my-data-pipeline54 resources:55 jobs:56 my_etl_job:57 name: My ETL Job58 tasks:59 - task_key: process_data60 notebook_task:61 notebook_path: ./notebooks/main_pipeline.py62 new_cluster:63 spark_version: "14.3.x-scala2.12"64 node_type_id: "Standard_DS3_v2"65 num_workers: 366 ```6768* **Pin Dependencies**: Always pin Python package versions in `requirements.txt` for reproducibility, especially for serverless compute.69 ❌ BAD:70 ```71 pandas72 numpy73 ```74 ✅ GOOD:75 ```76 pandas==2.2.377 numpy==1.26.478 ```7980## 2. Common Patterns and Anti-patterns8182**Leverage Databricks-native services for all heavy lifting.** Avoid using Databricks Apps compute for data processing.8384* **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.85 ❌ BAD:86 ```python87 # In a Databricks App (e.g., Flask app)88 df = spark.sql("SELECT * FROM large_table").toPandas() # Pulls large data to app compute89 processed_data = df.groupby('col').sum() # Heavy processing on app compute90 ```91 ✅ GOOD:92 ```python93 # In a Databricks App94 # Use Databricks SQL for queries95 from databricks.sdk.service.sql import StatementExecutionAPI96 # ... authenticate ...97 statement_execution = StatementExecutionAPI(api_client)98 result = statement_execution.execute_statement(99 warehouse_id="your_sql_warehouse_id",100 statement="SELECT SUM(col) FROM large_table GROUP BY col",101 # ... handle async results ...102 )103 # For batch processing, trigger a Lakeflow Job104 # For AI inference, call a Model Serving endpoint105 ```106107* **Serverless Compute Compatibility**: Ensure data is in Unity Catalog, use Databricks Runtime 14.3+, and avoid JARs.108 ❌ BAD:109 ```python110 # Attempting to use a custom JAR for a data source on serverless111 spark.sparkContext.addJar("s3://my-bucket/custom-connector.jar")112 df = spark.read.format("com.example.CustomSource").load(...)113 ```114 ✅ GOOD:115 ```python116 # Use native ingestion methods for serverless117 # For cloud storage:118 df = spark.readStream.format("cloudFiles") \119 .option("cloudFiles.format", "json") \120 .load("s3://my-bucket/raw_data")121122 # For external databases (query federation):123 df = spark.read.table("my_catalog.external_schema.external_table")124 ```125126## 3. Performance Considerations127128**Optimize resource usage and data access patterns.**129130* **Databricks Apps Startup**: Keep initialization lightweight. Load heavy resources only when needed.131 ❌ BAD:132 ```python133 # app.py134 import large_model_library135 model = large_model_library.load_model("path/to/model") # Blocks startup136 ```137 ✅ GOOD:138 ```python139 # app.py140 model = None141 def get_model():142 nonlocal model143 if model is None:144 import large_model_library145 model = large_model_library.load_model("path/to/model") # Lazy load146 return model147 ```148149* **In-Memory Caching (Apps)**: Cache frequently used data or API responses.150 ✅ GOOD:151 ```python152 from functools import lru_cache153154 @lru_cache(maxsize=128)155 def get_cached_data(key):156 # Expensive operation, e.g., query Databricks SQL157 return fetch_data_from_warehouse(key)158 ```159160## 4. Common Pitfalls and Gotchas161162**Avoid common missteps that lead to instability or security vulnerabilities.**163164* **Graceful Shutdown (Databricks Apps)**: Implement `SIGTERM` handling to shut down within 15 seconds.165 ❌ BAD: No `SIGTERM` handler, app gets `SIGKILL`.166 ✅ GOOD:167 ```python168 import signal169 import sys170 import time171172 def signal_handler(signum, frame):173 print("SIGTERM received, initiating graceful shutdown...")174 # Perform cleanup, close connections, etc.175 sys.exit(0)176177 signal.signal(signal.SIGTERM, signal_handler)178 # ... your app logic ...179 ```180181* **Logging (Databricks Apps)**: Log to `stdout` and `stderr` only.182 ❌ BAD: `with open("app.log", "a") as f: f.write(...)`183 ✅ GOOD: `print("Log message"); import logging; logging.info("Log message")`184185* **Secrets Management**: Never expose raw secrets. Use `valueFrom` in app config.186 ❌ BAD: `env: MY_API_KEY: "super-secret-value"`187 ✅ GOOD:188 ```yaml189 env:190 MY_API_KEY:191 valueFrom:192 secret:193 name: "my-scope/my-api-key"194 ```195196* **Privileged Operations (Databricks Apps)**: Apps run as non-privileged users.197 ❌ BAD: Attempting `apt-get install` or root access.198 ✅ GOOD: Use Python/Node.js package managers (PyPI, npm).199200## 5. Error Handling201202**Implement robust error handling and observability.**203204* **Global Exception Handling (Databricks Apps)**: Prevent crashes, return proper HTTP errors.205 ❌ BAD: Uncaught exceptions exposing stack traces.206 ✅ GOOD:207 ```python208 from flask import Flask, jsonify209 app = Flask(__name__)210211 @app.errorhandler(Exception)212 def handle_exception(e):213 # Log the full exception for internal debugging214 app.logger.error(f"Unhandled exception: {e}", exc_info=True)215 return jsonify(error="An unexpected error occurred"), 500216 ```217218* **Observability**: Enable monitoring for Lakeflow Jobs and Pipelines.219 ✅ GOOD: Configure job logging and alerts within Databricks.220221## 6. Request/Response Patterns222223**Use Databricks SDKs/APIs and secure data exchange.**224225* **Interoperability**: Use the unified Databricks REST API or higher-level SDKs (Python, Java, Go, R) for cross-system integration.226 ❌ BAD: Custom HTTP clients for Databricks API calls.227 ✅ GOOD:228 ```python229 from databricks.sdk import WorkspaceClient230 w = WorkspaceClient()231 jobs = w.jobs.list()232 for job in jobs:233 print(job.settings.name)234 ```235236* **Parameterized SQL**: Prevent SQL injection.237 ❌ BAD: `cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'")`238 ✅ GOOD: `cursor.execute("SELECT * FROM users WHERE name = %s", (user_input,))`239240* **Asynchronous Requests (Apps for Long-Running Operations)**: Avoid synchronous waits.241 ✅ GOOD:242 ```python243 # Initial request to start a job244 job_run = w.jobs.run_now(job_id="my_long_job")245 run_id = job_run.run_id246247 # Periodically poll for status248 while w.jobs.get_run(run_id).state.life_cycle_state not in ["TERMINATED", "SKIPPED"]:249 time.sleep(10)250 ```251252## 7. Testing Approaches253254**Embrace automated testing for reliability.**255256* **Unit Testing**: Unit test extracted Python modules locally or in CI/CD.257 ✅ GOOD:258 ```python259 # test_transformations.py260 import pytest261 from src.transformations import complex_transformation262 from pyspark.sql import SparkSession263264 @pytest.fixture(scope="session")265 def spark():266 return SparkSession.builder.appName("pytest-spark").getOrCreate()267268 def test_complex_transformation(spark):269 data = [("A", 1), ("B", 2)]270 input_df = spark.createDataFrame(data, ["col1", "col2"])271 output_df = complex_transformation(input_df)272 assert output_df.count() == 2273 # ... more assertions ...274 ```275276* **CI/CD Pipeline**: Implement a full CI/CD pipeline using Databricks Asset Bundles, GitHub Actions, Azure DevOps, etc.277 ✅ GOOD:278 ```yaml279 # .github/workflows/databricks_ci.yml280 name: Databricks CI/CD281 on: [push]282 jobs:283 deploy:284 runs-on: ubuntu-latest285 steps:286 - uses: actions/checkout@v4287 - uses: databricks/setup-cli@v0288 - run: databricks bundle deploy -t production # Deploy using the bundle289 env:290 DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }}291 DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }}292 ```