Created
June 23, 2026 17:34
-
-
Save sfmskywalker/f3fff25184a8a2a48de1174002df93ed to your computer and use it in GitHub Desktop.
Medium import HTML for Elsa blog post: Stable Instance Names for Azure Service Bus
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | |
| <title>Stable Instance Names for Azure Service Bus</title> | |
| <meta name="description" content="A practical look at why Elsa's Azure Service Bus deployments now support stable application instance names, and how a random per-process name can turn restarts into orphaned subscriptions." /> | |
| <link rel="canonical" href="https://www.elsaworkflows.io/blog/stable-instance-names-for-azure-service-bus" /> | |
| <meta property="og:type" content="article" /> | |
| <meta property="og:title" content="Stable Instance Names for Azure Service Bus" /> | |
| <meta property="og:description" content="A practical look at why Elsa's Azure Service Bus deployments now support stable application instance names, and how a random per-process name can turn restarts into orphaned subscriptions." /> | |
| <meta property="og:url" content="https://www.elsaworkflows.io/blog/stable-instance-names-for-azure-service-bus" /> | |
| <meta property="og:image" content="https://elsa-workflows.github.io/elsa-blog/assets/2026-06-22-stable-instance-names-for-azure-service-bus/featured.png" /> | |
| <meta property="article:published_time" content="2026-06-22" /> | |
| <meta property="article:author" content="Sipke Schoorstra" /> | |
| <meta property="article:tag" content="elsa-workflows" /> | |
| <meta property="article:tag" content="dotnet" /> | |
| <meta property="article:tag" content="azure-service-bus" /> | |
| <meta property="article:tag" content="kubernetes" /> | |
| <meta property="article:tag" content="operations" /> | |
| </head> | |
| <body> | |
| <article> | |
| <h1>Stable Instance Names for Azure Service Bus</h1> | |
| <p><em>By Sipke Schoorstra · 2026-06-22</em></p> | |
| <p><img src="https://elsa-workflows.github.io/elsa-blog/assets/2026-06-22-stable-instance-names-for-azure-service-bus/featured.png" alt="Stable Instance Names for Azure Service Bus" /></p> | |
| <p><strong>A practical look at why Elsa's Azure Service Bus deployments now support stable application instance names, and how a random per-process name can turn restarts into orphaned subscriptions.</strong></p> | |
| <h1>Stable Instance Names for Azure Service Bus</h1> | |
| <p>Some production bugs start with the wrong question.</p> | |
| <p>A customer running Elsa on Kubernetes with the Azure Service Bus transport reported slow startup, crash loops, and a Service Bus namespace full of old per-instance queues. The obvious suspicion was the backlog. There were queues with thousands of messages in them, and the application was not becoming ready quickly enough.</p> | |
| <p>That was close, but not quite right.</p> | |
| <p>The backlog was real. It just was not the thing blocking startup.</p> | |
| <p>The more interesting failure was that every restart created another per-instance change-token subscription. Over time those abandoned subscriptions accumulated until the Azure Service Bus topic hit its hard subscription limit. At that point a fresh pod could no longer create the topology it needed, MassTransit kept retrying, and the host never reached <code>ApplicationStarted</code>.</p> | |
| <p>That is the kind of operational detail that is easy to miss until a system has been restarted enough times in anger.</p> | |
| <h2>Why the instance name matters</h2> | |
| <p>Elsa uses an application instance name for cluster-related infrastructure. With the Azure Service Bus transport, that name is part of the per-instance entities used for distributed cache change-token signaling.</p> | |
| <p>Before the recent fix, the default provider generated a random instance name for each process start. That is a reasonable local default. It avoids collisions when multiple logical instances are started inside the same process, including some component test scenarios.</p> | |
| <p>In a Kubernetes deployment backed by Azure Service Bus, though, that behavior has a sharp edge.</p> | |
| <p>If a pod restarts and gets a new random Elsa instance name, the new process creates a new change-token subscription. The old subscription is now orphaned. Azure Service Bus does have <code>AutoDeleteOnIdle</code>, but continuous change-token traffic can keep those abandoned entities non-idle. So they do not necessarily disappear just because the process that created them is gone.</p> | |
| <p>One restart is harmless.</p> | |
| <p>Many restarts can become a pile of abandoned subscriptions.</p> | |
| <p>And Azure Service Bus Standard has a hard limit of 2,000 subscriptions per topic. Once the change-token topic reaches that limit, creating the next subscription fails with <code>QuotaExceeded</code>. Since Elsa's MassTransit host startup waits for the bus to start, the application can get stuck before readiness.</p> | |
| <p>The investigation is captured in <a href="https://github.com/elsa-workflows/elsa-core/issues/7732"><code>elsa-core</code> issue #7732</a> and the root-cause issue <a href="https://github.com/elsa-workflows/elsa-core/issues/7736"><code>#7736</code></a>. The fix landed in <a href="https://github.com/elsa-workflows/elsa-core/pull/7734"><code>elsa-core</code> PR #7734</a> and was ported to the 3.7, 3.8, and main branches in <a href="https://github.com/elsa-workflows/elsa-core/pull/7742"><code>#7742</code></a>, <a href="https://github.com/elsa-workflows/elsa-core/pull/7743"><code>#7743</code></a>, and <a href="https://github.com/elsa-workflows/elsa-core/pull/7744"><code>#7744</code></a>.</p> | |
| <h2>The useful correction</h2> | |
| <p>The fix is deliberately opt-in for the patch line: Elsa now has <code>ApplicationInstanceOptions</code>, read by <code>ConfiguredApplicationInstanceNameProvider</code>.</p> | |
| <p>The provider resolves the instance name in this order:</p> | |
| <ol> | |
| <li><code>ApplicationInstanceOptions.InstanceName</code>, when set.</li> | |
| <li>The environment variable named by <code>ApplicationInstanceOptions.InstanceNameEnvironmentVariable</code>, when configured and non-empty.</li> | |
| <li>A random name, preserving the previous behavior when nothing is configured.</li> | |
| </ol> | |
| <p>That gives production hosts a way to say: this logical instance should reuse the same transport identity across restarts.</p> | |
| <p>The important part is that the name must be both stable and unique.</p> | |
| <p>Stable means the same logical instance gets the same value after a restart. Unique means two instances that are running at the same time must not share the value. A Kubernetes StatefulSet fits that model well because each pod has a stable ordinal hostname. A Deployment can work too, but then you need to be much more deliberate about what you project into the environment.</p> | |
| <p>The shape looks like this:</p> | |
| <pre><code class="language-csharp">elsa.UseApplicationCluster(cluster => | |
| { | |
| cluster.ApplicationInstanceOptions = options => | |
| { | |
| options.InstanceNameEnvironmentVariable = "HOSTNAME"; | |
| }; | |
| }); | |
| </code></pre> | |
| <p>Or, if the host already knows the right stable identity:</p> | |
| <pre><code class="language-csharp">elsa.UseApplicationCluster(cluster => | |
| { | |
| cluster.ApplicationInstanceOptions = options => | |
| { | |
| options.InstanceName = "orders-0"; | |
| }; | |
| }); | |
| </code></pre> | |
| <p>The implementation also validates the configured name and shortens long values deterministically when needed, because downstream transport entity names still have provider-specific limits. That is a small detail, but it matters. A fix for restart churn should not introduce a new failure mode where a long pod name breaks transport setup.</p> | |
| <h2>What this does not solve</h2> | |
| <p>This does not magically clean up a namespace that has already reached the subscription cap.</p> | |
| <p>If an Azure Service Bus namespace already has thousands of orphaned change-token subscriptions or queues, you still need a one-time cleanup as part of rollout. The stable name prevents the same logical instance from leaking a new subscription on every restart after the fix is configured. It does not delete historical debris by itself.</p> | |
| <p>It also does not mean the original startup report had only one cause. The tracking issue <a href="https://github.com/elsa-workflows/elsa-core/issues/7737"><code>#7737</code></a> groups a few related reliability findings, including a separate scheduling bookmark startup path in <a href="https://github.com/elsa-workflows/elsa-core/issues/7735"><code>#7735</code></a>. The Quartz scheduling error that appeared in the same customer logs was fixed separately in <code>elsa-extensions</code> through <a href="https://github.com/elsa-workflows/elsa-extensions/pull/160"><code>#160</code></a>, <a href="https://github.com/elsa-workflows/elsa-extensions/pull/161"><code>#161</code></a>, and <a href="https://github.com/elsa-workflows/elsa-extensions/pull/162"><code>#162</code></a>.</p> | |
| <p>That separation is worth spelling out. In distributed systems, one incident report can contain several problems that look like one problem because they all show up during startup.</p> | |
| <h2>The broader lesson</h2> | |
| <p>Random names are useful when the lifetime of the name is the lifetime of the process.</p> | |
| <p>They are less useful when the name is used to create durable infrastructure.</p> | |
| <p>For local development and tests, a random Elsa application instance name is convenient. For a clustered production deployment using a broker with durable topology and hard entity limits, the instance name becomes part of the operational contract. It should be treated the same way you treat a queue name, a consumer group, or a database schema name: stable enough that the infrastructure can converge, and unique enough that live instances do not step on each other.</p> | |
| <p>That is a fairly small configuration change.</p> | |
| <p>But small identity choices have a habit of becoming very large once a system is restarted hundreds or thousands of times.</p> | |
| </article> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment