<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nasim</title>
    <description>The latest articles on DEV Community by Nasim (@n451m).</description>
    <link>https://dev.to/n451m</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1144755%2F9349a350-e03f-4d76-ae6c-283e7e6d7111.png</url>
      <title>DEV Community: Nasim</title>
      <link>https://dev.to/n451m</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/n451m"/>
    <language>en</language>
    <item>
      <title>Why Your Terraform Cloud Runs Are Stuck</title>
      <dc:creator>Nasim</dc:creator>
      <pubDate>Mon, 12 Oct 2026 23:30:37 +0000</pubDate>
      <link>https://dev.to/n451m/why-your-terraform-cloud-runs-are-stuck-2ba6</link>
      <guid>https://dev.to/n451m/why-your-terraform-cloud-runs-are-stuck-2ba6</guid>
      <description>&lt;p&gt;&lt;strong&gt;Workspaces, queues, locks, and the concurrency ceiling nobody documents.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our Terraform Cloud organization has 270 workspaces. One morning, 20 of them had a run waiting on someone to approve it, and 46 more runs queued behind those. The dashboard's "needs attention" count said twenty; the real number of things wrong was one. A change merged two days earlier, never approved, sat at the head of fifteen of those queues with two days of ordinary merges piled up behind it. Clearing it was straightforward. What cost the time was finding out the next morning that seventeen &lt;em&gt;more&lt;/em&gt; workspaces held another 67 runs in the same state, going back nine days — and had never shown up in any sweep we ran, because the tooling searched four component names, which matched 54 workspaces. The other 216 were invisible to it.&lt;/p&gt;

&lt;p&gt;If you manage more than a handful of Terraform Cloud (TFC) or Terraform Enterprise (TFE) workspaces, you know the moment: the dashboard shows a wall of queued, errored, and "needs attention" runs, and the honest answer to "why is this stuck" is that nobody on the team knows the model.&lt;/p&gt;

&lt;p&gt;This is that model. Six layers — workspace, run, queue, lock, concurrency ceiling, trigger — and a triage script at the end that turns a wall of runs into a five-minute answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workspace Is the Unit of Serialization
&lt;/h2&gt;

&lt;p&gt;A workspace isn't a folder of Terraform code. It's the unit of state isolation (one state file, always), variable scope, execution mode, and — the part people miss — run concurrency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A workspace processes one apply-track run at a time.&lt;/strong&gt; Not one apply: one run, plan through apply. Every other run that could eventually apply queues behind it, strictly FIFO, until the current one reaches a terminal status.&lt;/p&gt;

&lt;p&gt;There's one exemption. &lt;strong&gt;Plan-only runs bypass both the queue and the workspace lock entirely.&lt;/strong&gt; That covers every speculative plan — PR-triggered plans, CLI speculative plans, any run against a configuration version marked speculative — plus the planning stage of saved-plan runs. They can never apply, so they can never corrupt state, so TFC starts them immediately even while an apply-track run holds the workspace's one slot. They don't become the current run and they don't block anything behind them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclude plan-only runs before you compute queue depth.&lt;/strong&gt; On an active repo they're the majority of what you see — in the organization these examples come from, 72 of the 100 most recent runs were plan-only — and counting them turns a healthy workspace into a phantom incident.&lt;/p&gt;

&lt;p&gt;Two structural notes before you scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Group workspaces by volatility.&lt;/strong&gt; HashiCorp's workspace guidance recommends sizing state files to minimize blast radius and separating long-lived resources (databases, networking) from frequently-changing ones (compute). The side effect is queue hygiene — volatile workspaces churn through their FIFO queue fast, and shouldn't be stuck behind something that changes twice a year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The anchor workspace is a serialization point.&lt;/strong&gt; If you're assuming roles into multiple AWS accounts from a single anchor workspace — the standard Control Tower and AFT pattern — every downstream workspace consuming its outputs is rate-limited by how fast that one queue drains. One anchor is one slot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Past a few dozen workspaces, the answer isn't more workspaces, it's Projects — a grouping layer below the org with per-project team permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Run Is a State Machine
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcam3yjt9p2iayhu30pgg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcam3yjt9p2iayhu30pgg.png" alt="state machine run" width="800" height="865"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yellow is where a run waits on a person. Red is where it is writing, or has failed. Grey and green are terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pending → plan_queued → planning → planned
   → [cost_estimated] → [policy_checked | policy_override]
   → confirmed → apply_queued → applying → applied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A common misconception: &lt;strong&gt;a plan with no changes never waits for you.&lt;/strong&gt; It terminates as &lt;code&gt;planned_and_finished&lt;/code&gt;, a final state that exists for plan-only runs and runs whose plan contains nothing to apply. TFC closes it out itself. There's nothing to confirm and nothing to discard, so no-op plans are not a source of dashboard noise. (The &lt;code&gt;allow_empty_apply&lt;/code&gt; option overrides this if you deliberately want a no-op apply — useful after a Terraform version bump or a batch of &lt;code&gt;moved&lt;/code&gt; blocks.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Queued," "errored," and "needs attention" aren't different failure modes. They're different resting points in one state machine&lt;/strong&gt;, and the resting point is the diagnosis.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PLANNED&lt;/code&gt; — real changes, awaiting confirmation or auto-apply.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POLICY_OVERRIDE&lt;/code&gt; — a soft-mandatory policy failed. Needs a human decision, not a retry.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ERRORED&lt;/code&gt; — root-cause the failure class first. IAM/auth, state lock, provider crash, and resource conflict need four different fixes, and blind retry only helps one of them.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APPLYING&lt;/code&gt; — leave it alone. It's mid-write to real infrastructure and real state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One resting point catches people because it looks like progress: &lt;code&gt;cost_estimated&lt;/code&gt;. If cost estimation is enabled org-wide, a run awaiting approval sits here rather than at &lt;code&gt;planned&lt;/code&gt;. Filter on &lt;code&gt;planned&lt;/code&gt; alone and you miss every run that is waiting for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Queues Are Per-Workspace, Not Per-Org
&lt;/h2&gt;

