<?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: ibrohim syarif</title>
    <description>The latest articles on DEV Community by ibrohim syarif (@ibrohhm).</description>
    <link>https://dev.to/ibrohhm</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%2F306251%2F1f2b50c6-3c55-4e8c-848a-8d24245ef573.jpeg</url>
      <title>DEV Community: ibrohim syarif</title>
      <link>https://dev.to/ibrohhm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ibrohhm"/>
    <language>en</language>
    <item>
      <title>Building an Autonomous Agent Team That Replicates My Engineering Workflow</title>
      <dc:creator>ibrohim syarif</dc:creator>
      <pubDate>Mon, 15 Jun 2026 17:22:23 +0000</pubDate>
      <link>https://dev.to/ibrohhm/building-an-autonomous-agent-team-that-replicates-my-engineering-workflow-2ne3</link>
      <guid>https://dev.to/ibrohhm/building-an-autonomous-agent-team-that-replicates-my-engineering-workflow-2ne3</guid>
      <description>&lt;p&gt;I've been working closely with agentic AI, and after a lot of iteration, I built a small agent team that can replicate the way I actually work — from reading a task to pushing a reviewable branch.&lt;/p&gt;

&lt;p&gt;In this post, will walk through the four specialized agents and one skill that orchestrates them end to end&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%2Fecubifgxxxscoqjn4jdu.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%2Fecubifgxxxscoqjn4jdu.png" alt="agent team workflow" width="799" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Model
&lt;/h2&gt;

&lt;p&gt;When I pick up a task, my workflow looks like this&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%2Fwjaj2zkztvvu8uy9y7bc.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%2Fwjaj2zkztvvu8uy9y7bc.png" alt="simple flow" width="798" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;the agent team mirrors this exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ship &amp;lt;task or Jira key&amp;gt;
      └─ clarifier     — is the task specific enough?
      └─ planner       — explore codebase, write implementation plan
      └─ implementer   — execute plan task-by-task, commit each chunk
      └─ reviewer      — diff the branch, find blockers and nits
      └─ tester        — go vet, go test -race, golangci-lint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The key insight: Each agent has one job and a fixed output contract. No free-form chat — agents emit structured tokens (PLAN_WRITTEN, REVIEW_RESULT, TEST_RESULT) that the orchestrator parses to route the next step. Its cheaper, faster, prevent AI to hallucinated&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Planner
&lt;/h2&gt;

&lt;p&gt;Before writing the code, we explore the codebase: find what already exists, check the dependencies, spot the blockers. The planner agent does the same&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%2Fhz6andyp5k7dcyg1mjy2.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%2Fhz6andyp5k7dcyg1mjy2.png" alt="Planner Agent Flow" width="648" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Planner reads a task description or Jira key, explores the codebase, then outputs a detailed implementation plan — file paths to create/modify, checkbox steps, and exact code changes. Detailed plans eliminate guessing by next subagents&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;planner&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Planner&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;receives&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;task&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;codebase&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;directory,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;explores&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;relevant&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;files,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;writes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;detailed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;implementation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;writing-plans&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;format."&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Glob, Grep, Bash&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opus&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Planner&lt;/span&gt;

Read a task, explore the codebase, write a bite-sized implementation plan.

&lt;span class="gu"&gt;## Input&lt;/span&gt;

TASK: &lt;span class="nt"&gt;&amp;lt;task&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt; &lt;span class="na"&gt;or&lt;/span&gt; &lt;span class="na"&gt;Jira&lt;/span&gt; &lt;span class="na"&gt;ticket&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
PLAN_PATH: &lt;span class="nt"&gt;&amp;lt;absolute&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt; &lt;span class="na"&gt;save&lt;/span&gt; &lt;span class="na"&gt;the&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
WORKDIR: &lt;span class="nt"&gt;&amp;lt;repo&lt;/span&gt; &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
JIRA_KEY: &lt;span class="nt"&gt;&amp;lt;optional&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;e.g.&lt;/span&gt; &lt;span class="na"&gt;TASK-1234&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Process&lt;/span&gt;

Use absolute paths throughout. Grep key terms, read relevant files, find reuse candidates

&lt;span class="gu"&gt;## Plan Format&lt;/span&gt;

&lt;span class="gh"&gt;# &amp;lt;Feature Name&amp;gt; Implementation Plan&lt;/span&gt;

