Streaming Pipelines in Azure
Every business eventually needs to know what is happening right now, not what happened yesterday. Whether it is detecting fraudulent transactions within seconds, monitoring sensor readings from industrial equipment, or updating a real-time leaderboard for a game, streaming pipelines process data continuously as it arrives rather than waiting for a batch window. Azure has a rich set of services for building these pipelines, and the right choice depends on your latency requirements, team skills, and the complexity of your transformation logic.
The anatomy of a streaming pipeline
All streaming pipelines share the same logical structure, regardless of which Azure services you use:
Event Sources Ingestion Processing Serving
────────────── ──────────────── ───────────────────── ──────────────
IoT sensors → Azure Event Hubs → Stream Analytics → Power BI
Web clickstream Azure IoT Hub Spark Structured Azure SQL DB
Mobile events Service Bus Streaming CosmosDB
Log streams Kafka (HDI/ADB) Azure Functions ADLS Gen2
Azure Data Explorer Synapse poolThe ingestion layer buffers incoming events so the processing layer can consume them at its own pace. The processing layer applies transformations, filters, aggregations, and enrichments. The serving layer makes results available to downstream consumers — dashboards, APIs, databases, or additional pipelines.
Azure Event Hubs as the streaming ingestion layer
Azure Event Hubs is the standard ingestion buffer for high-throughput streaming in Azure. It accepts millions of events per second, retains them for 1–90 days, and allows multiple consumer groups to read the same stream independently. Think of it as a managed Kafka — and in fact, Event Hubs exposes a Kafka-compatible endpoint so Kafka producers and consumers work without code changes.
For a detailed explanation of Event Hubs’ internal model, see the Azure Event Hubs Overview page. For this page, we focus on how Event Hubs plugs into a streaming processing pipeline.
Azure Stream Analytics: SQL for streaming data
Stream Analytics is the simplest path to streaming analytics. You write a SQL-like query that continuously processes events from an input (Event Hubs, IoT Hub, or Blob Storage) and writes results to an output (Power BI, Azure SQL, Cosmos DB, ADLS, etc.). There is no cluster to manage — Stream Analytics is fully serverless.
-- Stream Analytics query: detect anomalous sensor readings
-- Input: 'SensorEvents' from Event Hubs
-- Output: 'AlertsDB' to Azure SQL Database
SELECT
IoTHub.ConnectionDeviceId AS device_id,
AVG(temperature) AS avg_temp_5min,
MAX(temperature) AS max_temp_5min,
MIN(temperature) AS min_temp_5min,
COUNT(*) AS reading_count,
System.Timestamp() AS window_end
INTO
AlertsDB
FROM
SensorEvents TIMESTAMP BY event_time
GROUP BY
IoTHub.ConnectionDeviceId,
TumblingWindow(minute, 5)
HAVING
AVG(temperature) > 80
OR MAX(temperature) > 95;This query groups sensor readings into 5-minute tumbling windows and outputs device averages only when temperature is dangerously high. The query evaluates continuously — new results emit to AlertsDB every 5 minutes for any device exceeding the threshold.
Stream Analytics supports three window types:
- Tumbling window — fixed, non-overlapping windows (e.g., 5-minute blocks)
- Hopping window — overlapping windows with a fixed hop (e.g., compute the last 10 minutes every 5 minutes)
- Sliding window — a window that slides forward as new events arrive, evaluating continuously
Spark Structured Streaming: full code flexibility
When your streaming transformation logic is too complex for SQL (scoring events with an ML model, joining a stream with a slowly-changing dimension, maintaining complex stateful aggregations), use Spark Structured Streaming on Synapse or Databricks.
Spark Structured Streaming reads from Event Hubs or Kafka in micro-batches, processes with the full PySpark API, and writes to Delta Lake or other sinks with exactly-once semantics.
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, from_json, window, avg, count
from pyspark.sql.types import StructType, StructField, StringType, DoubleType, TimestampType
spark = SparkSession.builder.getOrCreate()
# Define the schema of event payloads
event_schema = StructType([
StructField("device_id", StringType(), True),
StructField("temperature", DoubleType(), True),
StructField("humidity", DoubleType(), True),
StructField("event_time", TimestampType(), True)
])
# Read from Azure Event Hubs
# Connection string comes from Spark config or Azure Key Vault secret scope
connection_string = spark.conf.get("spark.eventhubs.connectionString")
eh_conf = {
"eventhubs.connectionString": sc._jvm.org.apache.spark.eventhubs.EventHubsUtils.encrypt(
connection_string
)
}
raw_stream = (
spark.readStream
.format("eventhubs")
.options(**eh_conf)
.load()
)
# Parse the binary body into JSON
parsed = raw_stream.select(
from_json(col("body").cast("string"), event_schema).alias("data"),
col("enqueuedTime")
).select("data.*", "enqueuedTime")
# Compute 5-minute averages with watermark for late data handling
aggregated = (
parsed
.withWatermark("event_time", "10 minutes")
.groupBy(
col("device_id"),
window(col("event_time"), "5 minutes")
)
.agg(
avg("temperature").alias("avg_temperature"),
avg("humidity").alias("avg_humidity"),
count("*").alias("reading_count")
)
)
# Write to Delta Lake with checkpointing for fault tolerance
query = (
aggregated.writeStream
.format("delta")
.outputMode("append")
.option("checkpointLocation", "abfss://checkpoints@storage.dfs.core.windows.net/sensor_agg/")
.start("abfss://silver@storage.dfs.core.windows.net/sensor_aggregates/")
)
query.awaitTermination()The checkpoint location is critical for fault tolerance. Structured Streaming uses it to record exactly where in the Event Hubs partition stream it last successfully read. If the job restarts, it picks up from the checkpoint rather than reprocessing old events or missing events that arrived during downtime.
Lambda vs Kappa architecture
When you have both batch historical data and real-time streaming data, you face an architectural choice: the Lambda architecture or the Kappa architecture.
The Lambda architecture maintains two separate systems: a batch layer (ADF + Synapse processing yesterday’s data) and a speed layer (Stream Analytics or Structured Streaming processing today’s data in real time). Results are merged at the serving layer. This is reliable but complex — you maintain two codebases doing similar transformations.
The Kappa architecture uses only a streaming system. Historical reprocessing is done by replaying stored events through the same streaming pipeline. Delta Lake makes this practical in Azure: events are stored in the Event Hubs capture feature (which writes raw events to ADLS as Avro files), and when you need to reprocess history, you replay those captured files through the Structured Streaming job.
| Architecture | Complexity | Consistency | Best for |
|---|---|---|---|
| Lambda | High (two systems) | Can have small discrepancies between batch and speed layers | Teams that already have a mature batch system and are adding streaming |
| Kappa | Lower (one system) | Single source of truth | Greenfield architectures, teams comfortable with Spark Structured Streaming |
Azure Functions for lightweight event processing
For simple event transformations, Azure Functions with an Event Hubs trigger is the lightest-weight option. Each function invocation processes one batch of events from a partition:
# Azure Function triggered by Event Hubs
# Processes events and writes anomalies to Cosmos DB
import azure.functions as func
import json
import logging
from azure.cosmos import CosmosClient
def main(events: func.EventHubEvent, anomalies: func.Out[func.DocumentList]):
results = []
for event in events:
body = json.loads(event.get_body().decode('utf-8'))
if body.get('temperature', 0) > 90:
anomaly = {
"id": event.sequence_number,
"device_id": body["device_id"],
"temperature": body["temperature"],
"detected_at": event.enqueued_time.isoformat(),
"partition_key": body["device_id"]
}
results.append(anomaly)
logging.warning(f"Anomaly detected: device {body['device_id']}, temp {body['temperature']}")
if results:
anomalies.set(func.DocumentList(results))Azure Functions is best for simple, stateless event processing — filtering, enrichment, and routing. For stateful aggregations, windowing, or complex transformations, use Stream Analytics or Spark Structured Streaming.
Common mistakes
- Not handling late-arriving data. Events can arrive out of order due to network delays or client retries. Stream Analytics and Structured Streaming both support late arrival tolerance through watermarks. If you do not configure this, late events are silently dropped from windowed aggregations.
- Using a single Event Hubs partition for a high-throughput stream. One partition can handle about 1 MB/s inbound. If you expect more traffic, pre-provision enough partitions. You cannot reduce the number of partitions after creation (you can only increase up to 32 partitions for Standard tier).
- Forgetting to save Structured Streaming checkpoints to durable storage. If checkpoints are saved to ephemeral local storage (the worker node’s local disk), a job restart loses the checkpoint and the job either reprocesses everything or skips events. Always point checkpoints to ADLS Gen2.
- Using Stream Analytics for complex ML-based event scoring. Stream Analytics SQL does not natively support custom ML model inference. Use Spark Structured Streaming or Azure Functions if you need to score events against a trained model.
Summary
- Streaming pipelines process data continuously as it arrives, with seconds or sub-second latency — unlike batch pipelines that run on schedules.
- Azure Event Hubs buffers incoming events; Stream Analytics or Spark Structured Streaming processes them; databases, Delta Lake, or Power BI serve results.
- Stream Analytics is serverless SQL-based streaming — ideal for simple windowed aggregations with no code overhead.
- Spark Structured Streaming handles complex transformations, stateful operations, and ML scoring in Python/PySpark.
- Always configure watermarks for late data tolerance and save checkpoints to ADLS Gen2 for fault-tolerant streaming jobs.
Frequently asked questions
What is the difference between batch and streaming pipelines?
Batch pipelines process a finite set of data at scheduled intervals — for example, processing all orders from yesterday at 2 AM. Streaming pipelines process data continuously as it arrives, with latency measured in seconds or milliseconds. Streaming is better for real-time dashboards, fraud detection, and IoT monitoring; batch is simpler and cheaper for reporting workloads that do not need up-to-the-minute data.
What is Azure Stream Analytics?
Azure Stream Analytics is a fully managed, serverless real-time analytics service. You write SQL-like queries over streaming data from Event Hubs, IoT Hub, or Blob Storage, and it continuously evaluates those queries as new events arrive. No code or infrastructure to manage — just write the SQL and define the output destination.
Can I use Spark Structured Streaming for real-time pipelines?
Yes. Spark Structured Streaming on Synapse Spark or Databricks processes streaming data as micro-batches or in continuous mode. It reads from Event Hubs or Kafka, processes with the full power of PySpark (complex transformations, ML model scoring, joins with static reference data), and writes to Delta Lake, ADLS, or Synapse SQL pools.