&lt;p&gt;There is no global run queue. There's one queue per workspace, and one stuck run at the head of it can accumulate an arbitrary number of runs behind it — every one of which looks like an independent problem in a dashboard count.&lt;/p&gt;

&lt;p&gt;Across workspaces, runs execute in parallel, up to the org ceiling.&lt;/p&gt;

&lt;p&gt;That gives you the first fork in triage: do the stuck runs &lt;strong&gt;cluster in a few workspaces&lt;/strong&gt; (queue-head problem — fix the run at the front and the rest drain by themselves), or are they &lt;strong&gt;spread thin, one per workspace across dozens&lt;/strong&gt; (systemic — auto-apply disabled broadly, or a new Sentinel policy shipped without a grace period)?&lt;/p&gt;

&lt;h2&gt;
  
  
  Locks Are State-Level, Not Run-Level
&lt;/h2&gt;

&lt;p&gt;Terraform holds a state lock during plan and apply so two processes can't write the same state file. Inside TFC the run queue already prevents concurrent apply-track runs, so locks matter in two places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Local CLI applies against a TFC-backed state&lt;/strong&gt;, running alongside CI-driven runs. The queue doesn't govern locally-initiated applies; the lock is the only thing standing between you and a corrupted state file. If anything besides TFC can reach the state, workspaces alone are not sufficient isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A run that crashed hard enough to leave the lock held&lt;/strong&gt; without transitioning to a terminal status. This is the classic "state locked" error, it needs a deliberate manual unlock, and it is the last thing you should ever script an automatic recovery for. Confirm nothing is mid-write first.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Depth and Ceiling Are Two Different Numbers
&lt;/h2&gt;

&lt;p&gt;Conflating these is where most "why won't my runs start" confusion lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-workspace depth is 1&lt;/strong&gt;, always, on every tier — apply-track runs only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The org concurrency ceiling&lt;/strong&gt; caps how many runs are actively planning or applying across the entire organization. On SaaS, this is set by your plan tier and nothing else — adding agents does not raise it. HCP Terraform now has five tiers (Free, Essentials, Standard, Premium, Enterprise); the legacy free plan reached end-of-life on March 31, 2026 and was replaced by a 500-managed-resource free tier. HashiCorp's support KB publishes concurrency figures for exactly two of them: &lt;strong&gt;1 concurrent run on Free, 3 on Standard.&lt;/strong&gt; Essentials and Premium integers aren't published anywhere — check your org's plan page rather than trusting a blog post, including this one. Standard and Premium orgs can also purchase additional remote and agent runs in increments of five, up to fifty additional runs.&lt;/p&gt;

&lt;p&gt;The concurrency number is &lt;strong&gt;not exposed through the API&lt;/strong&gt;. &lt;code&gt;GET /organizations/:org?include=entitlement-set&lt;/code&gt; returns &lt;code&gt;operations&lt;/code&gt;, &lt;code&gt;agents&lt;/code&gt;, policy limits and much else, but no run-concurrency field. The plan page in the UI is the only place it appears.&lt;/p&gt;

&lt;p&gt;Self-hosted TFE is the opposite situation: the ceiling is a dial you own. On Kubernetes, &lt;code&gt;TFE_CAPACITY_CONCURRENCY&lt;/code&gt; defaults to 10 concurrent jobs per agent pod and tunes up to 50, so it becomes capacity planning against cluster autoscaling.&lt;/p&gt;

&lt;p&gt;This is the limit that quietly bites teams as workspace count grows without a plan or agent-pool upgrade. Zero conflicts, zero locks, zero errors — and runs still sitting in &lt;code&gt;PENDING&lt;/code&gt;, because the org is at capacity. It presents identically to "something is stuck," but the fix isn't debugging a workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Creates Runs
&lt;/h2&gt;

&lt;p&gt;VCS webhooks, CLI plans and applies, direct API calls, and &lt;strong&gt;run triggers&lt;/strong&gt; — a downstream workspace firing automatically after an upstream workspace applies successfully. That last one is the anchor-workspace fan-out, and it's capped at 20 source workspaces per workspace. HashiCorp warns about cascading failures and recommends short trigger chains with failure notifications. One anchor apply spawning fifteen downstream runs is exactly when you hit the org ceiling while every individual workspace queue looks perfectly healthy.&lt;/p&gt;

&lt;p&gt;The sneakier backlog source: &lt;strong&gt;a superseded commit doesn't kill its predecessor's run.&lt;/strong&gt; Push commit A, then commit B thirty seconds later, and you get two runs. The first isn't auto-discarded for being stale — it sits in &lt;code&gt;PLANNED&lt;/code&gt; awaiting confirmation until something clears it.&lt;/p&gt;

&lt;p&gt;Half of this has a built-in fix: &lt;strong&gt;"Automatically cancel speculative plans for outdated commits"&lt;/strong&gt; (Settings → Version Control → General) cancels in-flight and pending plan-only runs when new commits land on the same branch. Turn it on. It does nothing for apply-track runs, which is the gap you have to close yourself.&lt;/p&gt;

&lt;p&gt;And that gap is worse than "some wasted compute." A queued run is pinned to the configuration version it was created from, and that never updates. Approving a nine-day-old run applies nine-day-old code — which can be the &lt;em&gt;opposite&lt;/em&gt; of what your current &lt;code&gt;main&lt;/code&gt; says, with nothing in the plan's summary counts to warn you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Policy Enforcement, Briefly
&lt;/h2&gt;

&lt;p&gt;Sentinel has exactly three enforcement levels: &lt;strong&gt;advisory&lt;/strong&gt; (logs a warning, blocks nothing), &lt;strong&gt;soft-mandatory&lt;/strong&gt; (blocks, overridable by permitted users), and &lt;strong&gt;hard-mandatory&lt;/strong&gt; (blocks unconditionally; the only way past it is removing the policy from the set).&lt;/p&gt;

&lt;p&gt;If you're staring at a pile of &lt;code&gt;POLICY_OVERRIDE&lt;/code&gt; runs, don't override them N times. Find the shared policy and stage it properly — advisory, then soft-mandatory, then hard-mandatory — so teams get a grace period instead of a wall.&lt;/p&gt;