&lt;span class="gs"&gt;**Goal:**&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;one&lt;/span&gt; &lt;span class="na"&gt;sentence&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="gs"&gt;**Architecture:**&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="na"&gt;-3&lt;/span&gt; &lt;span class="na"&gt;sentences&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="gs"&gt;**Tech Stack:**&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;key&lt;/span&gt; &lt;span class="na"&gt;technologies&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;
---
&lt;/span&gt;
Followed by numbered tasks. Each task must have:
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`**Files:**`&lt;/span&gt; — exact paths to create/modify/test
&lt;span class="p"&gt;-&lt;/span&gt; Checkbox steps (&lt;span class="sb"&gt;`- [ ]`&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; Real code in every code step (no placeholders)
&lt;span class="p"&gt;-&lt;/span&gt; Exact shell commands with expected output
&lt;span class="p"&gt;-&lt;/span&gt; TDD order: write failing test → run → implement → run again → commit

Rules:
&lt;span class="p"&gt;-&lt;/span&gt; No TBD, no TODO, no "similar to above"
&lt;span class="p"&gt;-&lt;/span&gt; Stage specific files: &lt;span class="sb"&gt;`git add &amp;lt;file&amp;gt;`&lt;/span&gt; (never &lt;span class="sb"&gt;`git add .`&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; Commit format: &lt;span class="sb"&gt;`&amp;lt;type&amp;gt;(&amp;lt;scope&amp;gt;): &amp;lt;subject&amp;gt;`&lt;/span&gt;

&lt;span class="gu"&gt;## Output&lt;/span&gt;
PLAN_WRITTEN: &lt;span class="nt"&gt;&amp;lt;PLAN_PATH&amp;gt;&lt;/span&gt;

If task is too vague:
AMBIGUOUS: &lt;span class="nt"&gt;&amp;lt;single&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt; &lt;span class="na"&gt;that&lt;/span&gt; &lt;span class="na"&gt;unblocks&lt;/span&gt; &lt;span class="na"&gt;planning&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementer
&lt;/h2&gt;

&lt;p&gt;The Implementer agent will reads the plan, create a new branch, executes every task in order, commits each chunk before moving to the next. Two modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normal mode&lt;/strong&gt; — follows the plan step by step. Stops immediately on test failure or build error. Never guesses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocker-fix mode&lt;/strong&gt; — activated when REVIEW_BLOCKERS is passed. Ignores the original plan. Fixes only the listed issues, re-runs tests, commits with fix(review): resolve review blockers.&lt;/p&gt;

&lt;p&gt;This dual mode is what makes the review-retry loop work&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%2Fq9vrhtnurbo69yniyuim.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%2Fq9vrhtnurbo69yniyuim.png" alt="implementer agent workflow" width="702" height="435"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;implementer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Implementer agent reads an implementation plan and executes it task-by-task, committing each chunk to the current branch.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Write, Edit, Bash, Glob, Grep&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Implementer&lt;/span&gt;

Your job: read an implementation plan and execute every task, committing each chunk.

&lt;span class="gu"&gt;## Input&lt;/span&gt;

You receive a message in this format:

PLAN_PATH: &lt;span class="nt"&gt;&amp;lt;absolute&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt; &lt;span class="na"&gt;the&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt; &lt;span class="na"&gt;markdown&lt;/span&gt; &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
BRANCH: &lt;span class="nt"&gt;&amp;lt;current&lt;/span&gt; &lt;span class="na"&gt;branch&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
REVIEW_BLOCKERS: (optional)

&lt;span class="gu"&gt;## Process&lt;/span&gt;

&lt;span class="gs"&gt;**If `REVIEW_BLOCKERS` is present in the input:**&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Ignore the plan at PLAN_PATH entirely
&lt;span class="p"&gt;2.&lt;/span&gt; Fix only the issues listed under REVIEW_BLOCKERS
&lt;span class="p"&gt;3.&lt;/span&gt; Run tests after fixing: &lt;span class="sb"&gt;`go vet ./... &amp;amp;&amp;amp; go test -race -short -count=1 ./...`&lt;/span&gt;
&lt;span class="p"&gt;4.&lt;/span&gt; If tests fail: stop immediately and report
&lt;span class="p"&gt;5.&lt;/span&gt; Stage and commit only the fixed files:
&lt;span class="p"&gt;   -&lt;/span&gt; Commit message: &lt;span class="sb"&gt;`fix(review): resolve review blockers`&lt;/span&gt;

&lt;span class="gs"&gt;**If `REVIEW_BLOCKERS` is absent (normal mode):**&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Read the plan at PLAN_PATH
&lt;span class="p"&gt;2.&lt;/span&gt; Execute tasks in order. For each task:
&lt;span class="p"&gt;   -&lt;/span&gt; Follow the checkbox steps exactly
&lt;span class="p"&gt;   -&lt;/span&gt; Run tests after each implementation step
&lt;span class="p"&gt;   -&lt;/span&gt; If a test fails: stop immediately
&lt;span class="p"&gt;   -&lt;/span&gt; If a build error occurs: stop immediately
&lt;span class="p"&gt;   -&lt;/span&gt; Stage and commit specific files after completing the task
&lt;span class="p"&gt;3.&lt;/span&gt; Count commits made

&lt;span class="gu"&gt;## Rules&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Commit format: &lt;span class="sb"&gt;`&amp;lt;type&amp;gt;(&amp;lt;scope&amp;gt;): &amp;lt;subject&amp;gt;`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; If a step says "run test to verify it fails" and it passes — stop and report the discrepancy
&lt;span class="p"&gt;-&lt;/span&gt; If blocked or confused — stop and report, do not guess

&lt;span class="gu"&gt;## Output&lt;/span&gt;

On success:
DONE: &lt;span class="nt"&gt;&amp;lt;N&amp;gt;&lt;/span&gt; commits on &lt;span class="nt"&gt;&amp;lt;BRANCH&amp;gt;&lt;/span&gt;

On failure:
FAIL: Task &lt;span class="nt"&gt;&amp;lt;N&amp;gt;&lt;/span&gt; "&lt;span class="nt"&gt;&amp;lt;task&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;" — &lt;span class="nt"&gt;&amp;lt;what&lt;/span&gt; &lt;span class="na"&gt;went&lt;/span&gt; &lt;span class="na"&gt;wrong&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reviewer
&lt;/h2&gt;

&lt;p&gt;The Reviewer agent will compare the branch against the default base and classifies every finding:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocker&lt;/strong&gt; — correctness bugs, security issues, data loss risk, nil dereference, breaking API contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nit&lt;/strong&gt; — naming inconsistency, redundant code, observability gaps, pattern deviation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signal bar&lt;/strong&gt; - findings below ~80% confidence are dropped. it reduce unnecessary review&lt;/p&gt;

&lt;p&gt;Move back all the bug findings to the Implementer agent&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%2Fcyyi05nk3motb507bf72.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%2Fcyyi05nk3motb507bf72.png" alt="reviewer agent flow" width="506" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reviewer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reviewer agent diffs a branch against the default base branch and emits structured Blocker/Nit findings. Blockers stop the pipeline.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Bash, Glob, Grep&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Reviewer&lt;/span&gt;

Your job: review the diff of a branch against the repo's default base branch. Emit findings. Blockers stop the ship pipeline.

&lt;span class="gu"&gt;## Input&lt;/span&gt;

You receive a message in this format:
BRANCH: &lt;span class="nt"&gt;&amp;lt;branch&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt; &lt;span class="na"&gt;review&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Process&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Detect base branch:
   BASE=$(git remote show origin 2&amp;gt;/dev/null | grep 'HEAD branch' | awk '{print $NF}')
   BASE=${BASE:-main}
&lt;span class="p"&gt;2.&lt;/span&gt; Get the diff:
   git diff ${BASE}...HEAD
&lt;span class="p"&gt;3.&lt;/span&gt; List changed files:
   git diff --name-only ${BASE}...HEAD
&lt;span class="p"&gt;4.&lt;/span&gt; For each changed file, read it in full if needed for context
&lt;span class="p"&gt;5.&lt;/span&gt; For each changed file, read surrounding code and direct callers for context — one level up only, at most 3 additional files total. Do not recurse further.
&lt;span class="p"&gt;6.&lt;/span&gt; Identify findings:
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Blocker**&lt;/span&gt;: correctness bug, security issue (SQL injection, secrets in code, auth bypass), data loss risk, nil/null dereference, off-by-one in critical path, missing error check on I/O, missing timeout/deadline on I/O call, missing idempotency key on mutation/payment op, inconsistent state risk (e.g. DB write succeeds but queue emit can fail with no rollback), breaking API contract (removed/renamed exported symbol, changed Kafka schema, removed HTTP route)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Nit**&lt;/span&gt;: naming inconsistency, redundant code, minor style deviation, missing doc comment on exported symbol, observability gap on critical path (missing metric, log correlation ID, or tracing span), pattern deviation (similar integrations in the codebase all have X — this one doesn't)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Signal bar**&lt;/span&gt;: only flag when confident. Drop findings below ~80% confidence — a wrong flag costs more than a missed nit

&lt;span class="gu"&gt;## Output format&lt;/span&gt;

Return exactly this structure when no blockers:
REVIEW_RESULT: PASS
BLOCKERS: none
NITS:
&lt;span class="p"&gt;-&lt;/span&gt; path/to/file.go:42 — unused variable &lt;span class="sb"&gt;`err`&lt;/span&gt; shadowed by inner scope

Or when blockers exist:

REVIEW_RESULT: BLOCKED
BLOCKERS:
&lt;span class="p"&gt;-&lt;/span&gt; path/to/file.go:15 — error from &lt;span class="sb"&gt;`rows.Scan`&lt;/span&gt; not checked, data silently ignored
NITS:
&lt;span class="p"&gt;-&lt;/span&gt; path/to/file.go:99 — naming: &lt;span class="sb"&gt;`getUser`&lt;/span&gt; should be &lt;span class="sb"&gt;`GetUser`&lt;/span&gt; (exported)

Rules:
&lt;span class="p"&gt;-&lt;/span&gt; Only flag real issues. Do not flag style preferences as blockers.
&lt;span class="p"&gt;-&lt;/span&gt; If diff is empty, return &lt;span class="sb"&gt;`REVIEW_RESULT: PASS`&lt;/span&gt; with &lt;span class="sb"&gt;`BLOCKERS: none`&lt;/span&gt; and &lt;span class="sb"&gt;`NITS: none`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tester
&lt;/h2&gt;

&lt;p&gt;The last one is tester agent. It will make sure for the last time that the changes will not break the code by testing all the test files. Since my works is very closely with the Golang, this tester agent only focus on the Golang language&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%2F3vgcch069dab1w01njov.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%2F3vgcch069dab1w01njov.png" alt="Tester agent flow" width="457" height="547"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tester&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tester agent runs go vet, go test -race -short, and golangci-lint (if .golangci.yml present). Returns PASS or FAIL with compact summary.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bash, Read&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;haiku&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Tester&lt;/span&gt;

Your job: run the test suite and report a one-line verdict.

&lt;span class="gu"&gt;## Input&lt;/span&gt;

You receive a message in this format:
WORKDIR: &lt;span class="nt"&gt;&amp;lt;absolute&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt; &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Process&lt;/span&gt;

First, detect repo type:
find &lt;span class="nt"&gt;&amp;lt;WORKDIR&amp;gt;&lt;/span&gt; -name "&lt;span class="err"&gt;*&lt;/span&gt;.go" | head -1

If no &lt;span class="sb"&gt;`.go`&lt;/span&gt; files found → return &lt;span class="sb"&gt;`TEST_RESULT: PASS`&lt;/span&gt; with note &lt;span class="sb"&gt;`No Go files found — skipping Go checks.`&lt;/span&gt; and stop.

If &lt;span class="sb"&gt;`.go`&lt;/span&gt; files exist, run these commands in order, stopping on first failure:
&lt;span class="p"&gt;
1.&lt;/span&gt; Go vet:
   go vet ./...
   (run from WORKDIR)
&lt;span class="p"&gt;
2.&lt;/span&gt; Go test:
   go test -race -short -count=1 -timeout 120s ./...
   (run from WORKDIR)
   Note: &lt;span class="sb"&gt;`-short`&lt;/span&gt; skips tests marked with &lt;span class="sb"&gt;`testing.Short()`&lt;/span&gt; — integration tests using that flag will not run.
&lt;span class="p"&gt;
3.&lt;/span&gt; Lint (only if &lt;span class="sb"&gt;`.golangci.yml`&lt;/span&gt; exists in WORKDIR):
   golangci-lint run
   (run from WORKDIR)

&lt;span class="gu"&gt;## Output&lt;/span&gt;

On full pass:
TEST_RESULT: PASS
All checks passed.

On failure:
TEST_RESULT: FAIL
&lt;span class="nt"&gt;&amp;lt;step&lt;/span&gt; &lt;span class="na"&gt;that&lt;/span&gt; &lt;span class="na"&gt;failed&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;: &lt;span class="nt"&gt;&amp;lt;error&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

Error output rules:
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`go vet`&lt;/span&gt;: include all output (usually short)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`go test`&lt;/span&gt;: include all lines containing &lt;span class="sb"&gt;`FAIL`&lt;/span&gt;, &lt;span class="sb"&gt;`panic`&lt;/span&gt;, or &lt;span class="sb"&gt;`Error`&lt;/span&gt;, plus the last 40 lines of output
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`golangci-lint`&lt;/span&gt;: include the first 30 lines of lint errors

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ship Skills
&lt;/h2&gt;

&lt;p&gt;All those agents will not run by their own, we still need skill to orchestrate those agent into workflow&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ship &amp;lt;task or Jira key&amp;gt;

Pipeline:

0. Clarifier → CLEAR or ask user one question
1. Create branch
2. Planner → PLAN_WRITTEN
3. Implementer (initial)
4. Review-retry loop (max 2 attempts)
   └─ BLOCKED → implementer fixes blockers → reviewer retries
5. Tester
6. Success summary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Design Decisions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Agents emit structured tokens (PLAN_WRITTEN:, REVIEW_RESULT:, BLOCKED:), not prose. Prose forces orchestrator to run a second LLM call just to extract intent — added latency, added cost, and a new failure surface for hallucinated routing. Structured tokens let orchestrator branch with a simple string match: deterministic, zero inference, no misroute&lt;/li&gt;
&lt;li&gt;80% confidence threshold — the most critical quality lever. False positives teach engineers to ignore the reviewer; high-noise output gets skipped, not fixed&lt;/li&gt;
&lt;li&gt;Different agents have different cost/capability tradeoffs. Planner needs deep reasoning (Opus). Reviewer needs precision (Sonnet). Tester just runs commands (Haiku). Wrong model assignment burns budget or misses findings&lt;/li&gt;
&lt;li&gt;Review-retry capped at 2. Uncapped loops are a denial-of-wallet attack on API credits&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you've ever caught yourself doing the same "explore → plan → implement → review → test" loop for the tenth time, you don't have to. The loop is automatable. You just have to write it down&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>agents</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>The Dangers of High-Cardinality Labels in Prometheus</title>
      <dc:creator>ibrohim syarif</dc:creator>
      <pubDate>Sun, 22 Feb 2026 04:51:57 +0000</pubDate>
      <link>https://dev.to/ibrohhm/the-dangers-of-high-cardinality-labels-in-prometheus-poi</link>
      <guid>https://dev.to/ibrohhm/the-dangers-of-high-cardinality-labels-in-prometheus-poi</guid>
      <description>&lt;p&gt;We're all familiar with the warnings: "&lt;em&gt;Don't use user_id as a Prometheus label&lt;/em&gt;" or "&lt;em&gt;Don't use transaction codes as labels — they can crash Prometheus&lt;/em&gt;". But do we really understand why these are so dangerous?&lt;/p&gt;

&lt;p&gt;Before that, we need to know how Prometheus works.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Prometheus Works
&lt;/h2&gt;

&lt;p&gt;Prometheus is an open-source systems monitoring and alerting tool that collects and stores its metrics as time-series data. It periodically scrapes metrics from your services based on the configured interval.&lt;/p&gt;

&lt;p&gt;This is an example of the config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scrape_configs:
  - job_name: 'golang-app'
    static_configs:
      - targets: ['localhost:8080']
    scrape_interval: 5s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This config will tell Prometheus to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target&lt;/strong&gt;: send an HTTP request &lt;code&gt;GET to http://localhost:8080/metrics&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Periodically&lt;/strong&gt;: for every 5 seconds&lt;br&gt;
&lt;strong&gt;Label&lt;/strong&gt;: with &lt;code&gt;job=golang-app&lt;/code&gt;&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%2Fe42h41ov5e38ed2hpzvj.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%2Fe42h41ov5e38ed2hpzvj.png" alt="how_prometheus_works" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prometheus has three metric types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gauges&lt;/strong&gt; represent current measurements and reflect the current state of a system, such as CPU usage and memory usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counters&lt;/strong&gt; measure discrete events that continuously increase over time. Common examples are the number of HTTP requests received, CPU seconds spent, and bytes sent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Histogram&lt;/strong&gt; tracks the distribution of observed values. For a base metric name &lt;code&gt;&amp;lt;basename&amp;gt;&lt;/code&gt;, it exposes multiple related time series:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;basename&amp;gt;_bucket{le="..."}&lt;/code&gt; — Cumulative counters representing the number of observations that fall within each bucket boundary&lt;br&gt;
&lt;code&gt;&amp;lt;basename&amp;gt;_sum&lt;/code&gt; — The total sum of all observed values&lt;br&gt;
&lt;code&gt;&amp;lt;basename&amp;gt;_count&lt;/code&gt; — The count of events that have been observed&lt;/p&gt;
&lt;h2&gt;
  
  
  Time Series Database (TSDB)
&lt;/h2&gt;

&lt;p&gt;Prometheus collects and stores metrics as time series. Each time series is uniquely identified by a metric name and a set of labels, while each sample within the series contains a timestamp and a value. Each unique combination of labels (method, path, and status) represents a separate time series whose value increases as more requests are processed, with total &lt;code&gt;method x path x status&lt;/code&gt;.&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%2Fdn5fmkmn7auzhkgka8rn.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%2Fdn5fmkmn7auzhkgka8rn.png" alt="methodxpathxstatus" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Counter metrics example, suppose we have two endpoints: &lt;code&gt;GET: /api/data&lt;/code&gt;, &lt;code&gt;GET: /api/users&lt;/code&gt;, and each of which can return either a &lt;code&gt;200&lt;/code&gt; or &lt;code&gt;500&lt;/code&gt; status code. This results in the following metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http_requests_total{method="GET", path="/api/data",  status="200"} 17
http_requests_total{method="GET", path="/api/data",  status="500"} 0
http_requests_total{method="GET", path="/api/users", status="200"} 10
http_requests_total{method="GET", path="/api/users", status="500"} 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because each time series represents a &lt;em&gt;unique combination of labels&lt;/em&gt;, these four label combinations produce four distinct time series. In the time-series database (TSDB), each of these time series is stored independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// time series 1
2026-02-19 09:00:00 | {__name__="http_requests_total", method="GET", path="/api/data", status="200"} | 15
2026-02-19 09:00:05 | {__name__="http_requests_total", method="GET", path="/api/data", status="200"} | 16
2026-02-19 09:00:10 | {__name__="http_requests_total", method="GET", path="/api/data", status="200"} | 17

// time series 2
2026-02-19 09:00:00 | {__name__="http_requests_total", method="GET", path="/api/data", status="500"} | 0
2026-02-19 09:00:05 | {__name__="http_requests_total", method="GET", path="/api/data", status="500"} | 0
2026-02-19 09:00:10 | {__name__="http_requests_total", method="GET", path="/api/data", status="500"} | 0

// time series 3
2026-02-19 09:00:00 | {__name__="http_requests_total", method="GET", path="/api/users", status="200"} | 8
2026-02-19 09:00:05 | {__name__="http_requests_total", method="GET", path="/api/users", status="200"} | 9
2026-02-19 09:00:10 | {__name__="http_requests_total", method="GET", path="/api/users", status="200"} | 10

// time series 4
2026-02-19 09:00:00 | {__name__="http_requests_total", method="GET", path="/api/users", status="500"} | 1
2026-02-19 09:00:05 | {__name__="http_requests_total", method="GET", path="/api/users", status="500"} | 2
2026-02-19 09:00:10 | {__name__="http_requests_total", method="GET", path="/api/users", status="500"} | 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Dangers
&lt;/h2&gt;

&lt;p&gt;Let's go back to the warning: "&lt;em&gt;Don't use user_id as a Prometheus label&lt;/em&gt;" or "&lt;em&gt;Don't use transaction codes as labels — they can crash Prometheus.&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;Imagine you want to record transaction latency using metric labels such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;status&lt;/code&gt;: &lt;code&gt;pending&lt;/code&gt;, &lt;code&gt;paid&lt;/code&gt;, &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;&lt;br&gt;
&lt;code&gt;payment_type&lt;/code&gt;: &lt;code&gt;wallet&lt;/code&gt;, &lt;code&gt;cash&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;status&lt;/code&gt; has 4 possible values, &lt;code&gt;payment_type&lt;/code&gt; has 2 possible values. It will produce &lt;code&gt;status (4) x payment_type (2) = 8 time series&lt;/code&gt;&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%2F0s1s5xzzvem70wkw12qj.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%2F0s1s5xzzvem70wkw12qj.png" alt="statusxpayment_type" width="771" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the example result of the metrics&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%2F100krkandp6x0r5wsj3s.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%2F100krkandp6x0r5wsj3s.png" alt="total processing time rate" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;there are exactly 8 label for the metrics&lt;/p&gt;

&lt;p&gt;Then, you adjust the metrics by adding a &lt;code&gt;code&lt;/code&gt; label, allowing request rates, error rates, and traffic patterns to be broken down per transaction&lt;/p&gt;

&lt;p&gt;&lt;code&gt;code&lt;/code&gt;: a unique identifier for each transaction&lt;/p&gt;

&lt;p&gt;However, &lt;code&gt;code&lt;/code&gt; is unique for every transaction and grows continuously with request volume. As a result, the number of possible values for &lt;code&gt;code&lt;/code&gt; is &lt;strong&gt;unbounded&lt;/strong&gt; and &lt;strong&gt;increases over time&lt;/strong&gt;. &lt;code&gt;status (4) × payment_type (2) × code (∞) = ∞ time series&lt;/code&gt;&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%2Fzg78isnri0arom4r2vxs.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%2Fzg78isnri0arom4r2vxs.png" alt="statusxpayment_typexcode" width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the example result of the metrics&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%2Fbxpfskghz8w5v40ofxxr.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%2Fbxpfskghz8w5v40ofxxr.png" alt="total processing time rate with code" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This single unbounded label is enough to turn an otherwise manageable metric into a high-cardinality time-series explosion that can cause memory exhaustion and query performance.&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%2Fsyxl4vuasgyc9gya0vfj.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%2Fsyxl4vuasgyc9gya0vfj.png" alt="nuke" width="559" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Adding labels whose values grow unbounded over time—such as UUIDs, timestamps, user IDs, or transaction codes—is strongly discouraged. These labels rarely add meaningful value at the metrics level and introduce high cardinality. For high-cardinality data, better use logging, not metrics&lt;/p&gt;

&lt;p&gt;High-cardinality labels can lead to serious issues, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Huge memory usage&lt;/strong&gt; — each unique label set creates a new time series&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rapid disk growth&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow queries&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scrape performance issue&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's better to use labels that have semantic meaning, and strongly recommended to keep the number of labels to a minimum&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A wise man says, "Never use a label whose value grows with users, requests, or time."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Good labels describe what something is, not who or which exact instance.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;code: &lt;a href="https://github.com/ibrohhm/prometheus-grafana-golang" rel="noopener noreferrer"&gt;https://github.com/ibrohhm/prometheus-grafana-golang&lt;/a&gt;&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Circuit Breaker Pattern</title>
      <dc:creator>ibrohim syarif</dc:creator>
      <pubDate>Wed, 04 Dec 2024 12:00:00 +0000</pubDate>
      <link>https://dev.to/ibrohhm/circuit-breaker-pattern-1775</link>
      <guid>https://dev.to/ibrohhm/circuit-breaker-pattern-1775</guid>
      <description>&lt;p&gt;Integrating with an external service often means dealing with unexpected behavior that impacts our service’s performance. The previous article prevents the crash/OOM by adding a timeout. But a timeout only limits how long you wait per request, it doesn’t stop you from waiting at all.&lt;/p&gt;

&lt;p&gt;If the partner has an outage for 20 minutes, do we actually need to keep sending requests to them? Every request still needs its full 3 seconds before it times out. It’s true that the timeout prevents our service from an OOM crash, but do we need to keep the 3-second wait for each request when we already know it’s broken?&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Once you’ve had a handful of failures in a row, there’s no reason to keep requesting. That’s the idea behind a circuit breaker: stop sending requests to a partner that’s already failing, and fail immediately instead of waiting to find out again.&lt;/p&gt;

&lt;p&gt;A circuit breaker has three states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Closed&lt;/strong&gt; — normal operation, meaning the service allows connections through normally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open&lt;/strong&gt; — once failures exceed the threshold, meaning the service no longer allows connections and returns an error immediately without touching the partner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Half-Open&lt;/strong&gt; — after a cooldown, the breaker lets a small number of test requests through. If they succeed, it closes again. If they fail, it stays open.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn3t66hvoqfcn8g2xdwb3.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%2Fn3t66hvoqfcn8g2xdwb3.png" alt="circuit breaker" width="464" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same setup as the previous article’s Case 3 — a 3-second timeout. Every caller experiences a flat 3-second latency spike for the entire outage.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzuci3e5r2hso0l4y383s.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%2Fzuci3e5r2hso0l4y383s.png" alt="3 second timeout latency" width="544" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the circuit breaker, the chart looks more like this:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgm4i9l9jxzgw7sqktn00.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%2Fgm4i9l9jxzgw7sqktn00.png" alt="circuit breaker latency" width="516" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s no more 3-second wait once the breaker has had enough failures to trip. Requests fail instantly, removing the unnecessary wait for an external call that we already know is broken.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;breaker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gobreaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gobreaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"partner-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// how long it stays open before probing again&lt;/span&gt;
    &lt;span class="n"&gt;ReadyToTrip&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="n"&gt;gobreaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConsecutiveFailures&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;fetchFromPartner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;breaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fetchFromPartnerWithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="c"&gt;// fails instantly once the breaker is open&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the 30 second Timeout window, the breaker moves to half-open and lets one request through to check if the partner recovered. If it succeeds, the breaker closes and traffic resumes normally. If it fails, the breaker opens again and waits another 30 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The timeout bounds how long a single request can wait. The circuit breaker bounds how many requests you send to a partner that’s already failing. Once it’s open, you stop paying the timeout cost on every call, and stop sending requests to an external service that is broken.&lt;/p&gt;

&lt;p&gt;The two work together: timeout limits the damage of any one request, and the breaker stops the damage from adding up once the partner is actually down.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>microservices</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Crash and Timeout Simulation</title>
      <dc:creator>ibrohim syarif</dc:creator>
      <pubDate>Sun, 21 Jul 2024 08:35:21 +0000</pubDate>
      <link>https://dev.to/ibrohhm/crash-and-timeout-simulation-jbp</link>
      <guid>https://dev.to/ibrohhm/crash-and-timeout-simulation-jbp</guid>
      <description>&lt;p&gt;Your team handles a service that has integration with external services, the service you cannot control. How do you make your service reliable regardless of what happens to the external service’s performance? How do you handle peak hours while the external service is having performance issues?&lt;/p&gt;

&lt;p&gt;Your job as the maintainer isn’t just shipping the code, but also preventing the chaos caused by that external service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simulation
&lt;/h2&gt;

&lt;p&gt;Each request to that external service holds a small chunk of your service’s memory until the request finishes. If requests pile up because the external service is slow, that memory stays locked up.&lt;/p&gt;

&lt;p&gt;More pending requests means more memory tied up. The number of pending requests depends on two things: &lt;strong&gt;how fast requests come in&lt;/strong&gt;, and &lt;strong&gt;how long each request takes&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;pending requests = request rate x request duration&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each request takes approximately 50 KB of memory, so 100 pending requests would take ~5 MB.&lt;/p&gt;

&lt;p&gt;It’s time for the simulation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 1: 100 rps with latency 200 milliseconds
&lt;/h3&gt;

&lt;p&gt;Your service integrates with a reliable external service — the max latency for a single request is only 200 milliseconds.&lt;/p&gt;

&lt;p&gt;At 100 rps with 200ms latency, pending requests stay steady at 100 × 0.2 = 20 — so memory ramps up fast, then flattens at ~20 × 50 KB = 1 MB and holds there. &lt;strong&gt;No growth, no crash.&lt;/strong&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fefoiqrchp9gz0yk6qrsq.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%2Fefoiqrchp9gz0yk6qrsq.png" alt="100 rps with latency 200 milliseconds" width="529" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 2: 100 rps with latency 20 seconds
&lt;/h3&gt;

&lt;p&gt;Something unexpectedly happens, the external service degrades badly, latency stretches to 20s.&lt;/p&gt;

&lt;p&gt;Pending requests are 100 × 20 = 2000, and at 50 KB memory for each request that’s ~100 MB — exactly your service’s memory limit. &lt;strong&gt;It never gets a chance to settle there&lt;/strong&gt;, the memory climbs straight into the ceiling, and the process gets &lt;strong&gt;OOM killed&lt;/strong&gt; — once memory goes over the limit, the kernel kills the process right away, with no chance to shut down cleanly first.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7kgo2wlqtal46ix0ehy.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%2Fk7kgo2wlqtal46ix0ehy.png" alt="100 rps with latency 20 second" width="557" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every pending request holds its memory until the external service finally responds. A 20 second hang at 100 rps is enough on its own, no traffic spike required.&lt;/p&gt;

&lt;p&gt;The simple way to solve the problem is by adding the timeout for each external request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 3: 100 rps with latency 20 second, with a 3 second timeout
&lt;/h3&gt;

&lt;p&gt;Same broken external service, same 20 second latency, same 100 rps, the only change is adding the timeout threshold 3s.&lt;/p&gt;

&lt;p&gt;The service will cut off any inflight request that takes longer than the threshold. The maximum latency will be 3s no matter how slow the external service actually is. Resulting pending = 100 × 3 = 300, or ~15 MB — nowhere near the 100 MB limit, regardless of how long the outage drags on.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8hdrvj3tt7gil8a8i38s.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%2F8hdrvj3tt7gil8a8i38s.png" alt="100 rps with latency 20 second, with a 3 second timeout" width="533" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The requests still fail, you’ll get a timeout error instead of a response, but the service itself keeps running, which means every other request still gets served.&lt;/p&gt;

&lt;p&gt;example code in golang:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequestWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodGet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partnerURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// ctx deadline exceeded after 3s: err wraps context.DeadlineExceeded&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The external service’s behavior was never something you could control. What you can control is how you handle that request. A simple timeout saves your service from crashing and prevents the cascading error for other internal services that call your service. But this is just a simulation, the request rate may vary in real cases, and there may be many external services you need to handle.&lt;/p&gt;

&lt;p&gt;Another solution that can control the request is a rate limiter that shrinks the request rate, or a circuit breaker that can automatically stop sending requests once the external service is failing. A resilient integration usually leans on all three together, not just one.&lt;/p&gt;

</description>
      <category>simulation</category>
      <category>timeout</category>
      <category>crash</category>
      <category>go</category>
    </item>
    <item>
      <title>Know Better About N+1 Queries Problem</title>
      <dc:creator>ibrohim syarif</dc:creator>
      <pubDate>Wed, 29 Nov 2023 16:41:42 +0000</pubDate>
      <link>https://dev.to/ibrohhm/know-better-about-n1-queries-problem-gpc</link>
      <guid>https://dev.to/ibrohhm/know-better-about-n1-queries-problem-gpc</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In the engineering process we often face the case of querying all the data based on its parent, and the data will be used for some reason. For example, the users table has a one-to-many correlation with the transactions table, and your job is to get all users and their transactions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;LoadData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;LoadResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;GetUsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;LoadResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;txs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;GetTransactionByUserID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LoadResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Transactions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;txs&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s really simple logic, but is it good enough? Is it bad? Can our service endure high throughput? Is there any way to make it more efficient?&lt;/p&gt;

&lt;h2&gt;
  
  
  Look Inside the Query
&lt;/h2&gt;

&lt;p&gt;The query depends on the number of users, so let’s say we have three users with id 1, 2, and 3. The queries from that code should look like this. The first one queries all the users based on the user_ids, and the 3 others are queries to fetch the transactions for each user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"transactions"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"user_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"transactions"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"user_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"transactions"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"user_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens if the user_ids list is huge? Will we query to fetch the transactions as many times as the number of users we have? Now we’re facing the N+1 queries problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  N+1 Query Problem
&lt;/h2&gt;

&lt;p&gt;This is a common problem in database queries: it executes the query one-by-one for every instance instead of 1 or 2 queries. In the example above we fetch all three users’ data, then continue with a query for all the transactions for each user — that’s 4 queries (1+3). If there are N users’ worth of data, first it fetches all N users, then continues to query the transactions for each user, so it’s called N+1 queries.&lt;/p&gt;

&lt;p&gt;The problem with N+1 queries is that each query takes some amount of time — the performance impact grows with data volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Instead of querying the transactions one-by-one, gather all the user_ids first, then query the transactions with all user_ids in one batch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;LoadData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;LoadResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;GetUsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;userIDs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;userIDs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userIDs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;txs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;GetTransactionByUserIDs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userIDs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;txsByUserID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;txs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;txsByUserID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txsByUserID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserID&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;LoadResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LoadResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Transactions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;txsByUserID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach reduces queries to just two: one fetching users and another fetching all their transactions in a single batch operation, then a cheap in-memory grouping step — significantly improving performance regardless of dataset size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;N+1 queries happen whenever you fetch a parent record, then loop over it to fetch each child record one by one. It’s easy to miss because the code reads simply and works fine in development with a handful of rows Next time a query feels slow, count how many times it’s actually hitting the database. If that number scales with your row count, you’ve found your N+1.&lt;/p&gt;

</description>
      <category>query</category>
    </item>
  </channel>
</rss>
