This guide demonstrates how to build a streaming data ingestion pipeline using Databricks Auto Loader and Structured Streaming.
The pipeline continuously ingests order item data from an Azure Data Lake Storage (ADLS) Gen2 landing zone into the Bronze layer of the Medallion Architecture.
The notebook performs the following tasks:
_rescued_data column.brz_order_items Delta table in the Bronze layer.The resulting Bronze table stores the raw ingested data along with metadata, providing a reliable foundation for downstream cleansing and transformation in the Silver layer.
from pyspark.sql.types import StructType, StructField, StringType, IntegerType, DateType, BooleanType from pyspark.sql import functions as F
This step creates Databricks notebook widgets to define configurable parameters used throughout the notebook. These parameters allow the notebook to be reused across different environments without modifying the source code.
Purpose:
dbutils.widgets.text("catalog_name", "ecommerce", "Catalog Name")
dbutils.widgets.text("storage_account_name", "stecommercedeveastus001", "Storage Account Name")
dbutils.widgets.text("container_name", "ecomm-raw-data", "Container Name")
This step retrieves the values entered through the Databricks notebook widgets and stores them in Python variables. These configuration values are then used throughout the notebook to access the appropriate Unity Catalog, Azure Storage Account, and Blob Storage container. The retrieved values are printed to verify that the notebook has been configured correctly.
Purpose:
catalog_name = dbutils.widgets.get("catalog_name")
storage_account_name = dbutils.widgets.get("storage_account_name")
container_name = dbutils.widgets.get("container_name")
print(catalog_name, storage_account_name, container_name)
This step defines the Azure Data Lake Storage (ADLS) Gen2 paths used throughout the streaming pipeline. It creates the source path for reading incoming order item data from the landing zone and specifies the checkpoint location used to maintain the streaming application’s state and progress.
Purpose:
# -------------------------
# Azure Data Lake Storage - ADLS Gen2
# -------------------------
adls_path = f"abfss://{container_name}@{storage_account_name}.dfs.core.windows.net/order_items/landing/"
# Checkpoint folders for streaming (bronze, silver, gold)
bronze_checkpoint_path = f"abfss://{container_name}@{storage_account_name}.dfs.core.windows.net/checkpoint/bronze/fact_order_items/"
This step uses Databricks Auto Loader with Structured Streaming to continuously ingest CSV files from the ADLS Gen2 landing zone into the Bronze Delta table. Auto Loader automatically detects new files, infers the schema, tracks schema changes, and stores checkpoint information to ensure reliable and fault-tolerant data ingestion.
Additional metadata, such as the ingestion timestamp and source file path, is captured to support data lineage and auditing. The streaming job processes all available files and writes the ingested records to the Bronze layer in Delta format.
Purpose:
_rescued_data column.| Code | Why it is Used |
|---|---|
spark.readStream |
Creates a streaming DataFrame to continuously read and process new files as they arrive in the source directory. |
.format("cloudFiles") |
Uses Databricks Auto Loader, which efficiently discovers and ingests new files from cloud storage without requiring manual file management. |
.option("cloudFiles.format", "csv") |
Specifies that the source data is stored in CSV format. |
.option("cloudFiles.schemaLocation", bronze_checkpoint_path) |
Stores the inferred schema metadata so Auto Loader can detect and manage schema changes over time. |
.option("cloudFiles.schemaEvolutionMode", "rescue") |
Prevents the stream from failing when new or unexpected columns appear by storing them in the _rescued_data column. |
.option("header", "true") |
Uses the first row of each CSV file as the column names. |
.option("cloudFiles.inferColumnTypes", "true") |
Automatically infers the appropriate data type for each column instead of reading all values as strings. |
.option("rescuedDataColumn", "_rescued_data") |
Captures malformed or unexpected data in a separate column for later analysis without interrupting the ingestion process. |
.option("cloudFiles.includeExistingFiles", "true") |
Processes both existing files already present in the source folder and any new files that arrive later. |
.option("pathGlobFilter", "*.csv") |
Restricts processing to files with the .csv extension, ignoring all other file types. |
.load(adls_path) |
Loads the streaming data from the specified ADLS Gen2 landing directory. |
.withColumn("_ingest_time", F.current_timestamp()) |
Adds an ingestion timestamp to each record, making it possible to track when the data entered the pipeline. |
.withColumn("_source_file", F.col("_metadata.file_path")) |
Captures the source file path for each record, supporting data lineage, auditing, and troubleshooting. |
.writeStream |
Starts the streaming write operation to the destination. |
.outputMode("append") |
Writes only newly processed records to the target Delta table, preserving existing data. |
.option("checkpointLocation", bronze_checkpoint_path) |
Stores checkpoint information to track stream progress and enable fault tolerance and exactly-once processing if the stream is restarted. |
.trigger(availableNow=True) |
Processes all available files in a single execution and then stops, making it suitable for scheduled or batch-style streaming jobs. |
.toTable(f"{catalog_name}.bronze.brz_order_items") |
Writes the processed streaming data directly into the Bronze Delta table stored in Unity Catalog. |
.awaitTermination() |
Waits for the streaming query to complete before allowing the notebook to continue or terminate. |
spark.readStream \
.format("cloudFiles") \
.option("cloudFiles.format", "csv") \
.option("cloudFiles.schemaLocation", bronze_checkpoint_path) \
.option("cloudFiles.schemaEvolutionMode", "rescue") \
.option("header", "true") \
.option("cloudFiles.inferColumnTypes", "true") \
.option("rescuedDataColumn", "_rescued_data") \
.option("cloudFiles.includeExistingFiles", "true") \
.option("pathGlobFilter", "*.csv") \
.load(adls_path) \
.withColumn("_ingest_time", F.current_timestamp()) \
.withColumn("_source_file", F.col("_metadata.file_path")) \
.writeStream \
.outputMode("append") \
.option("checkpointLocation", bronze_checkpoint_path) \
.trigger(availableNow=True) \
.toTable(f"{catalog_name}.bronze.brz_order_items") \
.awaitTermination()
display(spark.sql(f"SELECT min(dt), max(dt) FROM {catalog_name}.bronze.brz_order_items"))
display(
spark.sql(
f"SELECT count(*) FROM CLOUD_FILES_STATE('abfss://{container_name}@{storage_account_name}.dfs.core.windows.net/checkpoint/bronze/fact_order_items/')"
)
)
