The EventBridge default bus is a shared resource, and using it for everything is a recipe for noisy, unpredictable event streams where your critical events get lost in the crowd.
Let’s see what happens when we send an event to the default bus and then try to capture it.
{
"version": "0.3",
"id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"detail-type": "EC2 Instance State-change Notification",
"source": "aws.ec2",
"account": "111122223333",
"time": "2023-10-27T10:00:00Z",
"region": "us-east-1",
"resources": [
"arn:aws:ec2:us-east-1:111122223333:instance/i-0123456789abcdef0"
],
"detail": {
"instance-id": "i-0123456789abcdef0",
"state": "running"
}
}
This event, a standard EC2 instance state change, could be sent from your EC2 instance’s IAM role or a Lambda function. Now, imagine you have several rules on your default bus. One rule might be for EC2 Instance State-change Notification events to trigger a shutdown script. Another might be for S3 Object Created events to trigger a virus scan. A third could be for API Gateway requests to log access patterns.
When the EC2 event arrives, EventBridge matches it against all these rules. If your shutdown script rule is configured to match source: "aws.ec2" and detail-type: "EC2 Instance State-change Notification", it will be triggered. This seems straightforward.
The problem arises when you have many sources and many consumers all dumping onto this single, shared default bus. What if another AWS service, or even another team in your organization, starts sending events with a similar source or detail-type? For instance, a new custom application might start sending events with source: "my.app" and detail-type: "UserLoggedIn". If you have a rule on your default bus that also matches source: "my.app" but for a different detail-type, your legitimate events could be consumed by the wrong target, or worse, your critical shutdown event might be drowned out by a flood of less important events from another source. The default bus becomes a noisy, undifferentiated firehose.
This is where custom event buses come in. A custom event bus is a dedicated, private event stream. You create it, and only events explicitly sent to that specific bus will ever arrive. This isolation is the key.
Consider your critical shutdown script. Instead of placing its rule on the default bus, you create a custom bus named critical-ops-bus. Your EC2 instances, or the systems responsible for triggering shutdown events, are configured to PutEvents directly to critical-ops-bus.
{
"Entries": [
{
"Source": "aws.ec2",
"DetailType": "EC2 Instance State-change Notification",
"Detail": "{\"instance-id\": \"i-0123456789abcdef0\", \"state\": \"stopping\"}",
"EventBusName": "critical-ops-bus"
}
]
}
On this critical-ops-bus, you create a single rule that matches source: "aws.ec2" and detail-type: "EC2 Instance State-change Notification", targeting your shutdown Lambda function. Now, only events sent to critical-ops-bus are even considered by this rule. The noise from other services and applications is completely filtered out because they’re sending to their own dedicated buses or the default bus, not yours.
The mental model is simple: the default bus is like a public square where anyone can shout anything. Custom buses are like private meeting rooms where only invited guests can speak, and their conversations are isolated.
When you create a custom bus, you’re essentially defining a distinct channel for a specific set of events. This allows you to:
- Isolate Event Streams: Prevent event storms from one application from impacting unrelated consumers.
- Control Access: Define IAM policies to restrict which AWS accounts or principals can publish events to your custom bus.
- Simplify Rule Management: Rules on a custom bus only need to consider events intended for that bus, making them less prone to unintended matches.
To send an event to a custom bus, you use the PutEvents API, specifying the EventBusName. For example, using the AWS CLI:
aws events put-events \
--entries '[
{
"Source": "my.custom.app",
"DetailType": "UserLoggedIn",
"Detail": "{\"user_id\": \"user-123\", \"timestamp\": \"2023-10-27T10:05:00Z\"}",
"EventBusName": "user-activity-bus"
}
]'
The EventBusName parameter is crucial here. If you omit it, the event goes to the default event bus.
A common pattern is to use custom buses for microservices. Each microservice can have its own bus for internal events, or a dedicated bus for events that need to be consumed by other services. For example, an order-service might publish OrderCreated events to an orders-bus, which then triggers downstream consumers like an inventory-service and a notification-service via rules on that bus.
The surprising thing is how often the default bus is used for critical production traffic, leading to subtle but persistent issues with event delivery and fan-out. People often don’t realize the default bus is a shared tenancy resource, much like a multi-tenant database, and its performance and reliability are subject to the behavior of all other users.
The primary levers you control are the EventBusName when publishing events and the EventBusName when creating rules. By strategically using custom buses, you gain predictability and resilience. You can even set up event buses in one AWS account and have other accounts send events to them, facilitating cross-account event routing.
Once you’ve successfully isolated your critical events onto custom buses and your rules are firing reliably, the next challenge is managing the lifecycle and auditing of events across multiple buses.