&lt;p&gt;Confirm the policy is attached before you trust it. A policy set committed to a repo enforces nothing until it's wired to the org; &lt;code&gt;GET /organizations/:org/policy-sets&lt;/code&gt; returning an empty array is the whole answer, and it's a five-second check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triage, Automated
&lt;/h2&gt;

&lt;p&gt;Three checks replace clicking into runs one at a time. All are read-only, and all go through the &lt;strong&gt;organization&lt;/strong&gt; runs endpoint — &lt;code&gt;GET /organizations/:org/runs&lt;/code&gt; — rather than iterating workspaces. Iterating workspaces is one request each; on a few hundred workspaces that's minutes, against seconds for the org endpoint.&lt;/p&gt;

&lt;p&gt;Three things about this endpoint that will silently break naive code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Its pagination metadata omits &lt;code&gt;total-count&lt;/code&gt; and &lt;code&gt;total-pages&lt;/code&gt;.&lt;/strong&gt; You get &lt;code&gt;current-page&lt;/code&gt;, &lt;code&gt;next-page&lt;/code&gt;, &lt;code&gt;prev-page&lt;/code&gt; and &lt;code&gt;page-size&lt;/code&gt;. Follow &lt;code&gt;next-page&lt;/code&gt; until it's empty; don't compute page counts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run objects carry a workspace ID but not a name.&lt;/strong&gt; &lt;code&gt;include=workspace&lt;/code&gt; solves it — the workspaces come back in &lt;code&gt;.included&lt;/code&gt; on each page, so you can attach names as you stream rather than building a separate lookup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;plan_only&lt;/code&gt; runs are &lt;em&gt;not&lt;/em&gt; excluded by default.&lt;/strong&gt; On an active org they'll be most of what you get back — filter them out explicitly for anything queue-related.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On rate limits: the general API limit is 30 requests/second — &lt;code&gt;X-RateLimit-Limit: 30&lt;/code&gt;, with a sub-second &lt;code&gt;X-Ratelimit-Reset&lt;/code&gt;. Some endpoints carry tighter limits depending on tier, so check the headers rather than assuming. Sequential pagination needs no sleeps; concurrent fan-out needs 429 retry.&lt;/p&gt;

&lt;p&gt;Shared setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ORG&lt;/span&gt;:?set&lt;span class="p"&gt; ORG to your Terraform Cloud organization name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;TF_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TF_TOKEN&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.credentials["app.terraform.io"].token'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ~/.terraform.d/credentials.tfrc.json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;API&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://app.terraform.io/api/v2"&lt;/span&gt;

&lt;span class="c"&gt;# Emits one run object per line, with .ws_name attached.&lt;/span&gt;
&lt;span class="c"&gt;# include=workspace returns the workspace in .included, so there's no second&lt;/span&gt;
&lt;span class="c"&gt;# lookup pass and no temp file. $1 is an extra query string.&lt;/span&gt;
tfc_runs&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;page&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 resp
  &lt;span class="k"&gt;while&lt;/span&gt; :&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sf&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TF_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API&lt;/span&gt;&lt;span class="s2"&gt;/organizations/&lt;/span&gt;&lt;span class="nv"&gt;$ORG&lt;/span&gt;&lt;span class="s2"&gt;/runs?include=workspace&amp;amp;page%5Bsize%5D=100&amp;amp;page%5Bnumber%5D=&lt;/span&gt;&lt;span class="nv"&gt;$page&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$qs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'
      (reduce (.included[]? | select(.type == "workspaces")) as $w
        ({}; .[$w.id] = $w.attributes.name)) as $ws
      | .data[]
      | .ws_name = ($ws[.relationships.workspace.data.id]
                    // .relationships.workspace.data.id)
    '&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$resp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;page&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.meta.pagination["next-page"] // empty'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$resp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$page&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break
  &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. Queue-head detection.&lt;/strong&gt; &lt;code&gt;filter[status_group]=non_final&lt;/code&gt; returns what's running, queued, or awaiting confirmation. Filter plan-only out explicitly — they skip the queue, so counting them inflates every depth number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tfc_runs &lt;span class="s1"&gt;'filter%5Bstatus_group%5D=non_final'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
| jq &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'
    map(select(.attributes["plan-only"] != true))
    | group_by(.relationships.workspace.data.id)
    | map(sort_by(.attributes["created-at"]))
    | map({
        ws:       .[0].ws_name,
        head:     .[0].id,
        status:   .[0].attributes.status,
        since:    .[0].attributes["created-at"],
        blocking: (length - 1)
      })
    | sort_by(-.blocking)[]
    | "\(.blocking)\t\(.ws)\thead=\(.head)\tstatus=\(.status)\tsince=\(.since)"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2   aft-prod-apps   head=run-a1  status=errored  since=2026-09-01T10:00:00.000Z
2   prod-network          head=run-b1  status=planned  since=2026-09-02T09:00:00.000Z
0   prod-eks              head=run-c2  status=applying since=2026-09-03T08:10:00.000Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sorting ascending by &lt;code&gt;created-at&lt;/code&gt; and taking &lt;code&gt;.[0]&lt;/code&gt; picks the &lt;strong&gt;oldest&lt;/strong&gt; non-terminal run — the one holding the lock. Leading column is how many runs are stacked behind it. One workspace in double digits is a queue-head problem: fix that run and the rest drain. Every workspace showing &lt;code&gt;0&lt;/code&gt; is systemic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Concurrency-ceiling check.&lt;/strong&gt; Here you &lt;em&gt;want&lt;/em&gt; plan-only runs, because speculative plans skip the workspace queue but still consume org concurrency. Name every operation explicitly rather than relying on defaults.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'plan_only,plan_and_apply,save_plan,refresh_only,destroy,empty_apply'&lt;/span&gt;

tfc_runs &lt;span class="s2"&gt;"filter%5Bstatus_group%5D=non_final&amp;amp;filter%5Boperation%5D=&lt;/span&gt;&lt;span class="nv"&gt;$OPS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
| &lt;span class="nb"&gt;tee&lt;/span&gt; /tmp/nonfinal.ndjson &lt;span class="se"&gt;\&lt;/span&gt;
| jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.attributes.status'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'live concurrency: '&lt;/span&gt;
jq &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'map(select(.attributes.status | IN(
    "planning","applying","plan_queued","apply_queued","cost_estimating",
    "policy_checking","post_plan_running","pre_plan_running","queuing","fetching"
  ))) | length'&lt;/span&gt; /tmp/nonfinal.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IN()&lt;/code&gt; needs jq 1.6 or later. If &lt;code&gt;live concurrency&lt;/code&gt; sits at your tier's cap while runs pile up in &lt;code&gt;pending&lt;/code&gt;, stop debugging — you're waiting on capacity. The histogram costs nothing on the way past: a large &lt;code&gt;errored&lt;/code&gt; count and a large &lt;code&gt;pending&lt;/code&gt; count are different mornings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Supersession sweep.&lt;/strong&gt; &lt;code&gt;filter[status_group]=discardable&lt;/code&gt; is the built-in definition of "paused and safe to clear." Within each workspace, only the newest reflects current config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tfc_runs &lt;span class="s1"&gt;'filter%5Bstatus_group%5D=discardable'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
