| name | databricks-data-engineering |
| description | Production data engineering pipelines following medallion architecture (Bronze/Silver/Gold layers) with data ingestion, transformation, quality checks, Delta Lake optimization, and orchestration. Use when building ETL pipelines, medallion architecture, data lakes, or data transformation workflows. |
| allowed-tools | ["Bash","Read","Write","Edit","Grep","Glob"] |
| model | claude-sonnet-4-5-20250929 |
| user-invocable | true |
Databricks Data Engineering Pipelines
Build production-grade data pipelines following medallion architecture (Bronze/Silver/Gold) with data quality checks, Delta Lake optimization, and multi-layer transformations.
When to Use This Skill
- Building ETL/ELT data pipelines
- Implementing medallion architecture
- Data lake transformations
- Data quality and validation workflows
- Incremental data processing
- Batch data pipelines
- Real-time streaming (structured streaming)
Medallion Architecture
The medallion architecture organizes data into three layers of increasing quality:
Bronze Layer (Raw/Landing)
↓
Silver Layer (Cleaned/Validated)
↓
Gold Layer (Business/Aggregated)
Bronze Layer (Raw Ingestion)
- Purpose: Ingest raw data with minimal transformation
- Pattern: Append-only, preserve source format
- Transformations: Type casting, timestamp addition
- Storage: Delta Lake tables
- Schema: Flexible, can evolve
Silver Layer (Cleaned Data)
- Purpose: Cleaned, validated, and conformed data
- Pattern: Deduplication, quality checks, schema enforcement
- Transformations: Data quality rules, null handling, type validation
- Storage: Delta Lake tables (optimized)
- Schema: Strict, well-defined
Gold Layer (Business-Ready)
- Purpose: Aggregated, business-ready datasets
- Pattern: Joins, aggregations, business logic
- Transformations: Metrics, KPIs, analytics-ready views
- Storage: Delta Lake tables (highly optimized)
- Schema: Denormalized for analytics
Complete Data Pipeline Workflow
Phase 1: Schema Setup
Use databricks-unity-catalog skill to create medallion schemas:
catalog = "de_prod"
create_schema(
catalog_name=catalog,
schema_name="bronze",
comment="Raw ingested data. Minimal transformation. Append-only. 90-day retention."
)
create_schema(
catalog_name=catalog,
schema_name="silver",
comment="Cleaned and validated data. Deduplicated, quality-checked. 1-year retention."
)
create_schema(
catalog_name=catalog,
schema_name="gold",
comment="Business-ready aggregates. Optimized for analytics. 3-year retention."
)
Phase 2: Bronze Layer Development
Use databricks-testing skill to test ingestion logic:
databricks_command(
cluster_id="0123-456789-abc123",
language="python",
code="""
from pyspark.sql import functions as F
# Ingest raw data
raw_df = (
spark.read
.format("json") # or csv, parquet, etc.
.option("inferSchema", "true")
.load("/mnt/source/events/*.json")
)
# Add ingestion metadata
bronze_df = (
raw_df
.withColumn("ingestion_timestamp", F.current_timestamp())
.withColumn("ingestion_date", F.current_date())
.withColumn("source_file", F.input_file_name())
)
print(f"Ingested {bronze_df.count()} records")
# Save to bronze
bronze_df.write \\
.format("delta") \\
.mode("append") \\
.saveAsTable("de_prod.bronze.raw_events")
print("Bronze ingestion complete")
"""
)
Phase 3: Silver Layer Development
Use databricks-testing skill to test cleaning logic:
Complete Silver Layer Notebook:
try:
catalog = dbutils.widgets.get("catalog")
except:
catalog = "de_dev"
try:
bronze_schema = dbutils.widgets.get("bronze_schema")
except:
bronze_schema = "bronze"
try:
silver_schema = dbutils.widgets.get("silver_schema")
except:
silver_schema = "silver"
try:
batch_date = dbutils.widgets.get("batch_date")
except:
from datetime import date
batch_date = str(date.today())
print(f"Processing silver layer:")
print(f" Catalog: {catalog}")
print(f" Bronze schema: {bronze_schema}")
print(f" Silver schema: {silver_schema}")
print(f" Batch date: {batch_date}")
from pyspark.sql import functions as F
from pyspark.sql.window import Window
bronze_df = spark.read.table(f"{catalog}.{bronze_schema}.raw_events") \\
.filter(F.col("ingestion_date") == batch_date)
print(f"Bronze records for {batch_date}: {bronze_df.count()}")
print("Data Quality Report - Before Cleaning:")
print(f" Total records: {bronze_df.count()}")
print(f" Null event_id: {bronze_df.filter(F.col('event_id').isNull()).count()}")
print(f" Null timestamp: {bronze_df.filter(F.col('timestamp').isNull()).count()}")
print(f" Duplicate event_id: {bronze_df.groupBy('event_id').count().filter(F.col('count') > 1).count()}")
initial_count = bronze_df.count()
silver_df = (
bronze_df
.filter(F.col("event_id").isNotNull())
.filter(F.col("timestamp").isNotNull())
.filter(F.col("user_id").isNotNull())
.withColumn("row_num", F.row_number().over(
Window.partitionBy("event_id").orderBy(F.col("timestamp").desc())
))
.filter(F.col("row_num") == 1)
.drop("row_num")
.withColumn("amount", F.col("amount").cast("double"))
.withColumn("quantity", F.col("quantity").cast("int"))
.withColumn("timestamp", F.col("timestamp").cast("timestamp"))
.filter(F.col("amount") >= 0)
.filter(F.col("quantity") > 0)
.withColumn("silver_processed_at", F.current_timestamp())
.withColumn("silver_processing_date", F.current_date())
.drop("source_file", "ingestion_timestamp", "ingestion_date")
)
final_count = silver_df.count()
removed_count = initial_count - final_count
print(f"\\nData Quality Report - After Cleaning:")
print(f" Clean records: {final_count}")
print(f" Removed records: {removed_count} ({removed_count/initial_count*100:.2f}%)")
assert silver_df.filter(F.col("event_id").isNull()).count() == 0, "Null event_ids remain"
assert silver_df.filter(F.col("timestamp").isNull()).count() == 0, "Null timestamps remain"
assert silver_df.groupBy("event_id").count().filter(F.col("count") > 1).count() == 0, "Duplicates remain"
print("✓ All quality checks passed")
from delta.tables import DeltaTable
silver_table = f"{catalog}.{silver_schema}.clean_events"
if spark.catalog.tableExists(silver_table):
deltaTable = DeltaTable.forName(spark, silver_table)
deltaTable.alias("target").merge(
silver_df.alias("source"),
"target.event_id = source.event_id"
).whenMatchedUpdateAll() \\
.whenNotMatchedInsertAll() \\
.execute()
print(f"Merged {final_count} records into {silver_table}")
else:
silver_df.write \\
.format("delta") \\
.mode("overwrite") \\
.saveAsTable(silver_table)
print(f"Created {silver_table} with {final_count} records")
spark.sql(f"OPTIMIZE {silver_table}")
print(f"✓ Optimized {silver_table}")
spark.sql(f"ANALYZE TABLE {silver_table} COMPUTE STATISTICS")
print(f"✓ Updated statistics for {silver_table}")
spark.sql(f"OPTIMIZE {silver_table} ZORDER BY (event_id, timestamp)")
print(f"✓ Z-ordered {silver_table}")
print(f"\\n{'='*50}")
print("Silver layer processing complete!")
print(f" Input (Bronze): {initial_count} records")
print(f" Output (Silver): {final_count} records")
print(f" Quality: {final_count/initial_count*100:.2f}% retention")
print(f"{'='*50}")
Phase 4: Gold Layer Development
Use databricks-testing skill to test aggregation logic:
Complete Gold Layer Notebook:
try:
catalog = dbutils.widgets.get("catalog")
except:
catalog = "de_dev"
try:
silver_schema = dbutils.widgets.get("silver_schema")
except:
silver_schema = "silver"
try:
gold_schema = dbutils.widgets.get("gold_schema")
except:
gold_schema = "gold"
print(f"Processing gold layer:")
print(f" Catalog: {catalog}")
print(f" Silver schema: {silver_schema}")
print(f" Gold schema: {gold_schema}")
from pyspark.sql import functions as F
from pyspark.sql.window import Window
silver_df = spark.table(f"{catalog}.{silver_schema}.clean_events")
print(f"Silver records: {silver_df.count()}")
daily_user_metrics = (
silver_df
.withColumn("event_date", F.to_date("timestamp"))
.groupBy("event_date", "user_id")
.agg(
F.count("*").alias("event_count"),
F.sum("amount").alias("total_amount"),
F.avg("amount").alias("avg_amount"),
F.sum("quantity").alias("total_quantity"),
F.countDistinct("event_id").alias("unique_events"),
F.min("timestamp").alias("first_event_time"),
F.max("timestamp").alias("last_event_time")
)
.withColumn("gold_created_at", F.current_timestamp())
)
print(f"Daily user metrics: {daily_user_metrics.count()} records")