<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Divyansh Rawat — Blogs and Writing</title>
        <link>https://divyansh.is-a.dev/blog</link>
        <atom:link href="https://divyansh.is-a.dev/feed.xml" rel="self" type="application/rss+xml"/>
        <description>Notes on backend systems, agentic AI and the things I ship.</description>
        <language>en</language>
        <item>
            <title>Distributed Tracing Foundations &amp; The Jaeger v2 Architecture Shift</title>
            <link>https://divyansh.is-a.dev/blog/distributed-tracing-foundations-jaeger-v2</link>
            <guid isPermaLink="true">https://divyansh.is-a.dev/blog/distributed-tracing-foundations-jaeger-v2</guid>
            <pubDate>Mon, 26 Oct 2026 09:00:00 GMT</pubDate>
            <description>Why distributed tracing exists in microservices, the anatomy of W3C trace context propagation, and the paradigm shift behind Jaeger v2 rebuilding on the OpenTelemetry Collector framework.</description>
            <content:encoded><![CDATA[                <blockquote>
                <p><strong>Series:</strong> The Jaeger Engineering Reference (Article 1 of 10)<br><strong>Official Repository:</strong> <a href="https://github.com/jaegertracing/jaeger" target="_blank" rel="noopener"><code>jaegertracing/jaeger</code></a><br><strong>Documentation:</strong> <a href="https://www.jaegertracing.io/docs/" target="_blank" rel="noopener"><code>jaegertracing.io/docs</code></a><br><strong>Key Upstream Sources:</strong> <a href="https://github.com/jaegertracing/jaeger/tree/main/cmd/jaeger/" target="_blank" rel="noopener"><code>cmd/jaeger/</code></a>, <a href="https://github.com/jaegertracing/jaeger/blob/main/cmd/jaeger/main.go" target="_blank" rel="noopener"><code>cmd/jaeger/main.go</code></a>, <a href="https://github.com/jaegertracing/jaeger/tree/main/cmd/jaeger/internal/extension/jaegerquery/" target="_blank" rel="noopener"><code>cmd/jaeger/internal/extension/jaegerquery/</code></a></p>
                </blockquote>
                <hr>
                <h2 id="1-the-microservices-blind-spot-why-tracing-exists">1. The Microservices Blind Spot: Why Tracing Exists<a class="anchor" href="#1-the-microservices-blind-spot-why-tracing-exists" aria-label="Link to this section">#</a></h2>
                <p>In a classical monolithic architecture, an incoming request is serviced within a single operating system process boundary. When an endpoint encounters high latency or crashes, diagnosing the failure is relatively direct: an engineer inspects thread dumps, local application logs, or a stack trace using thread-local storage identifiers.</p>
                <p>In modern cloud-native distributed architectures, a single end-user action (such as clicking <strong>&quot;Place Order&quot;</strong> or invoking <code>POST /checkout</code>) fans out across dozens of decoupled microservices communicating across heterogeneous network transports (gRPC, HTTP/REST, Kafka, Redis).</p>
                <div class="code-wrap"><span class="code-lang">text</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-text">User            API Gateway            Order Service           Payment / Inventory            Database
                 |                   |                      |                           |                        |
                 +--POST /checkout--&gt;|                      |                           |                        |
                 |   [Root Span: T1-S1]                     |                           |                        |
                 |                   +--gRPC CreateOrder---&gt;|                           |                        |
                 |                   |  (W3C: T1-S1)        | [Child Span: T1-S2]       |                        |
                 |                   |                      +--HTTP /charge (T1-S2)----&gt;|                        |
                 |                   |                      |                           | [Span: T1-S3]          |
                 |                   |                      |&lt;--200 OK------------------+                        |
                 |                   |                      +--gRPC ReserveStock-------&gt;|                        |
                 |                   |                      |   (W3C: T1-S2)            | [Span: T1-S4]          |
                 |                   |                      |                           +--SQL UPDATE items-----&gt;|
                 |                   |                      |                           |&lt;--OK-------------------+
                 |                   |                      |&lt;--Stock Reserved----------+                        |
                 |                   |&lt;--Order Created------+                           |                        |
                 |&lt;--201 Created-----+                      |                           |                        |</code></pre></div>
                <div class="mermaid-wrap"><div class="mermaid">sequenceDiagram
                    autonumber
                    actor User
                    participant Gateway as API Gateway
                    participant OrderSvc as Order Service
                    participant PaymentSvc as Payment Service
                    participant InventorySvc as Inventory Service
                    participant DB as Postgres / Cassandra
                
                    User-&gt;&gt;Gateway: POST /checkout
                    Note over Gateway: Creates Root Span (TraceID: T1, SpanID: S1)
                    Gateway-&gt;&gt;OrderSvc: gRPC CreateOrder (Inject W3C traceparent: T1-S1)
                    Note over OrderSvc: Extracts Context, Child Span (TraceID: T1, SpanID: S2, ParentID: S1)
                    par Concurrent Calls
                        OrderSvc-&gt;&gt;PaymentSvc: HTTP /charge (traceparent: T1-S2)
                        Note over PaymentSvc: Child Span (TraceID: T1, SpanID: S3, ParentID: S2)
                        PaymentSvc--&gt;&gt;OrderSvc: 200 OK
                    and
                        OrderSvc-&gt;&gt;InventorySvc: gRPC ReserveStock (traceparent: T1-S2)
                        Note over InventorySvc: Child Span (TraceID: T1, SpanID: S4, ParentID: S2)
                        InventorySvc-&gt;&gt;DB: SQL UPDATE items
                        DB--&gt;&gt;InventorySvc: OK
                        InventorySvc--&gt;&gt;OrderSvc: Stock Reserved
                    end
                    OrderSvc--&gt;&gt;Gateway: Order Created
                    Gateway--&gt;&gt;User: 201 Created</div><details class="mermaid-source"><summary>View Diagram Definition</summary><div class="code-wrap"><span class="code-lang">mermaid</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-mermaid">sequenceDiagram
                    autonumber
                    actor User
                    participant Gateway as API Gateway
                    participant OrderSvc as Order Service
                    participant PaymentSvc as Payment Service
                    participant InventorySvc as Inventory Service
                    participant DB as Postgres / Cassandra
                
                    User-&gt;&gt;Gateway: POST /checkout
                    Note over Gateway: Creates Root Span (TraceID: T1, SpanID: S1)
                    Gateway-&gt;&gt;OrderSvc: gRPC CreateOrder (Inject W3C traceparent: T1-S1)
                    Note over OrderSvc: Extracts Context, Child Span (TraceID: T1, SpanID: S2, ParentID: S1)
                    par Concurrent Calls
                        OrderSvc-&gt;&gt;PaymentSvc: HTTP /charge (traceparent: T1-S2)
                        Note over PaymentSvc: Child Span (TraceID: T1, SpanID: S3, ParentID: S2)
                        PaymentSvc--&gt;&gt;OrderSvc: 200 OK
                    and
                        OrderSvc-&gt;&gt;InventorySvc: gRPC ReserveStock (traceparent: T1-S2)
                        Note over InventorySvc: Child Span (TraceID: T1, SpanID: S4, ParentID: S2)
                        InventorySvc-&gt;&gt;DB: SQL UPDATE items
                        DB--&gt;&gt;InventorySvc: OK
                        InventorySvc--&gt;&gt;OrderSvc: Stock Reserved
                    end
                    OrderSvc--&gt;&gt;Gateway: Order Created
                    Gateway--&gt;&gt;User: 201 Created</code></pre></div></details></div>
                <h3 id="the-three-fundamental-distributed-systems-blind-spots">The Three Fundamental Distributed Systems Blind Spots<a class="anchor" href="#the-three-fundamental-distributed-systems-blind-spots" aria-label="Link to this section">#</a></h3>
                <p>Traditional observability tooling (server metrics and isolated log collectors) fails to provide actionable insight in this environment due to three structural blind spots:</p>
                <ol>
                <li><strong>Network Boundary Obfuscation (Causal Erasure):</strong><ul>
                <li>The network boundary strips execution context. Operating system Thread IDs and Process IDs cannot cross TCP sockets or messaging queues.</li>
                <li>When Service A calls Service B, Service B has no native knowledge of who invoked it or which upstream user transaction initiated the request.</li>
                </ul>
                </li>
                <li><strong>Independent Log Silos (The Needle in a Haystack):</strong><ul>
                <li>High-throughput microservices independently write tens of thousands of log lines per second into disjoint log aggregates (Elasticsearch, Loki, CloudWatch).</li>
                <li>Searching across disparate log buckets without a globally unified correlation ID requires manual timestamp cross-referencing, which breaks under clock drift and asynchronous concurrency.</li>
                </ul>
                </li>
                <li><strong>The P99 Latency Mystery (Concurrency &amp; Contention):</strong><ul>
                <li>An API endpoint exhibiting an average latency of 80ms may suddenly spike to a 99th percentile (P99) latency of 3.8 seconds.</li>
                <li>Standard resource telemetry (host CPU usage, memory utilization, network bandwidth) appears completely normal because average resource consumption is low.</li>
                <li>Distributed tracing exposes the actual bottleneck: the Order Service spent 3.7 seconds waiting in an asynchronous queue or blocked on a transactional row lock inside PostgreSQL.</li>
                </ul>
                </li>
                </ol>
                <hr>
                <h2 id="2-the-core-anatomy-of-a-trace-w3c-specifications">2. The Core Anatomy of a Trace &amp; W3C Specifications<a class="anchor" href="#2-the-core-anatomy-of-a-trace-w3c-specifications" aria-label="Link to this section">#</a></h2>
                <p>Jaeger implements the distributed tracing data model originally formalized in Google Dapper research paper (<em>Dapper, a Large-Scale Distributed Systems Tracing Infrastructure</em>, Sigelman et al., 2010) and standardized today by the OpenTelemetry and W3C specifications.</p>
                <div class="code-wrap"><span class="code-lang">text</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-text">       [Span A: Root (HTTP POST /checkout)]
                                  +-- [Span B: gRPC CreateOrder]
                                  |          +-- [Span C: HTTP POST /charge]
                                  |          +-- [Span D: gRPC ReserveStock]
                                  |                     +-- [Span E: SQL UPDATE items]
                                  +-- [Span F: Background Audit Log Event]</code></pre></div>
                <h3 id="1-fundamental-entities-traces-vs-spans">1. Fundamental Entities: Traces vs Spans<a class="anchor" href="#1-fundamental-entities-traces-vs-spans" aria-label="Link to this section">#</a></h3>
                <ul>
                <li><strong>Trace:</strong> A Directed Acyclic Graph (DAG) of Spans representing an entire end-to-end transaction journey through a distributed topology.</li>
                <li><strong>Span:</strong> The atomic building block of a trace. A span represents a single contiguous unit of timed work executed by a specific component or service.</li>
                </ul>
                <h3 id="2-anatomy-of-a-span">2. Anatomy of a Span<a class="anchor" href="#2-anatomy-of-a-span" aria-label="Link to this section">#</a></h3>
                <p>Every span encapsulates the following attributes:</p>
                <table>
                <thead>
                <tr>
                <th align="left">Field</th>
                <th align="left">Data Type</th>
                <th align="left">Engineering Purpose</th>
                </tr>
                </thead>
                <tbody><tr>
                <td align="left"><strong><code>TraceID</code></strong></td>
                <td align="left">16-byte (128-bit hex)</td>
                <td align="left">Globally unique identifier shared across all spans belonging to the same transaction.</td>
                </tr>
                <tr>
                <td align="left"><strong><code>SpanID</code></strong></td>
                <td align="left">8-byte (64-bit hex)</td>
                <td align="left">Unique identifier for this specific operation within the trace.</td>
                </tr>
                <tr>
                <td align="left"><strong><code>ParentSpanID</code></strong></td>
                <td align="left">8-byte (64-bit hex)</td>
                <td align="left">The <code>SpanID</code> of the caller that initiated this work (empty for root spans).</td>
                </tr>
                <tr>
                <td align="left"><strong><code>OperationName</code></strong></td>
                <td align="left">UTF-8 String</td>
                <td align="left">Low-cardinality semantic descriptor of the work (e.g. <code>SELECT items</code>, <code>HTTP GET /users</code>).</td>
                </tr>
                <tr>
                <td align="left"><strong><code>StartTime</code> &amp; <code>EndTime</code></strong></td>
                <td align="left">Unix Nanoseconds</td>
                <td align="left">Precise wall-clock timestamps capturing start and end of work for latency calculation.</td>
                </tr>
                <tr>
                <td align="left"><strong><code>Attributes</code> (Tags)</strong></td>
                <td align="left">Key-Value Pairs</td>
                <td align="left">Structured metadata querying (e.g. <code>http.status_code=200</code>, <code>db.system=postgresql</code>).</td>
                </tr>
                <tr>
                <td align="left"><strong><code>Events</code> (Logs)</strong></td>
                <td align="left">Timestamped Records</td>
                <td align="left">Structured milestone annotations recorded during execution (e.g. exceptions or cache misses).</td>
                </tr>
                <tr>
                <td align="left"><strong><code>SpanKind</code></strong></td>
                <td align="left">Semantic Enum</td>
                <td align="left">Identifies role in network relationship: <code>SERVER</code>, <code>CLIENT</code>, <code>PRODUCER</code>, <code>CONSUMER</code>, or <code>INTERNAL</code>.</td>
                </tr>
                <tr>
                <td align="left"><strong><code>Links</code></strong></td>
                <td align="left">Array of SpanRefs</td>
                <td align="left">Asynchronous causal connections linking spans across different traces (batch processing or fan-in).</td>
                </tr>
                </tbody></table>
                <h3 id="3-context-propagation-the-w3c-traceparent-standard">3. Context Propagation &amp; The W3C <code>traceparent</code> Standard<a class="anchor" href="#3-context-propagation-the-w3c-traceparent-standard" aria-label="Link to this section">#</a></h3>
                <p>To preserve execution context across network boundaries, services inject and extract standard metadata headers defined by the W3C Trace Context specification:</p>
                <div class="code-wrap"><span class="code-lang">http</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-http"><span class="hljs-attribute">traceparent</span><span class="hljs-punctuation">: </span>00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
                             |  +--------------+-----------------+ +-------+--------+ +-++
                          version         16-byte TraceID             8-byte SpanID    flags</code></pre></div>
                <ul>
                <li><strong>Version (<code>00</code>):</strong> Current W3C specification version.</li>
                <li><strong>Trace ID (<code>4bf92f35...</code>):</strong> 16-byte random hex string identifying the overall trace.</li>
                <li><strong>Parent ID / Span ID (<code>00f067aa...</code>):</strong> 8-byte hex string identifying the calling span.</li>
                <li><strong>Trace Flags (<code>01</code>):</strong> 8-bit bitmap field; <code>01</code> signals that the trace was recorded / sampled (<code>FLAG_SAMPLED</code>).</li>
                </ul>
                <div class="code-wrap"><span class="code-lang">text</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-text">W3C Context Headers:
                  +-- traceparent : 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
                  +-- tracestate  : rojo=123,congo=456 (Vendor-specific state passing)
                  +-- baggage     : tenant=acme,tier=premium (Distributed application context)</code></pre></div>
                <ul>
                <li><strong><code>tracestate</code>:</strong> Carries vendor-specific routing state across multiple tracing systems without parsing or modifying the core <code>traceparent</code>.</li>
                <li><strong><code>baggage</code>:</strong> Carries arbitrary key-value pairs (e.g. <code>tenant_id=enterprise</code>) accessible to downstream services across the entire trace journey.</li>
                </ul>
                <hr>
                <h2 id="3-the-jaeger-v1-vs-v2-architectural-paradigm-shift">3. The Jaeger v1 vs v2 Architectural Paradigm Shift<a class="anchor" href="#3-the-jaeger-v1-vs-v2-architectural-paradigm-shift" aria-label="Link to this section">#</a></h2>
                <p>Historically, Jaeger operated as a bespoke ecosystem of Go microservices designed during the early microservices era:</p>
                <ul>
                <li><strong><code>jaeger-agent</code>:</strong> A local host daemon that accepted spans over Thrift UDP packets on port 6831.</li>
                <li><strong><code>jaeger-collector</code>:</strong> A custom Go application that received spans, validated them, buffered them in in-memory Go channel queues, and wrote them to storage plugins.</li>
                <li><strong><code>jaeger-query</code>:</strong> A standalone HTTP/gRPC server hosting the Jaeger UI and querying storage backends.</li>
                <li><strong><code>jaeger-ingester</code>:</strong> A specialized daemon consuming spans from Apache Kafka topics to decouple ingestion from storage writes.</li>
                </ul>
                <h3 id="architectural-bottlenecks-in-jaeger-v1">Architectural Bottlenecks in Jaeger v1<a class="anchor" href="#architectural-bottlenecks-in-jaeger-v1" aria-label="Link to this section">#</a></h3>
                <ol>
                <li><strong>UDP Packet Loss Under Load:</strong> The <code>jaeger-agent</code> relied on Thrift over UDP to avoid connection pooling overhead in client runtimes. Under network congestion or buffer saturation, UDP packets were silently dropped with zero backpressure.</li>
                <li><strong>In-Memory Channel Bottlenecks:</strong> <code>jaeger-collector</code> processed individual spans sequentially through Go channels. When databases slowed down, channel buffers overflowed, dropping spans (<code>dropped_spans</code> metric).</li>
                <li><strong>Plumbing Duplication:</strong> Jaeger maintainers had to build, maintain, and secure custom TLS handshakes, HTTP/gRPC servers, health checks, and metrics exporters, duplicating work done by the wider Cloud Native Computing Foundation (CNCF) ecosystem.</li>
                </ol>
                <h3 id="the-jaeger-v2-architecture-an-opentelemetry-collector-distribution">The Jaeger v2 Architecture: An OpenTelemetry Collector Distribution<a class="anchor" href="#the-jaeger-v2-architecture-an-opentelemetry-collector-distribution" aria-label="Link to this section">#</a></h3>
                <p>In <strong>Jaeger v2</strong>, the architecture underwent a complete re-platforming: <strong>Jaeger v2 is built directly on top of the OpenTelemetry Collector framework (<code>go.opentelemetry.io/collector</code>)</strong>.</p>
                <div class="code-wrap"><span class="code-lang">text</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-text">Jaeger v1 (Legacy Monolith &amp; Custom Microservices):
                  [Client App] --Thrift UDP--&gt; [jaeger-agent] --Channel Queue--&gt; [jaeger-collector] --&gt; [DB Storage]
                                                                                                             ^
                  [User UI] &lt;--REST/gRPC API----------------------------------- [jaeger-query] -------------+
                
                Jaeger v2 (Unified OpenTelemetry Collector Base):
                  [Client App] --OTLP gRPC/HTTP (:4317/:4318)--+
                                                               v
                  +--------------------------------- OTel Collector Engine ---------------------------------+
                  | Receivers (OTLP, Jaeger, Zipkin) --&gt; Processors (Batch, Sampling) --&gt; Exporters (Storage)|
                  +--------------------------------------------+--------------------------------------------+
                                                               v
                                              [internal/storage/v2/api/tracestore]
                                                               |
                                       +-----------------------+-----------------------+
                                       v                                               v
                  [Backends: ClickHouse, ES, Cassandra, Memory]     [Extensions: jaegerquery &amp; jaegerstorage]</code></pre></div>
                <div class="mermaid-wrap"><div class="mermaid">flowchart TD
                    subgraph &quot;Jaeger v1 (Legacy Custom Microservices)&quot;
                        V1Agent[&quot;jaeger-agent&lt;br/&gt;(Zipkin/Thrift UDP :6831)&quot;] --&gt; V1Collector[&quot;jaeger-collector&lt;br/&gt;(Custom Channel Pipeline)&quot;]
                        V1Collector --&gt; V1Storage[(&quot;Custom Storage Plugins&quot;)]
                        V1Query[&quot;jaeger-query&lt;br/&gt;(Custom REST/gRPC :16686)&quot;] --&gt; V1Storage
                    end
                
                    subgraph &quot;Jaeger v2 (OpenTelemetry Collector Architecture)&quot;
                        OTLPSource[&quot;OTLP Ingestion&lt;br/&gt;(gRPC :4317 / HTTP :4318)&quot;] --&gt; Pipeline
                        subgraph Pipeline[&quot;OpenTelemetry Pipeline Engine&quot;]
                            Receivers[&quot;Receivers&lt;br/&gt;(otlp, jaeger, zipkin)&quot;] --&gt; Processors[&quot;Processors&lt;br/&gt;(memory_limiter, batch, adaptive_sampling)&quot;]
                            Processors --&gt; Exporters[&quot;Exporters&lt;br/&gt;(jaeger_storage_exporter)&quot;]
                        end
                        Exporters --&gt; V2SPI[&quot;Storage SPI: internal/storage/v2/api/tracestore&quot;]
                        V2SPI --&gt; Backends[(&quot;Backends: ClickHouse, Elasticsearch, Memory&quot;)]
                        ExtQuery[&quot;Extension: jaegerquery&lt;br/&gt;(UI, REST, gRPC v3)&quot;] --&gt; V2SPI
                        ExtStorage[&quot;Extension: jaegerstorage&lt;br/&gt;(Shared Connection Pools)&quot;] -.-&gt; V2SPI
                    end</div><details class="mermaid-source"><summary>View Diagram Definition</summary><div class="code-wrap"><span class="code-lang">mermaid</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-mermaid">flowchart TD
                    subgraph &quot;Jaeger v1 (Legacy Custom Microservices)&quot;
                        V1Agent[&quot;jaeger-agent&lt;br/&gt;(Zipkin/Thrift UDP :6831)&quot;] --&gt; V1Collector[&quot;jaeger-collector&lt;br/&gt;(Custom Channel Pipeline)&quot;]
                        V1Collector --&gt; V1Storage[(&quot;Custom Storage Plugins&quot;)]
                        V1Query[&quot;jaeger-query&lt;br/&gt;(Custom REST/gRPC :16686)&quot;] --&gt; V1Storage
                    end
                
                    subgraph &quot;Jaeger v2 (OpenTelemetry Collector Architecture)&quot;
                        OTLPSource[&quot;OTLP Ingestion&lt;br/&gt;(gRPC :4317 / HTTP :4318)&quot;] --&gt; Pipeline
                        subgraph Pipeline[&quot;OpenTelemetry Pipeline Engine&quot;]
                            Receivers[&quot;Receivers&lt;br/&gt;(otlp, jaeger, zipkin)&quot;] --&gt; Processors[&quot;Processors&lt;br/&gt;(memory_limiter, batch, adaptive_sampling)&quot;]
                            Processors --&gt; Exporters[&quot;Exporters&lt;br/&gt;(jaeger_storage_exporter)&quot;]
                        end
                        Exporters --&gt; V2SPI[&quot;Storage SPI: internal/storage/v2/api/tracestore&quot;]
                        V2SPI --&gt; Backends[(&quot;Backends: ClickHouse, Elasticsearch, Memory&quot;)]
                        ExtQuery[&quot;Extension: jaegerquery&lt;br/&gt;(UI, REST, gRPC v3)&quot;] --&gt; V2SPI
                        ExtStorage[&quot;Extension: jaegerstorage&lt;br/&gt;(Shared Connection Pools)&quot;] -.-&gt; V2SPI
                    end</code></pre></div></details></div>
                <h3 id="key-architectural-advantages-of-jaeger-v2">Key Architectural Advantages of Jaeger v2<a class="anchor" href="#key-architectural-advantages-of-jaeger-v2" aria-label="Link to this section">#</a></h3>
                <ol>
                <li><strong>Native OTLP Ingestion:</strong> Replaces custom Thrift protocols with native OpenTelemetry Protocol (<code>OTLP/gRPC</code> on port 4317 and <code>OTLP/HTTP</code> on port 4318).</li>
                <li><strong>From Single Spans to Native Batches (<code>ptrace.Traces</code>):</strong> Jaeger v2 processes spans using OpenTelemetry Collector native columnar in-memory data structures (<code>ptrace.Traces</code>), unlocking massive bulk insert efficiencies on columnar engines like ClickHouse.</li>
                <li><strong>The Collector Extension Model:</strong><ul>
                <li><strong><code>jaegerstorage</code> Extension:</strong> Acts as a centralized singleton providing shared, thread-safe database handles, connection pools, and health checks across ingestion and query paths.</li>
                <li><strong><code>jaegerquery</code> Extension:</strong> Packages the Jaeger UI, HTTP query endpoints, and gRPC Query APIs as a native Collector extension (<code>service.Extension</code>).</li>
                </ul>
                </li>
                <li><strong>Standardized Configuration (<code>jaeger.yaml</code>):</strong> Replaces dozens of fragmented CLI flags with structured, validated YAML schemas conforming to standard OpenTelemetry Collector syntax.</li>
                </ol>
                <hr>
                <h2 id="4-production-jaeger-v2-configuration-walkthrough">4. Production Jaeger v2 Configuration Walkthrough<a class="anchor" href="#4-production-jaeger-v2-configuration-walkthrough" aria-label="Link to this section">#</a></h2>
                <p>Below is a production-ready Jaeger v2 configuration file illustrating how receivers, processors, exporters, extensions, and pipelines fit together:</p>
                <div class="code-wrap"><span class="code-lang">yaml</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-yaml"><span class="hljs-attr">service:</span>
                  <span class="hljs-attr">extensions:</span> [<span class="hljs-string">jaegerstorage</span>, <span class="hljs-string">jaegerquery</span>, <span class="hljs-string">health_check</span>]
                  <span class="hljs-attr">pipelines:</span>
                    <span class="hljs-attr">traces:</span>
                      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>, <span class="hljs-string">jaeger</span>]
                      <span class="hljs-attr">processors:</span> [<span class="hljs-string">memory_limiter</span>, <span class="hljs-string">batch</span>]
                      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">jaeger_storage_exporter</span>]
                  <span class="hljs-attr">telemetry:</span>
                    <span class="hljs-attr">metrics:</span>
                      <span class="hljs-attr">address:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:8888</span>
                
                <span class="hljs-attr">extensions:</span>
                  <span class="hljs-attr">health_check:</span>
                    <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:13133</span>
                  <span class="hljs-attr">jaegerstorage:</span>
                    <span class="hljs-attr">backends:</span>
                      <span class="hljs-attr">clickhouse:</span>
                        <span class="hljs-attr">endpoint:</span> <span class="hljs-string">tcp://clickhouse:9000</span>
                        <span class="hljs-attr">database:</span> <span class="hljs-string">jaeger</span>
                  <span class="hljs-attr">jaegerquery:</span>
                    <span class="hljs-attr">storage:</span>
                      <span class="hljs-attr">traces:</span> <span class="hljs-string">clickhouse</span>
                    <span class="hljs-attr">ui:</span>
                      <span class="hljs-attr">config_file:</span> <span class="hljs-string">/etc/jaeger/ui.json</span>
                
                <span class="hljs-attr">receivers:</span>
                  <span class="hljs-attr">otlp:</span>
                    <span class="hljs-attr">protocols:</span>
                      <span class="hljs-attr">grpc:</span>
                        <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:4317</span>
                      <span class="hljs-attr">http:</span>
                        <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:4318</span>
                  <span class="hljs-attr">jaeger:</span>
                    <span class="hljs-attr">protocols:</span>
                      <span class="hljs-attr">thrift_compact:</span>
                        <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:6831</span>
                
                <span class="hljs-attr">processors:</span>
                  <span class="hljs-attr">memory_limiter:</span>
                    <span class="hljs-attr">check_interval:</span> <span class="hljs-string">1s</span>
                    <span class="hljs-attr">limit_percentage:</span> <span class="hljs-number">80</span>
                    <span class="hljs-attr">spike_limit_percentage:</span> <span class="hljs-number">20</span>
                  <span class="hljs-attr">batch:</span>
                    <span class="hljs-attr">send_batch_size:</span> <span class="hljs-number">8192</span>
                    <span class="hljs-attr">timeout:</span> <span class="hljs-string">200ms</span>
                    <span class="hljs-attr">send_batch_max_size:</span> <span class="hljs-number">16384</span>
                
                <span class="hljs-attr">exporters:</span>
                  <span class="hljs-attr">jaeger_storage_exporter:</span>
                    <span class="hljs-attr">trace_storage:</span> <span class="hljs-string">clickhouse</span></code></pre></div>
                <hr>
                <h2 id="5-the-three-tier-engineering-analysis">5. The Three-Tier Engineering Analysis<a class="anchor" href="#5-the-three-tier-engineering-analysis" aria-label="Link to this section">#</a></h2>
                <h3 id="level-1-beginner-conceptual-mental-model">Level 1: Beginner / Conceptual Mental Model<a class="anchor" href="#level-1-beginner-conceptual-mental-model" aria-label="Link to this section">#</a></h3>
                <ul>
                <li><strong>The International Shipping Container Analogy:</strong><ul>
                <li>In early freight transport, every shipping company operated custom-built carts, crates, and railway wagons. Moving cargo from a ship to a train required unloading, repacking, and re-sorting every package by hand.</li>
                <li>In modern global logistics, everyone uses the <strong>standardized ISO shipping container</strong> (OpenTelemetry).</li>
                <li><strong>Jaeger v1</strong> was a custom truck fleet that only accepted proprietary crates.</li>
                <li><strong>Jaeger v2</strong> is the high-throughput logistics hub: it accepts standardized containers from any carrier, verifies their seals, routes them through inspection scanners (processors), and stores them in high-density automated warehouses (ClickHouse/Elasticsearch).</li>
                </ul>
                </li>
                </ul>
                <h3 id="level-2-intermediate-go-systems-engineer">Level 2: Intermediate Go Systems Engineer<a class="anchor" href="#level-2-intermediate-go-systems-engineer" aria-label="Link to this section">#</a></h3>
                <p>Let us examine how Jaeger v2 initializes itself as an OpenTelemetry Collector distribution inside <a href="https://github.com/jaegertracing/jaeger/blob/main/cmd/jaeger/main.go" target="_blank" rel="noopener"><code>cmd/jaeger/main.go</code></a>:</p>
                <div class="code-wrap"><span class="code-lang">go</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-go"><span class="hljs-keyword">package</span> main
                
                <span class="hljs-keyword">import</span> (
                	<span class="hljs-string">&quot;log&quot;</span>
                
                	<span class="hljs-string">&quot;go.opentelemetry.io/collector/component&quot;</span>
                	<span class="hljs-string">&quot;go.opentelemetry.io/collector/otelcol&quot;</span>
                
                	<span class="hljs-string">&quot;github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerstorage&quot;</span>
                	<span class="hljs-string">&quot;github.com/jaegertracing/jaeger/internal/extension/jaegerquery&quot;</span>
                )
                
                <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
                	info := component.BuildInfo{
                		Command:     <span class="hljs-string">&quot;jaeger&quot;</span>,
                		Description: <span class="hljs-string">&quot;Jaeger distributed tracing platform&quot;</span>,
                		Version:     <span class="hljs-string">&quot;2.x&quot;</span>,
                	}
                
                	params := otelcol.CollectorSettings{
                		BuildInfo: info,
                		Factories: components, <span class="hljs-comment">// Bundles all receivers, processors, exporters, extensions</span>
                	}
                
                	<span class="hljs-keyword">if</span> err := run(params); err != <span class="hljs-literal">nil</span> {
                		log.Fatalf(<span class="hljs-string">&quot;collector server run finished with error: %v&quot;</span>, err)
                	}
                }</code></pre></div>
                <h4 id="key-implementation-insights">Key Implementation Insights:<a class="anchor" href="#key-implementation-insights" aria-label="Link to this section">#</a></h4>
                <ul>
                <li>Rather than invoking <code>http.ListenAndServe</code> or configuring raw gRPC servers manually, Jaeger v2 registers its components into <code>otelcol.CollectorSettings</code>.</li>
                <li>Lifecycle events (<code>Start</code>, <code>Shutdown</code>, health probing, signal handling, and configuration hot-reloading) are coordinated directly by the OpenTelemetry Collector runtime.</li>
                <li>Custom Jaeger capabilities (e.g. storage drivers and UI query endpoints) are implemented cleanly as Collector components adhering to <code>component.Component</code> and <code>service.Extension</code> contracts.</li>
                </ul>
                <h3 id="level-3-senior-staff-systems-architect">Level 3: Senior / Staff Systems Architect<a class="anchor" href="#level-3-senior-staff-systems-architect" aria-label="Link to this section">#</a></h3>
                <p>At enterprise scale (100,000+ spans/second per node), the architectural shift to v2 eliminates systemic failure modes:</p>
                <ol>
                <li><strong>Backpressure &amp; Memory Bounds (OOM Prevention):</strong><ul>
                <li>In Jaeger v1, downstream database latency spikes caused in-memory Go channel queues to fill rapidly, resulting in silent span loss or container OOMKilled crashes.</li>
                <li>In v2, the <code>memory_limiter</code> processor constantly measures process memory. When thresholds are breached, it exerts backpressure by returning <code>Unavailable</code> / <code>ResourceExhausted</code> over gRPC, forcing callers to back off or buffer locally.</li>
                </ul>
                </li>
                <li><strong>Shared Database Connection Pools:</strong><ul>
                <li>In v1, running the collector and query service in the same container meant each process established independent connection pools, multiplying connection overhead against Elasticsearch or Cassandra.</li>
                <li>In v2, the <code>jaegerstorage</code> Extension acts as a singleton database provider. The ingestion pipeline and the query service share the same underlying driver instances, connection pooling, prepared statements, and health checks.</li>
                </ul>
                </li>
                <li><strong>Zero-Allocation Columnar Ingestion:</strong><ul>
                <li>The OpenTelemetry Collector uses <code>pdata/ptrace</code> internal structures designed for zero-allocation mutation and sequential memory layouts.</li>
                <li>Converting OTLP batches directly into ClickHouse block formats minimizes garbage collector (GC) pause times and heap churn.</li>
                </ul>
                </li>
                </ol>
                <hr>
                <h2 id="6-summary-key-takeaways">6. Summary &amp; Key Takeaways<a class="anchor" href="#6-summary-key-takeaways" aria-label="Link to this section">#</a></h2>
                <ul>
                <li><strong>Distributed Tracing</strong> solves the causal visibility gap in microservices using 128-bit TraceIDs and 64-bit SpanIDs propagated via standardized W3C <code>traceparent</code> headers.</li>
                <li><strong>Spans</strong> encapsulate execution timing, attributes, structured events, semantic kinds, and causal links into a Directed Acyclic Graph (DAG).</li>
                <li><strong>Jaeger v2</strong> is not merely an incremental release; it is an OpenTelemetry Collector distribution that unifies ingestion, processing, and querying into a single, scalable, production-grade runtime.</li>
                <li>The transition replaces fragile Thrift/UDP collectors with OTLP standard receivers, batch pipelines, memory limiter guards, and extension-based database singletons.</li>
                </ul>
                <hr>
                <h3 id="official-references-and-documentation">Official References and Documentation<a class="anchor" href="#official-references-and-documentation" aria-label="Link to this section">#</a></h3>
                <ul>
                <li>Official Repository: <a href="https://github.com/jaegertracing/jaeger" target="_blank" rel="noopener">GitHub - jaegertracing/jaeger</a></li>
                <li>Official Documentation: <a href="https://www.jaegertracing.io/docs/" target="_blank" rel="noopener">Jaeger Tracing Documentation</a></li>
                <li>Architecture and OpenTelemetry Migration: <a href="https://github.com/jaegertracing/jaeger/tree/main/cmd/jaeger" target="_blank" rel="noopener">Jaeger v2 Architecture Source (<code>cmd/jaeger</code>)</a></li>
                <li>OpenTelemetry Collector Framework: <a href="https://opentelemetry.io/docs/collector/" target="_blank" rel="noopener">OpenTelemetry Collector Documentation</a></li>
                <li>W3C Trace Context Specification: <a href="https://www.w3.org/TR/trace-context/" target="_blank" rel="noopener">W3C Recommendation for Distributed Tracing</a></li>
                </ul>
                <hr>
                <h3 id="author">Author<a class="anchor" href="#author" aria-label="Link to this section">#</a></h3>
                <ul>
                <li><strong>Author:</strong> Divyansh Rawat</li>
                <li><strong>GitHub:</strong> <a href="https://github.com/DsThakurRawat" target="_blank" rel="noopener">@DsThakurRawat</a></li>
                <li><strong>Series:</strong> The Jaeger Engineering Reference Series</li>
                </ul>]]></content:encoded>
            <category>distributed-tracing</category>
            <category>jaeger</category>
            <category>opentelemetry</category>
            <category>microservices</category>
            <category>go</category>
        </item>
        <item>
            <title>The contribution heatmap on this site draws fewer weeks on a phone</title>
            <link>https://divyansh.is-a.dev/blog/heatmaps-that-fit-the-phone</link>
            <guid isPermaLink="true">https://divyansh.is-a.dev/blog/heatmaps-that-fit-the-phone</guid>
            <pubDate>Tue, 18 Aug 2026 09:00:00 GMT</pubDate>
            <description>A 53-week grid is 742px wide. No phone can show that, so the grid asks how much room it has and draws only the columns that fit.</description>
            <content:encoded><![CDATA[                <p>Every GitHub-style contribution heatmap has the same problem on mobile. The grid is 53 columns of 7 cells. At a 12px cell with a 2px gap that is 742 pixels, and the widest phone in common use gives you about 390. Most sites solve it by putting the grid in a horizontal scroller.</p>
                <p>That solution is worse than it looks. The most interesting part of a contribution graph is the right-hand edge, because that is this week. Put it in a scroller and the default view shows a year ago, with the part anyone cares about parked off-screen behind a gesture nobody performs.</p>
                <h2 id="ask-the-container-first">Ask the container first<a class="anchor" href="#ask-the-container-first" aria-label="Link to this section">#</a></h2>
                <p>The grid on this page measures its own host before it draws anything:</p>
                <div class="code-wrap"><span class="code-lang">js</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">weeksThatFit</span>(<span class="hljs-params">prefix</span>) {
                    <span class="hljs-keyword">var</span> grid = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(prefix + <span class="hljs-string">&#x27;-grid&#x27;</span>);
                    <span class="hljs-keyword">var</span> host = grid &amp;&amp; grid.<span class="hljs-property">parentElement</span>;
                    <span class="hljs-keyword">var</span> w = host ? host.<span class="hljs-property">clientWidth</span> : <span class="hljs-number">0</span>;
                    <span class="hljs-keyword">if</span> (!w) <span class="hljs-keyword">return</span> <span class="hljs-variable constant_">WEEKS_MAX</span>;
                    <span class="hljs-keyword">return</span> <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">max</span>(<span class="hljs-number">10</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">min</span>(<span class="hljs-variable constant_">WEEKS_MAX</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">floor</span>(w / <span class="hljs-title function_">cellStep</span>())));
                }</code></pre></div>
                <p><code>cellStep()</code> returns 14 on desktop and 12 below 560px, matching the cell and gap sizes in the stylesheet. Whatever comes back is how many week-columns get rendered, ending with the current week. On a laptop that is the full 53. On a phone it is closer to 30.</p>
                <p>The count under the grid then has to stop lying about the range:</p>
                <div class="code-wrap"><span class="code-lang">js</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-js"><span class="hljs-keyword">var</span> span = <span class="hljs-variable constant_">WEEKS</span> &gt;= <span class="hljs-variable constant_">WEEKS_MAX</span>
                    ? <span class="hljs-string">&#x27;in the last year&#x27;</span>
                    : <span class="hljs-string">&#x27;in the last &#x27;</span> + <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">max</span>(<span class="hljs-number">1</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">round</span>(<span class="hljs-variable constant_">WEEKS</span> / <span class="hljs-number">4.345</span>)) + <span class="hljs-string">&#x27; months&#x27;</span>;</code></pre></div>
                <p>So a phone reads &quot;412 contributions in the last 7 months&quot; instead of claiming a year it never drew. Fewer weeks, no scrollbar, and the label is still true.</p>
                <p>There is one catch. Column count depends on width, so rotating the phone invalidates the layout. A resize listener re-runs <code>weeksThatFit</code> and only re-renders when the number actually changed, which is why the rendered counts are kept in memory rather than refetched.</p>
                <h2 id="shading-against-yourself-not-against-a-constant">Shading against yourself, not against a constant<a class="anchor" href="#shading-against-yourself-not-against-a-constant" aria-label="Link to this section">#</a></h2>
                <p>The other thing worth doing differently is the colour ramp. Fixed thresholds — 1 commit is level one, 10 is level four — make a normal month look empty next to whoever you borrowed the thresholds from.</p>
                <p>These cells are bucketed against the quartiles of the person&#39;s own non-zero days:</p>
                <div class="code-wrap"><span class="code-lang">js</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-js"><span class="hljs-keyword">var</span> q = <span class="hljs-keyword">function</span> (<span class="hljs-params">p</span>) { <span class="hljs-keyword">return</span> vals[<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">min</span>(vals.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">floor</span>(vals.<span class="hljs-property">length</span> * p))]; };
                <span class="hljs-keyword">var</span> t1 = <span class="hljs-title function_">q</span>(<span class="hljs-number">0.25</span>), t2 = <span class="hljs-title function_">q</span>(<span class="hljs-number">0.5</span>), t3 = <span class="hljs-title function_">q</span>(<span class="hljs-number">0.75</span>);</code></pre></div>
                <p>A quiet year still shows contrast, because the scale is relative to that year. It is a graph of your rhythm, not a leaderboard.</p>
                <h2 id="codeforces-makes-you-work-for-it">Codeforces makes you work for it<a class="anchor" href="#codeforces-makes-you-work-for-it" aria-label="Link to this section">#</a></h2>
                <p>The last piece is unrelated to layout but was the thing that actually kept the card blank. Codeforces rate-limits to roughly one call every two seconds, and it signals a violation by returning <strong>HTTP 200</strong> with <code>{&quot;status&quot;:&quot;FAILED&quot;}</code> in the body.</p>
                <blockquote>
                <p>A 200 that means failure defeats every <code>fetch().then()</code> you have written. <code>r.ok</code> is true, <code>JSON.parse</code> succeeds, and you render a card from an error.</p>
                </blockquote>
                <p>Two things fix it. Check <code>status</code> rather than the HTTP code, and space the calls out instead of firing them together:</p>
                <div class="code-wrap"><span class="code-lang">js</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">cfUnwrap</span>(<span class="hljs-params">d</span>) {
                    <span class="hljs-keyword">if</span> (!d || d.<span class="hljs-property">status</span> !== <span class="hljs-string">&#x27;OK&#x27;</span> || !d.<span class="hljs-property">result</span>) {
                        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(d &amp;&amp; d.<span class="hljs-property">comment</span> ? d.<span class="hljs-property">comment</span> : <span class="hljs-string">&#x27;CF request failed&#x27;</span>);
                    }
                    <span class="hljs-keyword">return</span> d.<span class="hljs-property">result</span>;
                }</code></pre></div>
                <p>The card also has a serverless proxy behind it as a second route, which helps mostly because it comes from a different source IP and caches at the edge.</p>
                <p>And when both routes fail, the card does not disappear. It renders an empty grid with a note saying the API could not be reached. A block that silently vanishes reads as a broken layout. A block that says what went wrong reads as a site someone maintains.</p>]]></content:encoded>
            <category>frontend</category>
            <category>ux</category>
        </item>
        <item>
            <title>You cannot be dumb and lazy at the same time</title>
            <link>https://divyansh.is-a.dev/blog/you-cannot-be-dumb-and-lazy-at-the-same-time</link>
            <guid isPermaLink="true">https://divyansh.is-a.dev/blog/you-cannot-be-dumb-and-lazy-at-the-same-time</guid>
            <pubDate>Tue, 18 Aug 2026 09:00:00 GMT</pubDate>
            <description>If you are lazy, you have to be sharp enough to solve things in ten lines. If you are still building intuition, you have to outwork the problem. Trying to do both is how you end up staring at broken builds all weekend.</description>
            <content:encoded><![CDATA[                <p>There is a blunt rule of thumb that holds up everywhere in engineering: you can be unexceptional, or you can be lazy, but you cannot afford to be both.</p>
                <p>If you are terrifyingly sharp, you can get away with working four hours a day. You glance at a stack trace, spot the unindexed database scan or the off-by-one race condition in three minutes, write a fifteen-line fix, and move on. Your leverage is high enough that your output looks effortless.</p>
                <p>If you aren&#39;t that person—and almost nobody is when they start—you still have a completely viable path: you just outwork the gap. You pull down the library&#39;s source code instead of guessing what the docs meant. You fire up a debugger and step through twenty stack frames. You write a script that generates ten thousand edge-case payloads until the bug reproduces locally. It takes longer, but you arrive at the exact same truth.</p>
                <p>The disaster is when people try to borrow the work habits of the first person while possessing the context of the second.</p>
                <h2 id="the-guessing-game">The guessing game<a class="anchor" href="#the-guessing-game" aria-label="Link to this section">#</a></h2>
                <p>You see this most clearly in how people debug.</p>
                <p>When someone lacks both deep understanding and the patience to grind, debugging turns into superstition. They change a variable name, wrap a synchronous call in an unnecessary retry loop, sprinkle random null-checks, and rerun the test suite. If it passes once, they push to main and pray nobody touches it again.</p>
                <p>Nothing was diagnosed. No invariants were verified. The underlying race condition or memory leak is still there, quietly waiting for traffic to spike.</p>
                <p>It happens because genuine diagnosis is tedious:</p>
                <ul>
                <li>Reading a 40-page protocol spec when you only care about three fields.</li>
                <li>Profiling memory allocations under load instead of blindly bumping the container&#39;s RAM limit.</li>
                <li>Tracing raw TCP packets when a connection drops silently behind a reverse proxy.</li>
                </ul>
                <p>None of that is glamorous work. It is mostly reading, testing hypotheses, and being wrong ten times in a row before finding the one line that actually matters.</p>
                <h2 id="the-leverage-tax">The leverage tax<a class="anchor" href="#the-leverage-tax" aria-label="Link to this section">#</a></h2>
                <p>Productive laziness is real, but it is an earned state.</p>
                <p>The engineers who build clean, minimal systems aren&#39;t &quot;lazy&quot; in the sense that they skip the hard parts. They are lazy in the sense that they refuse to solve the same problem twice. They spend three days designing a clean abstraction or writing an idempotent pipeline specifically so they never have to wake up for an on-call alert on Saturday.</p>
                <p>That kind of simplicity requires enormous upfront cognitive work. Making something simple is much harder than making something complicated.</p>
                <p>If you don&#39;t have the mental models yet to build that leverage, you can&#39;t skip straight to the relaxed afternoon. Your only real option is to put in the hours: read the manuals, write the test harnesses, inspect the logs, and build the intuition from scratch.</p>
                <p>One handicap is workable. Both at once is just negligence.</p>]]></content:encoded>
            <category>engineering</category>
            <category>craft</category>
        </item>
    </channel>
</rss>