| jq &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'
    map(select(.attributes.actions["is-discardable"]))
    | group_by(.relationships.workspace.data.id)
    | map(sort_by(.attributes["created-at"]) | reverse | .[1:])
    | flatten | .[]
    | "\(.id)\t\(.ws_name)\t\(.attributes["created-at"])"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Discard with &lt;code&gt;POST /runs/:id/actions/discard&lt;/code&gt;, which accepts an optional &lt;code&gt;comment&lt;/code&gt; — use it, so the audit trail says why.&lt;/p&gt;

&lt;p&gt;Two verbs, and using the wrong one fails: a run that has finished planning is &lt;strong&gt;discardable&lt;/strong&gt;; a run that never started is only &lt;strong&gt;cancelable&lt;/strong&gt;, and &lt;code&gt;discard&lt;/code&gt; answers 409 on it. Route on the run's own &lt;code&gt;actions.is-discardable&lt;/code&gt; / &lt;code&gt;actions.is-cancelable&lt;/code&gt; flags rather than matching status names, and you can't get it wrong.&lt;/p&gt;

&lt;p&gt;Two caveats on this sweep. Newest-per-workspace is a proxy for "newest configuration version," and it's wrong for a manually-queued run pinned to an older config, so review the list before wiring it to a cron. And half this problem has a native fix already — turn on "Automatically cancel speculative plans for outdated commits" and this sweep only has to handle apply-track runs.&lt;/p&gt;

&lt;p&gt;One ordering detail if you clear a deep queue: &lt;strong&gt;work newest-first, lock-holder last.&lt;/strong&gt; Discard the head first and TFC immediately starts the next queued run, so you end up fighting a plan that's building while you're still clearing.&lt;/p&gt;

&lt;p&gt;Run all three and the flat scary number resolves into three buckets: fix the head of the queue, wait on or scale capacity, clear the superseded noise. The dashboard gives you counts. The state machine gives you causes.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Further reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/workspaces/run/remote-operations#ordering-and-timing" rel="noopener noreferrer"&gt;Remote operations: ordering and timing&lt;/a&gt; — the plan-only and saved-plan queue exemption&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/run/states" rel="noopener noreferrer"&gt;Run states and stages&lt;/a&gt; — every status and what leaves it&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/overview/manage-plan" rel="noopener noreferrer"&gt;Manage your HCP Terraform plan&lt;/a&gt; — concurrency by tier, buying additional runs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run" rel="noopener noreferrer"&gt;&lt;code&gt;/runs&lt;/code&gt; API reference&lt;/a&gt; — attributes and actions used above&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>infrastructure</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Distributed Tracing and W3C Trace Context</title>
      <dc:creator>Nasim</dc:creator>
      <pubDate>Mon, 13 Apr 2026 17:45:01 +0000</pubDate>
      <link>https://dev.to/n451m/distributed-tracing-and-w3c-trace-context-2p4n</link>
      <guid>https://dev.to/n451m/distributed-tracing-and-w3c-trace-context-2p4n</guid>
      <description>&lt;p&gt;When a user makes a single request to an application or clicks “checkout” on an e-commerce site, it involves multiple services, API gateway → auth service → cart service → Redis cache → inventory database → payment → notification. Traditional logs show what happened within a single service. Distributed tracing shows the entire story of a request as it traveled across all the services in the application. &lt;/p&gt;

&lt;p&gt;The core data model has &lt;em&gt;three&lt;/em&gt; concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trace - journey of request. Each trace has unique trace-id&lt;/li&gt;
&lt;li&gt;Span - unit of work within that trace defined with a start time and duration. Could be calling another service, doing a database query. Child spans are also created &lt;/li&gt;
&lt;li&gt;Context - the metadata that goes with the request to tell each service that this request is part of another request. This the W3C Trace Context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6in4lwamxm59iph5d5t8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6in4lwamxm59iph5d5t8.png" alt="Gantt chart" width="800" height="510"&gt;&lt;/a&gt;&lt;br&gt;
The Gantt chart (known as a waterfall) immediately shows you the critical path for the application. &lt;br&gt;
Auth → Cart → Inventory → Payment&lt;br&gt;
&lt;strong&gt;Reading the Gantt Chart&lt;/strong&gt;&lt;br&gt;
Each horizontal bar represents one span of work within one service. The left edge of each bar is the start time of that work, the right edge is the finish time, and the width is the length of that span of work. The vertical axis represents the different services, and the horizontal axis is the wall-clock time between 0ms and 600ms.&lt;br&gt;
The vertical lines that are indented within each service represent the parent-child relationships between those services. Each service’s work starts directly when these vertical lines start: it was called directly by its parent service.&lt;/p&gt;

&lt;h4&gt;
  
  
  Look at the time at when each service starts:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Auth service starts at ~20ms and finishes at ~100ms. API Gateway must authenticate the user before continuing with any other work; Cart cannot start until Auth is complete.&lt;/li&gt;
