Hello! Welcome to the next lesson in your journey to mastering the Databricks Lakehouse.
In our previous lesson, we established the core conceptual model of Structured Streaming: treating a continuous flow of data as a single, unbounded table. This abstraction is what allows you to write simple, batch-like queries against a never-ending data stream.
Today, we will turn that concept into code. Our goal is to read a data stream from a source (e.g., Delta table, cloud files) using spark.readStream. This is the first practical step in building any streaming pipeline, where you define your input source and create a streaming DataFrame.
The Anatomy of a Streaming Read
The entry point for all streaming queries is spark.readStream. While its batch counterpart, spark.read, reads a static dataset and terminates, spark.readStream creates a DataFrame that represents a potentially infinite stream of data.
The general syntax pattern looks like this in PySpark:
streaming_df = (spark.readStream
.format("source_format")
.option("key1", "value1")
.option("key2", "value2")
.load("/path/to/source")
)
Let's break this down:
spark.readStream: This is the explicit instruction to Spark to create a streaming source..format(...): Specifies the type of the source, such as"delta"for a Delta table or"cloudFiles"for Auto Loader..option(...): Provides specific configurations for the chosen format, like schema information or rate limits..load(...): Specifies the path to the source data. Alternatively, you can use.table(...)for sources registered in the metastore.
Now, let's see how to apply this to the two most common streaming sources you'll encounter in Databricks.
1. Ingesting Files with Auto Loader (cloudFiles)
When you need to incrementally ingest files landing in cloud storage (like S3, ADLS Gen2, or GCS), the best-practice tool in Databricks is Auto Loader. It is an optimized streaming source that efficiently discovers and processes new files as they arrive.
Under the hood, Auto Loader is implemented as a Structured Streaming source called cloudFiles. You use it by setting the format in your readStream call to "cloudFiles".
This first video provides a great overview and practical demonstration of setting up and using Auto Loader.
24 Auto Loader in Databricks | AutoLoader Schema Evolution Modes | File Detection Mode in AutoLoader
This video from Ease With Data demonstrates how to configure and run an Auto Loader stream to ingest CSV files.
Please watch two segments: 00:19 to 01:12: This introduces Auto Loader as the cloudFiles source for Structured Streaming. 05:09 to 08:28: This is the core demonstration. Pay close attention to the code and the specific options used, like cloudFiles.format and cloudFiles.schemaLocation.
As you saw, the key to using Auto Loader is format("cloudFiles") and providing a few essential options. The most important of these is cloudFiles.schemaLocation. Auto Loader uses this location to store state information about which files have been processed, a mechanism known as checkpointing. This is how it tracks the stream's progress and ensures files are processed exactly once, even if the stream stops and restarts.
The official Databricks documentation provides another clean example.
Run your first Structured Streaming workload
This documentation page shows a concise code example for reading JSON files with Auto Loader.
Review the Python code block in the section 'Use Auto Loader to read streaming data from object storage'. Notice the same pattern: format("cloudFiles"), options for the underlying file format (json in this case), and the crucial schemaLocation option.
Your Turn: Set up an Auto Loader Stream
Now it's time for some hands-on practice. In your Databricks workspace, create a new notebook and try the following.
Goal: Read a stream of CSV files using Auto Loader.
-
Prepare a Source Directory: Create a directory in your DBFS (or a volume) where you can drop files. For example,
/FileStore/streaming_input. -
Prepare Sample Data: Create a simple CSV file with a header and a few rows, and upload it to the directory you just created.
Exampledata_1.csv:id,value 1,100 2,200 -
Write the
readStreamCode: In your notebook, write the PySpark code to read from this directory. You'll need to define paths for your source data and for the checkpoint.# Define paths for your source data and checkpoint source_path = "/FileStore/streaming_input" checkpoint_path = "/tmp/ss-tutorial/_checkpoint" # Configure the Auto Loader stream streaming_df = (spark.readStream .format("cloudFiles") .option("cloudFiles.format", "csv") .option("cloudFiles.inferColumnTypes", "true") # Optional: let Auto Loader infer data types .option("header", "true") # Our CSV has a header .option("cloudFiles.schemaLocation", checkpoint_path) .load(source_path) ) # To see the stream in action, display it # Note: In a real pipeline, you would use .writeStream instead of .display display(streaming_df) -
Test the Stream: Run the cell. You should see a streaming temporary table that shows the data from
data_1.csv. Now, without stopping the stream, upload a second file (data_2.csv) to the same directory. You should see the new rows appear in the output automatically!
2. Streaming from a Delta Table
The other fundamental streaming source is a Delta table itself. This is the backbone of the medallion architecture, where you might have one stream writing to a Bronze table, and a second stream reading from that Bronze table to apply transformations and write to a Silver table.
Because Delta Lake maintains a transaction log of all changes, it is a perfect source for streaming. Spark can simply query the transaction log to discover new data that has been committed since the last micro-batch.
This video demonstrates the full cycle: creating a small Delta table, and then setting up a readStream to process it.
Advancing Spark - Databricks Delta Streaming
This clip from Advancing Analytics shows how to create a simple Delta table and then configure a spark.readStream to use it as a source.
Watch from 00:54 to 03:05. The first part shows the creation of the source Delta table. The second part shows the readStream code, where the key is format("delta") and loading the table's path.
The syntax is very straightforward. You can specify the source Delta table either by its path or, if it's registered in the metastore, by its name.
Let's look at the official documentation for the precise syntax.
Delta table streaming reads and writes
This page from the Databricks documentation provides the definitive syntax for streaming from a Delta table.
Read the short section 'Option 2: Stream from a Delta table'. It shows the two simple ways to create a streaming read: spark.readStream.table("table_name") and spark.readStream.load("/path/to/table").
Your Turn: Stream from a Delta Table
Let's apply this in your notebook.
Goal: Create a Delta table and read from it as a stream.
-
Create a Source Delta Table:
# Create a sample DataFrame data = [("a", 1), ("b", 2)] columns = ["key", "value"] source_df = spark.createDataFrame(data, columns) # Write it as a Delta table source_df.write.format("delta").mode("overwrite").saveAsTable("my_source_table") -
Write the
readStreamCode: Now, configure a stream to read from this table.# Read from the Delta table as a stream streaming_df_delta = spark.readStream.table("my_source_table") # Display the stream display(streaming_df_delta) -
Test the Stream: Run the display cell. You will see the initial two rows. Now, in a new cell, append more data to the source table and run it.
# Append new data to the source table more_data = [("c", 3), ("d", 4)] more_df = spark.createDataFrame(more_data, columns) more_df.write.format("delta").mode("append").saveAsTable("my_source_table")Go back to your streaming display. You will see the new rows from
more_dfappear in the output.
3. Important Configuration Options
When you create a streaming read, you can provide options to control its behavior. While there are many, a few are particularly useful to know about.
Delta table streaming reads and writes
Let's revisit the 'Delta table streaming reads and writes' documentation to learn about a few key options.
Please read the following two sections: 'Limit input rate': This section explains maxFilesPerTrigger, a crucial option for controlling how much data is processed in each batch. 'Specify initial position': This explains startingVersion and startingTimestamp, which let you start a stream from a specific point in a table's history instead of from the very beginning.
maxFilesPerTrigger: This is very useful for controlling costs and resource usage. It prevents your stream from being overwhelmed if a large number of files land at once.startingVersion/startingTimestamp: These are powerful for production scenarios. For example, if you need to restart a failed stream or backfill data from a certain point in time, you can use these options to avoid reprocessing the entire table history.
Conclusion
In this lesson, you've moved from theory to practice. You now know how to create a streaming DataFrame—the fundamental building block of a Structured Streaming application—from the two most important data sources in the Databricks ecosystem.
Key Takeaways
- You start a streaming query with
spark.readStream. - To ingest files incrementally from cloud storage, you use Auto Loader by setting
.format("cloudFiles")and specifying acloudFiles.schemaLocation. - To stream changes from a Delta table, you set
.format("delta")and use.load("/path/to/table")or.table("table_name"). - You can control the behavior of a streaming read using options like
maxFilesPerTriggerto limit the batch size orstartingVersionto process data from a specific point in time.
Next Steps
Reading data is the first half of the equation. The second half is writing it somewhere. In our next lesson, Write streaming query results to a Delta table sink using spark.writeStream with checkpointing, we will learn how to take the streaming DataFrames we created today and write their output to a destination table, completing our first end-to-end streaming pipeline.