停止編寫自訂檔案監聽器,改用 Databricks Auto Loader
Stop Writing Custom File Listeners: Use Auto Loader Instead
Databricks Auto Loader 可取代自訂檔案監聽器,負責串流檔案的發現和狀態管理。它可透過雲端原生通知服務偵測新檔案,並自動追蹤結構變更;無法依據結構解析的資料則可保存在 `_rescued_data` 欄位,避免管道因此失敗。
You ship the job. It passes CI. Then, a source system upstream changes its naming convention, or a vendor pushes a malformed JSON file at 3 AM. Your custom file listener, built on top of listStatus or a polling mechanism, silently skips the file or crashes the entire streaming micro-batch. You are now spending your morning hunting down missing records in S3 or ADLS.
The industry has spent years reinventing the wheel with custom foreachBatch logic and manual checkpointing to track which files have been processed. Databricks Auto Loader (cloudFiles) exists to solve exactly this by offloading the file discovery and state management to the cloud provider's native notification services.
Prerequisites
To follow this tutorial, you need:
- A Databricks workspace (AWS, Azure, or GCP).
- Access to a UC-enabled catalog to store the target Delta table.
- A local environment or Databricks notebook with a Spark runtime (Databricks Runtime 10.4 LTS or higher is recommended for full feature support).
- Basic permissions to read from a cloud storage bucket (e.g., S3, ADLS Gen2, GCS).
Photo by Logan Voss on Unsplash
Step 1: Configuring the Stream with CloudFiles
Instead of using the standard spark.readStream.format("json"), which requires you to provide a schema upfront and assumes a stable file list, we use cloudFiles. This instructs Databricks to use native file notification services (like SQS/SNS for AWS or Event Grid for Azure) to detect new files.
# Hypothetical setup: reading JSON files from a landing zone
cloud_source = "s3://your-landing-bucket/raw-data/"
checkpoint_path = "s3://your-checkpoint-bucket/checkpoints/raw-data/"
df = (spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "json")
.option("cloudFiles.schemaLocation", checkpoint_path) # Essential for schema inference
.load(cloud_source))
# This DataFrame is now lazily evaluated and ready to write
By setting cloudFiles.schemaLocation, you trigger Auto Loader’s schema evolution engine. It will infer the schema from the files and store it in the checkpoint location. When a new file arrives with a different structure, Auto Loader tracks those changes automatically.
Step 2: Rescuing Malformed Data
In regulated environments, you cannot simply drop data that doesn't match your schema. If a field changes type—for example, a transaction_amount shifts from an integer to a string—you need a way to capture the "bad" data without stopping the pipeline.
Enable the "rescued data" column to capture any data that cannot be parsed into your defined schema.
df_rescued = (spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "json")
.option("cloudFiles.inferColumnTypes", "true")
.option("cloudFiles.schemaEvolutionMode", "addNewColumns")
.option("cloudFiles.rescuedDataColumn", "_rescued_data") # The magic column
.load(cloud_source))
# Write to a Delta table
query = (df_rescued.writeStream
.option("checkpointLocation", checkpoint_path)
.table("main.raw.transactions"))
When a record fails to map to your inferred schema, it isn't discarded. Instead, it is packaged into the _rescued_data column as a JSON string. Your data analysts can then query SELECT * FROM main.raw.transactions WHERE _rescued_data IS NOT NULL to identify and fix upstream issues without the pipeline ever failing.
Step 3: Optimizing for Performance
Auto Loader defaults to using file notification mode (cloudFiles.useNotifications). This is the most efficient method because it avoids expensive directory listings. In large buckets with millions of files, a listStatus call (the standard Spark approach) can time out or incur significant API costs.
However, if you are testing locally or in a sandbox without sufficient cloud permissions to create notification resources, you can fall back to fileDiscoveryMode.
# Use this only if you cannot configure cloud notifications
df = (spark.readStream
.format("cloudFiles")
.option("cloudFiles.format", "json")
.option("cloudFiles.useNotifications", "false") # Fallback mode
.load(cloud_source))
Mechanism Check: When useNotifications is true, Databricks creates an SQS queue (on AWS) or Event Grid (on Azure) to listen for storage events. This means the ingestion latency is near-instant, regardless of how many files exist in the bucket. When false, Spark performs a recursive list of the directory, which scales linearly with the number of files, leading to performance degradation as your volume grows.
Step 4: Testing Schema Evolution
To verify that your pipeline handles structural changes, simulate a schema drift. Create a JSON file with an unexpected column and drop it into your landing directory.
# Illustrative: Writing a file to trigger schema evolution
import json
new_data = {"id": 101, "amount": 50.0, "new_feature_flag": True}
dbutils.fs.put(f"{cloud_source}/drift_test.json", json.dumps(new_data), True)
Because you set cloudFiles.schemaEvolutionMode to addNewColumns, the Delta table will automatically alter its schema to include new_feature_flag. If you prefer a stricter approach, use failOnLoad to force the job to stop when the schema changes, preventing "dirty" data from entering your production tables.
Photo by Tania Melnyczuk on Unsplash
Common pitfalls
- Checkpoint location is not optional: Auto Loader stores the state of "seen" files in the checkpoint directory. If you delete this directory, the ingestion will restart from the beginning, leading to duplicate records in your Delta table. Ensure your checkpoint paths are managed with the same lifecycle policies as your production code.
-
Permission Bloat: In production, ensure the IAM role or Service Principal running the Databricks cluster has
PutObjectandListBucketpermissions for the storage, but also permissions to create/delete queues and subscriptions if usinguseNotifications. If these are restricted, the pipeline will fail at startup. -
Over-inferring Types: Schema inference is convenient but not free. It samples the files to determine data types. On very large, messy datasets, this can add latency to the stream startup. If you know your schema, define it explicitly using
cloudFiles.schema. -
Case Sensitivity: Auto Loader is case-insensitive by default regarding file names, but schema inference can be sensitive to JSON key casing. Ensure your upstream source is consistent if you want to avoid fragmented column names (e.g.,
user_idvsUser_ID).
Conclusion
Building custom file listeners is a liability. By leveraging Auto Loader, you shift the burden of file discovery and schema management to a managed service designed for scale. You get the benefits of native cloud notifications, automatic schema evolution, and a safety net for malformed data—all while keeping your Spark code readable and maintainable.
Try it: Configure a small test stream using cloudFiles against a dummy S3/ADLS bucket. Drop a malformed JSON file into the bucket and observe how the _rescued_data column captures the error without interrupting the stream. Then, change the schema and watch the Delta table evolve automatically.
Cover photo by Roger Starnes Sr on Unsplash.
This article was drafted with AI assistance and is intended for technical review and fact-checking before publication.
來源:DEV Community · dev.to