&lt;li&gt;Cart service starts at ~110ms (after Auth) and makes an inline call to Redis (a child service). Cart cannot return successfully to its caller until Redis returns.&lt;/li&gt;
&lt;li&gt;Inventory service starts at ~270ms (after Cart) and makes an inline call to Postgres (a child service). Postgres is called inline so that the Inventory service can return to Cart with the requested inventory information.&lt;/li&gt;
&lt;li&gt;The Payment service starts at ~410ms (after Inventory is called) and takes 140ms to complete (the longest task after the API Gateway’s root span).&lt;/li&gt;
&lt;li&gt;The Notification service starts at ~555ms and is very short at ~25ms in length. This is likely a fire-and-forget service that is called asynchronously when the Purchase operation is complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The process strictly follows a sequence: Auth → Cart (with Redis) → Inventory (with Postgres) → Payment → Notification. None of these steps run simultaneously. That’s the main issue—the design enforces a linear chain when, in theory, some parts could operate in parallel.&lt;/p&gt;

&lt;p&gt;Now, about why Payment is the bottleneck. A bottleneck isn’t just the slowest step; it’s the part on the critical path that, if improved, would speed up the entire workflow. Here, the longest sequence—the critical path—takes 580ms, and Payment’s 140ms sits toward the end. Cutting time in Payment directly reduces the overall response time. For example, even if you eliminated the 70ms Redis cache retrieval in Cart, Payment would still take 140ms afterward, leaving the total duration almost unchanged.&lt;/p&gt;

&lt;p&gt;There’s also another factor: Payment involves calling an external third-party API (Stripe), which adds extra dependency and latency.&lt;br&gt;
You can’t control latency here—it can jump unpredictably. Network round-trips add delays on top of Stripe’s processing time, and any outage or rate limit from Stripe stalls your whole checkout flow.&lt;/p&gt;

&lt;p&gt;The amber highlight in the diagram shows it’s slower compared to other parts, but the real problem is where it sits—in a late spot in a chain with no parallel work and relying on an external system you can't tweak.&lt;/p&gt;

&lt;p&gt;So, now what? Two main handles: &lt;em&gt;parallelism&lt;/em&gt; and &lt;em&gt;externalization&lt;/em&gt;.&lt;br&gt;
First, run Inventory and Auth checks at the same time. Starting both simultaneously after the request could cut down about 200ms of sequential waiting to around 130ms, limited by whichever is slower.&lt;/p&gt;

&lt;p&gt;Second, take Payment out of the critical path. Accept the order right away, then process payment asynchronously using a queue. This lets users see a quick “order received” response while payment happens behind the scenes—just like most large e-commerce platforms do.&lt;/p&gt;

&lt;p&gt;Also, add a timeout and retry strategy. Since Stripe is external, its slowest 1% delays become yours too. Setting a strict timeout—say 200ms—and handling errors gracefully means a Stripe glitch won’t wreck the checkout experience.&lt;/p&gt;

&lt;p&gt;Without this trace, you might blame the database or cache, but the waterfall view clears that up instantly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Now, lets dive into W3C Trace Context
&lt;/h4&gt;

&lt;p&gt;Before W3C settled on a standard in 2021, each had their own way of passing trace context around. Zipkin used &lt;em&gt;X-B3-TraceId&lt;/em&gt; headers. AWS X-Ray used &lt;em&gt;X-Amzn-Trace-Id&lt;/em&gt;. Jaeger had its own format. When requests moved between these systems, the trace often got lost. &lt;a href="https://www.w3.org/TR/trace-context" rel="noopener noreferrer"&gt;W3C Trace Context&lt;/a&gt; introduced two HTTP headers that all compliant systems should support&lt;/p&gt;

&lt;h4&gt;
  
  
  Header 1: traceparent
&lt;/h4&gt;

&lt;p&gt;This is the core carrier. It encodes four fields in a single, compact header:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0uqmdsdua8nwl1h089a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0uqmdsdua8nwl1h089a.png" alt="traceparent" width="800" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;version&lt;/strong&gt; — always 00 currently. Future versions can add fields.&lt;br&gt;
&lt;strong&gt;trace-id&lt;/strong&gt; — 128-bit identifier for the entire distributed trace. All spans across all services share this value.&lt;br&gt;
&lt;strong&gt;parent-id&lt;/strong&gt; — 64-bit identifier of the calling span. The receiving service uses this as its parent span when creating its own span.&lt;br&gt;
&lt;strong&gt;flags&lt;/strong&gt; — currently 1 byte. The lowest bit is the sampling flag (01 = sampled, 00 = not sampled).&lt;/p&gt;

&lt;h4&gt;
  
  
  Header 2: tracestate
&lt;/h4&gt;

&lt;p&gt;Vendor extension list, an ordered comma-separated set of key=value pairs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx70mw8y4t21b3q7u9z0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx70mw8y4t21b3q7u9z0t.png" alt="tracestate" width="696" height="71"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each tracing vendor gets a unique key (e.g. dd for Datadog, b3 for Zipkin B3). This lets vendor-specific metadata (sampling priority, origin, session IDs) ride alongside the standard traceparent without polluting it. When a service forwards a request, it must preserve the full tracestate and may prepend its own entry at the front. Entries beyond 32 are dropped from the tail.&lt;/p&gt;

&lt;p&gt;Headers propagation across service boundaries&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqcw08mka93golxp27ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqcw08mka93golxp27ux.png" alt="Headers propagation across service boundaries" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sampled flag, which shows as 01 or 00, might seem like a small detail but it actually matters a lot. Tracking every single request in a busy production system is usually too costly—for example, a service handling 100k requests per second. Sampling helps by recording just a slice of traces, yet still keeping full detail for those captured.&lt;/p&gt;

&lt;p&gt;The sampled bit (flags 01 vs 00) plays a crucial role in tracing. Tracing every request in a high-traffic system, like one handling 100k requests per second, quickly becomes impractical. Sampling helps by recording only a subset of traces while maintaining detail for those captured.&lt;/p&gt;

