Event Driven Compute with Azure Functions
Azure Functions shines brightest in event-driven architectures — where code executes in response to something happening rather than on a schedule or a direct request. This page focuses on the four most important event sources for Functions: Event Hubs (high-volume streaming), Service Bus (reliable messaging), Event Grid (Azure resource events and custom events), and Storage Queues (simple async task distribution).
The event-driven pattern
In a traditional request-response architecture, your code runs when a user or system calls it directly. In an event-driven architecture, your code runs when something happens: a file is uploaded, an order is placed, a sensor sends a reading, a resource changes state.
Azure Functions makes this pattern simple. You declare what event should trigger your function, and Azure handles the infrastructure: polling the queue, maintaining consumer group state on Event Hubs, and routing Event Grid subscriptions to your endpoint. Your code focuses only on processing the event.
The result is a system that reacts to load automatically — when a thousand events arrive simultaneously, Azure scales out multiple function instances in parallel. When nothing is happening, the Consumption plan scales to zero and you pay nothing.
Event Hubs trigger: high-volume streaming
Event Hubs is Azure’s managed event streaming platform — similar to Apache Kafka. It ingests millions of events per second from IoT devices, application telemetry, clickstreams, or any high-volume source.
A Functions trigger for Event Hubs processes batches of events from each partition:
import azure.functions as func
import json
import logging
app = func.FunctionApp()
@app.event_hub_message_trigger(
arg_name="events",
event_hub_name="telemetry",
connection="EventHubConnectionString",
cardinality="many" # Process events in batches
)
def process_telemetry(events: list[func.EventHubEvent]) -> None:
for event in events:
body = json.loads(event.get_body().decode('utf-8'))
device_id = body.get('deviceId')
temperature = body.get('temperature')
logging.info(f"Device {device_id}: {temperature}°C")
# Store to Cosmos DB, trigger alerts, etc.The cardinality=“many” setting passes all events in a batch together, which is more efficient than processing them one at a time. Tune the batch size and prefetch count in host.json to optimise throughput:
{
"extensions": {
"eventHubs": {
"batchCheckpointFrequency": 5,
"eventProcessorOptions": {
"maxBatchSize": 64,
"prefetchCount": 256
}
}
}
}Service Bus trigger: reliable task processing
Service Bus is the right trigger for workloads where every message must be processed exactly once, delivery must be reliable, and message order can matter. The classic use case is a task queue: an API writes a “send invoice email” message to Service Bus, and a Function processes it.
@app.service_bus_queue_trigger(
arg_name="msg",
queue_name="invoice-queue",
connection="ServiceBusConnectionString"
)
def process_invoice(msg: func.ServiceBusMessage) -> None:
order_id = msg.correlation_id
payload = json.loads(msg.get_body().decode('utf-8'))
try:
send_invoice_email(
to=payload['customer_email'],
order_id=order_id,
amount=payload['total']
)
logging.info(f"Invoice sent for order {order_id}")
# Message is automatically completed on successful return
except EmailDeliveryError as e:
logging.error(f"Failed to send invoice: {e}")
# Raise the exception — message returns to queue for retry
raiseService Bus handles the retry logic: if the function raises an exception, the message is unlocked and retried after the lock timeout. After the configured maximum delivery count (default 10), the message moves to the dead-letter queue for investigation.
For topics (pub/sub patterns), use service_bus_topic_trigger to subscribe a function to a topic subscription — multiple functions can subscribe to the same topic and each gets an independent copy of every message.
Event Grid trigger: reacting to Azure events
Event Grid is Azure’s event routing service. Azure services publish events to Event Grid automatically: Blob Storage publishes events when blobs are created or deleted, Resource Manager publishes events when resources are created or modified, and your application can publish custom events.
A common pattern is triggering image processing when a blob is uploaded to Storage:
@app.event_grid_trigger(arg_name="event")
def process_blob_upload(event: func.EventGridEvent) -> None:
logging.info(f"Event type: {event.event_type}")
if event.event_type == "Microsoft.Storage.BlobCreated":
blob_url = event.data.get('url')
content_type = event.data.get('contentType')
if content_type.startswith('image/'):
logging.info(f"Processing new image: {blob_url}")
resize_and_optimise_image(blob_url)Event Grid subscriptions can filter events by type and subject pattern. You can subscribe only to events matching *.jpg in a specific container rather than receiving every blob event from an entire storage account.
Storage Queue trigger: simple async work
Azure Storage Queues are the simplest queue option — no ordering guarantees, at-least-once delivery, and lower throughput than Service Bus. They are fine for background tasks where occasional duplicate processing is acceptable and reliability requirements are moderate.
@app.queue_trigger(
arg_name="msg",
queue_name="resize-queue",
connection="AzureWebJobsStorage"
)
def resize_image_task(msg: func.QueueMessage) -> None:
task = json.loads(msg.get_body().decode('utf-8'))
source_url = task['source_url']
target_width = task['target_width']
logging.info(f"Resizing image {source_url} to {target_width}px")
resize_image(source_url, target_width)
# Message is automatically deleted on successful returnStorage Queue Functions are cheap, stateless, and simple to reason about. They are a good starting point for teams new to event-driven architectures — upgrade to Service Bus only when you need ordering, dead-lettering, sessions, or higher throughput than queues provide.
How functions scale with events
Azure Functions uses KEDA (Kubernetes-based Event Driven Autoscaling) internally to scale instances based on the depth of the event source backlog:
- Queue and Service Bus triggers: Scale instances based on message count. When the queue has 100 messages, Azure scales out to process them in parallel (up to the configured max instances).
- Event Hubs trigger: Scale to at most the number of partitions in the Event Hub. Each instance processes one partition exclusively.
- HTTP trigger: Scale based on concurrent request count.
- Timer trigger: Always single instance — one function runs the scheduled job, regardless of backlog.
On the Premium and Dedicated plans, maximum instances are configurable. On the Consumption plan, the default maximum is 200 instances. For Event Hubs workloads needing more than 32 parallel instances, increase the partition count in the Event Hub.
Common event-driven Function mistakes
- Not handling duplicate messages. Event Hubs and Storage Queues deliver messages at least once — your function may process the same message twice during retry scenarios. Design your processing to be idempotent: processing the same message twice should produce the same result as processing it once. Track processed message IDs in a database if necessary.
- Long processing time per message with no checkpoint. If your function processes an Event Hubs batch and fails halfway through, the entire batch is replayed from the last checkpoint. Use stateful processing patterns or write intermediate results to storage so replayed messages can detect and skip already-processed events.
- Not monitoring the dead-letter queue. Service Bus dead-lettered messages represent work that permanently failed. Without a dead-letter monitor, you silently lose completed business operations. Set up alerting when the dead-letter queue grows above zero.
Summary
- Event Hubs trigger: high-volume streaming data, batch processing per partition, scales to partition count.
- Service Bus trigger: reliable task queues with retries, dead-lettering, and at-least-once delivery. Best for business-critical message processing.
- Event Grid trigger: react to Azure resource events (blob created, resource changed) or custom events published by your application.
- Storage Queue trigger: simple async work distribution. Lower reliability guarantees than Service Bus — upgrade when those limits become problems.
- Design function processing to be idempotent — duplicate message delivery is normal in event-driven systems.
Frequently asked questions
What is the difference between Event Hubs trigger and Service Bus trigger for Functions?
Event Hubs is designed for high-volume telemetry and log streaming — millions of events per second, retained for up to 90 days, consumed by multiple consumers independently. Service Bus is designed for reliable message delivery with ordering, deduplication, dead-lettering, and at-most-once or at-least-once delivery guarantees. Use Event Hubs for streaming data ingestion; use Service Bus for task queues and command processing where reliability matters.
How many function instances scale out when using an Event Hubs trigger?
Up to the number of partitions in the Event Hub. An Event Hub with 32 partitions can scale to at most 32 function instances simultaneously (one instance per partition). To increase parallelism beyond that, increase the partition count — but partition count cannot be reduced after creation, so plan conservatively and increase as needed.
What happens if my Function fails while processing a Service Bus message?
The message returns to the queue after the lock timeout expires and is retried. After a configured number of retries (default 10 for Service Bus), the message is moved to the dead-letter sub-queue. Configure a dead-letter handler function (or alerting) to investigate and replay messages that exhaust retries. This prevents silent message loss.