Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save sfmskywalker/f3fff25184a8a2a48de1174002df93ed to your computer and use it in GitHub Desktop.

Select an option

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
<!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&#39;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 =&gt;
{
cluster.ApplicationInstanceOptions = options =&gt;
{
options.InstanceNameEnvironmentVariable = &quot;HOSTNAME&quot;;
};
});
</code></pre>
<p>Or, if the host already knows the right stable identity:</p>
<pre><code class="language-csharp">elsa.UseApplicationCluster(cluster =&gt;
{
cluster.ApplicationInstanceOptions = options =&gt;
{
options.InstanceName = &quot;orders-0&quot;;
};
});
</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