&lt;p&gt;There are &lt;em&gt;three main sampling methods&lt;/em&gt;:&lt;br&gt;
&lt;em&gt;Head-based&lt;/em&gt; sampling decides at the root span before downstream calls. It sets the sampled flag to 01 or 00 and propagates it. This approach is straightforward and efficient but can't prioritize capturing interesting events like errors since they haven’t occurred yet.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tail-based&lt;/em&gt; sampling collects all spans from every service. The collector delays the sampling decision until the full trace is received, allowing it to keep all error traces and a small fraction of normal ones. This requires a more complex, stateful collector, such as the OpenTelemetry Collector with tail sampling.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Parent-based&lt;/em&gt; sampling simply follows the parent’s sampling decision via the sampled flag. It’s a safe default that ensures the whole trace is either fully recorded or entirely dropped, preventing disconnected spans without a root.&lt;/p&gt;

&lt;p&gt;A span isn’t just about its name and how long it lasts. OpenTelemetry, which leads as the open-source implementation of W3C Trace Context, sets semantic conventions—standard attribute names. This helps tracing tools interpret your data consistently, without needing extra setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgb7p4ydonoxwegf64qj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgb7p4ydonoxwegf64qj.png" alt="semantic conventions" width="800" height="277"&gt;&lt;/a&gt; Spans can include events, which are specific log entries tied to a moment within the span, like noting a “cache miss occurred”. They also hold links, which reference other traces—useful in async or queue situations where the producer and consumer traces are related but happen separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Async and Queue Propagation&lt;/strong&gt;&lt;br&gt;
Handling async and queue propagation differs from straightforward HTTP propagation. Since producer and consumer don’t operate simultaneously, you can’t rely on passing a traceparent in a request header. &lt;br&gt;
Instead, the traceparent value is embedded within the message metadata—such as Kafka headers, SQS attributes, or AMQP headers. The consumer then reads this metadata and treats it as a span link, not a parent. This means the consumer’s trace connects to the producer’s but remains a separate trace-id. This approach keeps the causal link clear without letting trace information grow uncontrollably deep.&lt;/p&gt;

&lt;p&gt;How does this fit into the OpenTelemetry stack?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypkrzfweugyrk1fmkxbe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypkrzfweugyrk1fmkxbe.png" alt="OpenTelemetry stack" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the work-horse OpenTelemetry SDK auto-instruments common frameworks like Express, Django, Spring, and net/http, automatically creating spans and managing traceparent injection and extraction. Each service uses the OTLP exporter to send span data to the OTel Collector, which processes the data—sampling, redacting sensitive info, batching, and distributing it to multiple backends at once. This vendor-neutral setup lets you switch between tools like Jaeger and Grafana Tempo without modifying your application code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key implementation considerations
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Clock skew&lt;/strong&gt; — spans from different machines have timestamps from different clocks. A child span with a start time before its parent is a common artifact of NTP drift. Tracing SDKs work around this by using monotonic clocks for durations and accepting some display weirdness on absolute timestamps.&lt;br&gt;
&lt;strong&gt;Baggage&lt;/strong&gt; — a sibling to tracestate, the W3C Baggage header (baggage: userId=alice, env=prod) lets you propagate arbitrary key-value pairs across service boundaries. It's useful for passing tenant IDs or feature flag states without touching application code, but beware: every hop re-serializes and forwards it, so large baggage values add latency.&lt;br&gt;
&lt;strong&gt;Instrumentation gaps&lt;/strong&gt; — if any service in the chain drops the traceparent header (an unmigrated internal proxy, a legacy service, a third-party vendor), the trace breaks. The downstream spans become "orphans" with no parent. Good tracing platforms surface these as disconnected sub-traces.&lt;br&gt;
&lt;strong&gt;Security&lt;/strong&gt; — traceparent is safe to expose externally. tracestate may contain internal vendor metadata you don't want leaking. Strip tracestate at your public ingress, then re-inject a fresh one for internal propagation.&lt;br&gt;
The W3C standard deliberately kept the spec minimal — it defines the wire format, not how to collect, store, or visualize. That separation of concerns is what made it possible for the whole ecosystem to converge on it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Diagrams created with the assistance of AI.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>microservices</category>
      <category>monitoring</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Ray: Building Distributed Systems—Just Like Running a Restaurant</title>
      <dc:creator>Nasim</dc:creator>
      <pubDate>Tue, 14 Oct 2025 16:37:06 +0000</pubDate>
      <link>https://dev.to/n451m/building-distributed-systems-with-ray-just-like-running-a-restaurant-1gjo</link>
      <guid>https://dev.to/n451m/building-distributed-systems-with-ray-just-like-running-a-restaurant-1gjo</guid>
      <description>&lt;p&gt;In our fast-paced digital world, data keeps piling up at a staggering rate. To make sense of it all, we need some supercharged computing tools that can handle what we throw at them. That is where Ray comes in. It is an open-source framework designed to help you build distributed applications without all the headaches. In the following sections, we’ll break down each Ray component using our restaurant analogy, translating abstract concepts into familiar, real-world operations. Let’s get cooking!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Data: Ingredient Prep Station&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Role&lt;/strong&gt;&lt;/em&gt;: Prepares every ingredient (chopping, washing, stalking) so when the kitchen manager is ready, everything is good to go with the cooking process. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Ray Version&lt;/strong&gt;&lt;/em&gt;: Handles all the preparations for massive data transformations—loading and mapping to transform data sets pre-work to avoid unnecessary time before training. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Key Analogy&lt;/strong&gt;&lt;/em&gt;: If you have a hundred tomatoes to chop or a hundred bags of flour, it is much easier to get it done on Ray Data before it is time to train than during actual cooking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Train: The Chefs Cooking the Main Dishes&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Role&lt;/em&gt;&lt;/strong&gt;: Cooks distribute what they're preparing and they can follow instructions, manage temperature and time, and check in along the way.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Ray Version&lt;/strong&gt;&lt;/em&gt;: Distributes CPU/GPUs for model training, distributes/checks in along the way.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Key Analogy&lt;/strong&gt;&lt;/em&gt;: You want different cooks (hardware) trained on different dishes (models) at the same time as possible so you can kill two birds with one stone and work efficiently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Tune: The Research Team Experimenting with Recipes&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Role&lt;/strong&gt;&lt;/em&gt;: The restaurant's R&amp;amp;D team is constantly exploring new tastes—iterating on sauces, thickness of pasta/ice cream/whatever other courses there are.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Ray Version&lt;/strong&gt;&lt;/em&gt;: Runs many trials in parallel for different parameters to assess best hyperparameters for a model. &lt;br&gt;
