TL;DR: A single Azure Service Bus topic can be a reasonable starting point for one-to-many messaging. The topic receives published messages, and subscriptions provide separate delivery views for consumers. As the number of subscriptions, filters, and messages grows, the shared topic can become a place where routing work and storage pressure meet. Splitting event streams across topics may help, but adding entities alone does not accelerate a system. This post is part of a short series on Azure...
Azure Service Bus: Count your filters
TL;DR: Azure Service Bus can publish one message to a topic and deliver it to multiple consumers. A topic is the shared destination, a subscription is a consumer-specific view, and a filter decides whether a message is copied to that subscription. In high-throughput systems, the number of subscription rules can matter more than the filter type itself. This is the first post in a short series on Azure Service Bus topology under load. The series looks at filter count, subscription fan-out (one...
Keep the gains: performance regression testing without fooling yourself
TL;DR: Keep only the benchmarks that protect important hot paths. Use baseline/diff comparisons to catch regressions, but do not trust noisy shared runners blindly. Performance regression testing needs stable machines, clear thresholds, and restraint. After a successful performance investigation, the benchmark folder looks valuable. It contains the history of the work: experiments, false starts, before-and-after comparisons, warmup cases, exception cases, and small probes that helped explain...
Event Sourcing: undo set-remove-based bi-temporal events – because we must
In part thirteen of this event sourcing series, we expand on set-remove-based bi-temporal events. In certain scenarios, we need to be able to undo events – typically because a user made a mistake. I’ll show you two approaches to solve this issue, why the “obvious” simple approach fails, and why we prefer one over the other.
Event Sourcing: set-and-remove-based bi-temporal events
We return to bi-temporal event sourcing in part twelve of my series on event sourcing. This time, we look at set-and-remove-based bi-temporal events – the second variant of bi-temporal events in our system. You’ll see why they are needed, and what additional problems this kind of event brings to the table.
The scheduler is part of your message pump
TL;DR: The async model is a contract. In .NET, async methods are expected to do little CPU work, spend most of their time awaiting input/output, and make long-running CPU work explicit. Lease renewal depends on that contract. If a handler hides blocking or CPU-heavy work inside an async method, the scheduler assumptions behind renewal can fall apart. The message pump depends on the broker API, but it also depends on the runtime’s execution model. After publishing the previous article...
When the handler outlives the lease
TL;DR: A visibility timeout is a lease. It tries to keep two consumers from working on the same queue message at the same time, but only for a limited period. If message handling takes longer than that period, the pump must renew the lease while business code continues to run. That sounds like a small addition until renewal needs scheduling, cancellation, shutdown behavior, mutable pop receipts, delete coordination, and a policy for what to do when renewal fails. The first version of the...
Event Sourcing: Aggregates, Dynamic Consistency Boundaries, or what?
In part eleven of this series on event sourcing, we make a small detour to discuss consistency boundaries. I never thought much about this aspect until recently, when I watched a couple of conference talks that all made a big fuss about domain-driven design’s aggregates, and had a discussion with Sara Pellegrini about dynamic consistency boundaries. This post is less about the resulting solution we have and more about the reasoning that led to it (and that might lead to another solution...
A benchmark win is not the finish line
TL;DR: Once a benchmark shows a win, put the optimized code back into the profiling harness. Compare the before and after memory and CPU profiles to see whether the larger execution path benefits too. A good benchmark table feels great. The before number is slower, the after number is faster, allocations drop, and the ratio looks impressive. After hours of staring at profiler stacks and benchmark output, that table feels like the finish line. Unfortunately, the table only describes the isolated...
Message pumps fail in the transaction details
TL;DR: Reading bytes from a queue is the easy part of a message pump. Things get difficult when message handling writes to a database, sends more messages, fails halfway through, moves to another broker, or runs in a cloud service with different transaction semantics. A small infrastructure project can quietly turn into a platform commitment. None of this looked particularly scary when we started. It looked like a loop: read from a queue, deserialize a message, find the handler, and call it. We...