Key Analogy: Tune is like a lab that tries dozens or hundreds or iterations on a single recipe to find the best version—most palatable tasting notes—and best version for this request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Serve: Waitstaff and Quick Service Station&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Role&lt;/em&gt;&lt;/strong&gt;: The system for delivering courses to diners in a timely manner at the speed these clients want, especially if there is a rush and volume/inventory can help accommodate. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Ray Version&lt;/strong&gt;&lt;/em&gt;: Deploys and manages models/APIs, auto-scaling for client request routing. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Key Analogy&lt;/strong&gt;&lt;/em&gt;: Serve does not train models because Serve is a waitstaff who does not cook; it takes what is made in the kitchen (Run) to clients and can take many requests at once without sacrificing turnaround time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray RLlib: The Robot Chef Experimenting with Reinforcement Learning&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Role&lt;/em&gt;&lt;/strong&gt;: A chef using learning mechanisms over time—finds the best way to create various dishes with notes/feedback along the way. &lt;br&gt;
Ray Version: Builds, distributes/runs reinforcement learning algorithms from agents that can get feedback to learn over time. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Key Analogy&lt;/strong&gt;&lt;/em&gt;: RLlib is a robot chef who can learn through trial and error, figuring out which seasonings people like after adjusting and trying repeatedly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Core: Kitchen Manager and Scheduling System&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Role&lt;/em&gt;&lt;/strong&gt;: Schedules all kitchen staff, knows what each cook is doing, who is available to support what task, ensuring order fulfillment does not bottleneck.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Ray Version&lt;/strong&gt;&lt;/em&gt;: The core component to handle fundamental parallel computing—starting/stopping processes for workers, knowing what's going on, who's got what tasks, and generating reports about assigned resources.&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Key Analogy&lt;/em&gt;&lt;/strong&gt;: If one cook is busy, he'll have to wait for the next and it will take longer to line up what's being prepared next but Ray Core will essentially keep everyone in line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Cluster = The Kitchen&lt;/strong&gt;: Just like a restaurant has a kitchen where all the cooking happens, a Ray cluster is a collection of computers that work together. They pool their resources, whether it is memory or processing power, to get things done efficiently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Workers = The Kitchen Staff&lt;/strong&gt;: In any kitchen, you have distinct roles. There are chefs whipping up meals, sous chefs prepping ingredients, and dishwashers keeping things clean. Ray workers are like that staff—they are computers or components of computers that tackle their tasks independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Scheduler = The Head Chef&lt;/strong&gt;: Think of the head chef as the one in charge of getting things done. They assign tasks to the staff, making sure everyone knows what to work on and when. The Ray scheduler does the same thing by distributing tasks to the workers and keeping everything on track.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Tasks = Recipes&lt;/strong&gt;: Just like a well-structured recipe guides a chef, Ray tasks are steps that need to be executed. Whether it is crunching data or performing calculations, these tasks are straightforward units of work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Jobs = Customer Orders&lt;/strong&gt;: When a customer puts in an order, the kitchen team jumps into action to make it happen. A Ray job is like that order—it is made up of a group of tasks that need to come together to complete a bigger goal, such as training a machine learning model or processing a big dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Actors = Specialized Chefs/Stations&lt;/strong&gt;: Some chefs have specialties; for instance, a pastry chef only focuses on desserts. In Ray, actors are specialized workers that keep their state between task executions, just like those chefs focus on their craft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Tasks with Dependencies = Recipe Steps&lt;/strong&gt;: In cooking, some steps cannot happen until others are finished—like you need to caramelize onions before adding them to the dish. Ray takes care of ordering these tasks properly, ensuring everything gets done at the right time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Libraries = Specialized Cookbooks&lt;/strong&gt;: Just as a chef might have cookbooks for various cuisines, Ray comes with built-in libraries that help you deal with common tasks. For instance, it has RLlib for reinforcement learning and Tune for optimizing hyperparameters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray Client = Restaurant Manager/Front of House&lt;/strong&gt;: Finally, much like a restaurant manager bridges the kitchen and the dining area, the Ray Client makes it easy for users outside the cluster to interact with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ray's Object Store&lt;/strong&gt; is like a restaurant's central pantry since it streamlines everything all Ray components (tasks/actors/workers) do by facilitating communication and possession of what is needed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Central Pantry (Object Store):&lt;/em&gt;&lt;br&gt;
The Object Store integrates everything from processed tasks and necessary tasks so there is one place for everyone to come together without issue. In a vast restaurant setting, it is the only way to operate, and a modern-day restaurant must become Ray.&lt;br&gt;
&lt;em&gt;The Ingredients (Objects):&lt;/em&gt;&lt;br&gt;
Everything that gets served eventually is in the pantry. It is all there as individual items or groups of items, if necessary, with separate and respective characteristics. In Ray, everything is stored and compartmentalized as an "object".&lt;br&gt;
&lt;em&gt;Access and Sharing:&lt;/em&gt;&lt;br&gt;
Since it is a shared pantry, one ingredient can be used by many chefs or dishes and there is no overlap or redundancy. This means that it is easily accessible and reapplied without offense. In Ray, Object Store allows multiple workers to easily access or repurpose what someone else has done so distributed processing is easier and quicker without wasting energy.&lt;br&gt;
&lt;em&gt;Efficiency:&lt;/em&gt;&lt;br&gt;
It saves time from searching everywhere and cuts down on ordering redundancy, excess shipping or transporting from other places. Everything they need is in-house. The same with Ray—if the Object Store can pre-exist certain uses, Ray saves on sending over certain pieces of data/transferring network access/computational time of selecting what is used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connecting it All — Ray as a Restaurant (single end-to-end flow)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Guest places order (&lt;em&gt;user request / Job starts&lt;/em&gt;): someone orders a meal — a job is sent to Ray.&lt;/li&gt;
&lt;li&gt;Restaurant manager (&lt;em&gt;Ray Core&lt;/em&gt; / &lt;em&gt;Scheduler&lt;/em&gt;) parses the order and breaks it down into tasks: Ray Core breaks the job down into tasks and schedules them to workers.&lt;/li&gt;
&lt;li&gt;Pantry &amp;amp; prep station (&lt;em&gt;Ray Data&lt;/em&gt;) prepare and stage ingredients: data pipelines partition, preprocess, cache, and share datasets for efficient access and effective workers.&lt;/li&gt;
&lt;li&gt;Head chef assigns &amp;amp; staff coordinate (&lt;em&gt;Scheduler _+ _Workers&lt;/em&gt; / Ray Train): the scheduler gives tasks to the cooks; workers perform distributed compute and training jobs (Ray Train), coordinating efforts for cooking and baking.&lt;/li&gt;
&lt;li&gt;Line cooks &amp;amp; sous-chefs execute dishes (&lt;em&gt;Workers&lt;/em&gt; / &lt;em&gt;Ray Train&lt;/em&gt;): workers perform the compute-heavy steps (model training, batch jobs, data transforms) on the ingredients.&lt;/li&gt;
&lt;li&gt;Tasting lab runs experiments (&lt;em&gt;Ray Tune&lt;/em&gt;): parallel experiments evaluate various recipe configurations (hyperparameters), feeding better versions back into training.&lt;/li&gt;
&lt;li&gt;Robot chef explores improvements continuously (&lt;em&gt;Ray RLlib&lt;/em&gt;): reinforcement-learning agents autonomously learn better policy/strategies that improve recipes/models.&lt;/li&gt;
&lt;li&gt;Waitstaff &amp;amp; hosts deliver dishes (&lt;em&gt;Ray Serve&lt;/em&gt;): Ray Serve hosts the trained model, manages inference requests, scales instances, adapts to traffic — bringing results to the user.&lt;/li&gt;
&lt;li&gt;Meal served &amp;amp; feedback loop (&lt;em&gt;Job done → iterate&lt;/em&gt;): the order is fulfilled; metrics, feedback fed into Ray Tune, RLlib, retraining, get into action for iterative development.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ray is like a restaurant that can easily ramp up for a busy night; it provides a means of scaling for computers to work collaboratively with ease on otherwise complicated tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Scaling the Kitchen&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Easy Scaling&lt;/em&gt;-Ray can add more machines to the cluster, just like a restaurant easily adds in more chefs for a rush, without any issues bringing in one, two, or three more trained professionals to manage a larger situation.&lt;br&gt;
&lt;em&gt;Execution Efficiency&lt;/em&gt;-Ray effectively works across many workers creating effective compilation of many hands and minds to cut down on complicated processes—like a kitchen where each team member takes part in a unique, critical role.&lt;br&gt;
&lt;em&gt;Complicated Execution&lt;/em&gt;-Ray can manage processes of advanced complexities adding in support to establish an all-encompassing effort—like a restaurant that can put out lots of meals with disparate processes, techniques and timing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-World Examples&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Recommendation Systems&lt;/strong&gt;&lt;br&gt;
Developers implement Ray by working on thousands of computations that require a quick turnaround—from movie recommendation engines that need instantaneous audience analytics to compile the best results.&lt;br&gt;
&lt;strong&gt;Scientific Inquiry&lt;/strong&gt;&lt;br&gt;
Ray engages in many pilot studies, from raw data assessment to model training, in research inquiries seeking simple predictive analytics or extensive exploration into existing data sets.&lt;br&gt;
&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Ray streamlines distributed computing for developers. When a once-strained restaurant manager can easily expand and organize and optimize service during a rush it creates better opportunities for quality work and better ideas. Similarly, Ray makes scaling and establishing powerful parallel projects to be simple and worthwhile endeavors, transforming developer thinking into reality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/adamnasim/" rel="noopener noreferrer"&gt;Connect with me on LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ray</category>
      <category>python</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Is Linux dead or awful</title>
      <dc:creator>Nasim</dc:creator>
      <pubDate>Thu, 29 Feb 2024 14:14:04 +0000</pubDate>
      <link>https://dev.to/n451m/is-linux-dead-or-awful-411p</link>
      <guid>https://dev.to/n451m/is-linux-dead-or-awful-411p</guid>
      <description>&lt;p&gt;The market share of Linux desktops is significantly lower compared to Windows and macOS, which dominate the desktop market. Some technologists have long predicted the &lt;a href="https://youtu.be/X_7AbdHbW4I" rel="noopener noreferrer"&gt;downfall&lt;/a&gt; of Linux. However, developers, system administrators, and enthusiasts continue to use Linux due to its flexibility, customization options, security features, and the availability of free and open-source software.&lt;br&gt;
Microsoft is still the sultan of OS, their Windows Mobile platform failed to generate interest compared to the pioneering iPhone and Android smartphones, resulting in a lackluster ecosystem with minimal interest. In contrast, Linux is gaining ground in both the mobile and server markets.&lt;br&gt;
Although Linux offers many benefits, such as flexibility, security, and open-source nature, it can be challenging for some users. Different Linux distributions have user interfaces that differ from Windows or macOS, which can be unfamiliar. Software compatibility may require workarounds, and the command-line interface can be daunting for users accustomed to graphical user interfaces. Additionally, while customization is a strength of Linux, it can be overwhelming for new users.&lt;br&gt;
Despite these challenges, the large and active Linux community provides support and documentation. However, finding the right resources and solutions for specific issues can be challenging for beginners. Nonetheless, with the right customization, Linux can be a superior tool if you have nerves, it offers increased privacy, a seamless desktop interface, and enhanced control. The anti-Linux cult has loyal disciples but to be fair all OS's have faults until it fits your use case.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
