<?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: Mindinu Ariyawansha</title>
    <description>The latest articles on DEV Community by Mindinu Ariyawansha (@mindinu).</description>
    <link>https://dev.to/mindinu</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%2F3755924%2F2fb5d34f-cd8c-4042-afeb-0a8775a5ce2e.jpg</url>
      <title>DEV Community: Mindinu Ariyawansha</title>
      <link>https://dev.to/mindinu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mindinu"/>
    <language>en</language>
    <item>
      <title>Why Your Production API Needs Idempotency Keys (And How to Build an Engine in Node.js &amp; Redis)</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Sun, 20 Sep 2026 09:02:45 +0000</pubDate>
      <link>https://dev.to/mindinu/why-your-production-api-needs-idempotency-keys-and-how-to-build-an-engine-in-nodejs-redis-30n6</link>
      <guid>https://dev.to/mindinu/why-your-production-api-needs-idempotency-keys-and-how-to-build-an-engine-in-nodejs-redis-30n6</guid>
      <description>&lt;p&gt;In a perfect network, every HTTP request arrives exactly once. In the real world, mobile clients drop connection mid-flight, timeouts hit load balancers, and frontend retry logic triggers duplicate operations.&lt;/p&gt;

&lt;p&gt;If a user clicks "Pay Now" on a $100 checkout, their connection drops, and their app automatically retries the request 3 seconds later, what happens?&lt;/p&gt;

&lt;p&gt;Without an &lt;strong&gt;Idempotency Engine&lt;/strong&gt;, your API risks processing two charges for a single intent. &lt;/p&gt;

&lt;p&gt;While &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; methods are naturally idempotent by HTTP spec, &lt;code&gt;POST&lt;/code&gt; endpoints (creating charges, generating invoices, triggering AI workflows) are inherently non-idempotent.&lt;/p&gt;

&lt;p&gt;Here is how production systems guarantee &lt;strong&gt;exact-once execution semantics&lt;/strong&gt; using Idempotency Keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  How an Idempotency Engine Works
&lt;/h2&gt;

&lt;p&gt;An idempotency key is a unique, client-generated identifier (usually a v4 UUID) sent in the HTTP header:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Idempotency-Key: 7b9e1d84-2a3c-4e89-9102-1a4f52e39a01&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When the server receives a request with an idempotency key, it enters a state machine execution loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  +-----------------------------------+
                  |   Incoming Request + Header Key   |
                  +-----------------------------------+
                                    |
                                    v
                       [ Check Redis Cache for Key ]
                                    |
                     +--------------+--------------+
                     |                             |
             (Key Exists?)                   (Key Missing?)
                     |                             |
          +----------+----------+                  v
          |                     |       [ Set Key State: "PROCESSING" ]
   (State: COMPLETE)   (State: PROCESSING)         |
          |                     |                  v
          v                     v       [ Execute Business Logic ]
   [ Return Saved ]      [ Return 409 ]            |
   [ HTTP Response ]     [ Conflict ]              v
                                        [ Save Response + State: "COMPLETE" ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First Request:&lt;/strong&gt; The server checks Redis for the key. Missing. It stores &lt;code&gt;key: "PROCESSING"&lt;/code&gt; with a short TTL (e.g., 30 seconds) and executes the logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent Duplicate:&lt;/strong&gt; If a second request arrives with the same key while the first is still processing, the server immediately rejects it with &lt;code&gt;409 Conflict&lt;/code&gt; or &lt;code&gt;429 Too Many Requests&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completed Duplicate:&lt;/strong&gt; Once the first request succeeds, the server caches the HTTP status code and response body in Redis under that key. Subsequent retries return the &lt;strong&gt;cached response instantly&lt;/strong&gt; without re-executing any logic.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Production Middleware Implementation (Express + Redis)
&lt;/h2&gt;

&lt;p&gt;Here is a clean, dependency-light middleware implementation in Node.js using Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IDEMPOTENCY_TTL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 24 Hours in seconds&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextFunction&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Skip if client didn't supply a key (or enforce it for critical routes)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redisKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`idempotency:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Atomically set key if it doesn't exist (NX) with a lock timeout&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acquired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redisKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PROCESSING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;acquired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cachedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redisKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PROCESSING&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Concurrent request in progress. Please wait.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Return previously cached execution response&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Intercept res.json to capture response payload before sending to client&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Save execution result to Redis for future retries&lt;/span&gt;
      &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;redisKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; 
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nx"&gt;IDEMPOTENCY_TTL&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;originalJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Crucial Edge Cases to Consider
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Request Payload Validation
&lt;/h3&gt;

&lt;p&gt;What if a malicious actor sends the same &lt;code&gt;Idempotency-Key&lt;/code&gt; with a completely &lt;em&gt;different&lt;/em&gt; JSON payload?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solution:&lt;/strong&gt; Hash the request body alongside the key (&lt;code&gt;SHA256(Key + Payload)&lt;/code&gt;). If the key matches but the payload hash differs, return &lt;code&gt;400 Bad Request&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Handling Hard Failures (5xx Server Errors)
&lt;/h3&gt;

&lt;p&gt;If your database or downstream service crashes while processing a request, &lt;strong&gt;do not save a 500 error as a permanent idempotent response&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the Redis key if the request throws an unhandled exception so the client can safely retry after your system recovers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Distributed Lock Leaks
&lt;/h3&gt;

&lt;p&gt;Always set an expiration TTL (e.g., 30 seconds) on the initial &lt;code&gt;PROCESSING&lt;/code&gt; lock. If your API worker dies mid-execution, the key will naturally expire rather than permanently locking out the user from retrying.&lt;/p&gt;




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

&lt;p&gt;Handling retries cleanly is the difference between a brittle prototype and enterprise-ready API infrastructure. By pushing idempotency tracking to a fast Redis layer, you protect your database from duplicate writes and give your client applications a bulletproof retry strategy.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>node</category>
      <category>redis</category>
    </item>
    <item>
      <title>How Eventual Consistency Breaks System Logic (And How to Handle It in Distributed Systems)</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Sat, 19 Sep 2026 12:31:59 +0000</pubDate>
      <link>https://dev.to/mindinu/how-eventual-consistency-breaks-system-logic-and-how-to-handle-it-in-distributed-systems-15cg</link>
      <guid>https://dev.to/mindinu/how-eventual-consistency-breaks-system-logic-and-how-to-handle-it-in-distributed-systems-15cg</guid>
      <description>&lt;p&gt;When building distributed microservices, moving away from monolithic ACID transactions to eventual consistency is often touted as the ultimate cure for scalability bottlenecks. But eventual consistency introduces a silent killer: &lt;strong&gt;race conditions between data propagation and business rules.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a single, unified database, a standard transaction guarantees immediate consistency. Once a write commits, every subsequent read sees that data. &lt;/p&gt;

&lt;p&gt;In an eventually consistent, asynchronous architecture (e.g., using Kafka, DynamoDB, or PostgreSQL logical replication), &lt;strong&gt;there is a time window where different parts of your system hold conflicting truths.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is how eventual consistency breaks application logic—and the patterns senior engineers use to prevent data corruption.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Phantom Inventory Problem (When Consistency Delays Kill UX)
&lt;/h2&gt;

&lt;p&gt;Consider an e-commerce checkout pipeline split into two services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Order Service:&lt;/strong&gt; Creates a pending order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory Service:&lt;/strong&gt; Decrements stock and confirms availability.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------+     1. Write Order     +----------------------+
| Order Service | ---------------------&amp;gt; | Primary DB (Master)  |
+---------------+                        +----------------------+
        |                                           |
        | 2. Emit "OrderCreated" Event              | Async Replication
        v                                           v
+------------------+                     +----------------------+
| Event Message Bus|                     | Read Replica DB      |
+------------------+                     +----------------------+
        |                                           ^
        | 3. Process Async                          | 4. User refreshes
        v                                           |    page (Reads old state!)
+-------------------+                       +------------------+
| Inventory Service |                       | Web Client / App |
+-------------------+                       +------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Goes Wrong:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;A user buys the last remaining item in stock.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Order Service&lt;/code&gt; writes to the primary database and emits an &lt;code&gt;OrderCreated&lt;/code&gt; event to a message queue.&lt;/li&gt;
&lt;li&gt;The user is redirected to their "Order Confirmation" page.&lt;/li&gt;
&lt;li&gt;The web client queries a &lt;strong&gt;Read Replica&lt;/strong&gt; for order status.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Trap:&lt;/strong&gt; Because database replication has a 200ms lag, the Read Replica still shows $0$ orders and stock available. The UI shows "Order Failed" or allows a second user to purchase the same item.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This isn't a database crash—it's a &lt;strong&gt;consistency window failure&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3 Design Patterns to Fix Eventual Consistency Bugs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Read-Your-Own-Writes Consistency (Client-Side State Tracking)
&lt;/h3&gt;

&lt;p&gt;Instead of relying on the backend read replicas immediately after a write operation, the host application maintains a short-lived local state or passes a session token (like a monotonic sequence ID or vector clock).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How it works:&lt;/strong&gt; When the client performs a write, the API returns a version marker (&lt;code&gt;version_id: 1042&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;When fetching data, the client sends &lt;code&gt;If-Match-Version: 1042&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;If the read replica hasn't caught up to version 1042 yet, the API gateway routes the read request directly to the &lt;strong&gt;Primary/Leader Database&lt;/strong&gt; instead of the lagging replica.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The Saga Pattern (Orchestration vs. Choreography)
&lt;/h3&gt;

&lt;p&gt;Because you cannot execute a distributed transaction spanning multiple databases using traditional 2-Phase Commit (2PC) without crippling performance, you use a &lt;strong&gt;Saga&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A Saga executes a sequence of local transactions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transaction 1:&lt;/strong&gt; Reserve inventory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction 2:&lt;/strong&gt; Process payment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction 3:&lt;/strong&gt; Confirm order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;strong&gt;Transaction 2 (Payment)&lt;/strong&gt; fails, the Saga Orchestrator triggers explicit &lt;strong&gt;Compensating Transactions&lt;/strong&gt; in reverse order (e.g., executing &lt;code&gt;Unreserve Inventory&lt;/code&gt;) to return the system to a clean, balanced state.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Idempotent Event Handlers
&lt;/h3&gt;

&lt;p&gt;In asynchronous networks, messages get delayed, retried, and delivered out of order. An eventual consistency pipeline MUST assume every event will be delivered &lt;strong&gt;at least once&lt;/strong&gt; and potentially &lt;strong&gt;out of sequence&lt;/strong&gt;.&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="c1"&gt;-- Bad: Non-idempotent update (Running twice doubles the discount)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;user_balances&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Good: Idempotent state machine with explicit event tracking&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;processed_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processed_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'evt_9921'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;user_balances&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;last_event_id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'evt_9921'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary Checklist for Distributed System State
&lt;/h2&gt;

&lt;p&gt;When designing async or multi-database features, ask these three questions before hitting production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What happens if this message arrives 5 seconds late?&lt;/strong&gt; (Will it overwrite newer data?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What happens if this event executes twice?&lt;/strong&gt; (Is the consumer strictly idempotent?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the UI reflect speculative execution or read-replica lag?&lt;/strong&gt; (Are you shielding the user from replica latency windows?)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Eventual consistency is necessary for high scale, but treating it like immediate consistency is the root cause of subtle, hard-to-reproduce production bugs!&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>microservices</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Why Your Production Microservices Should Use Circuit Breakers (And How to Implement One in 50 Lines)</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Sat, 19 Sep 2026 05:59:46 +0000</pubDate>
      <link>https://dev.to/mindinu/why-your-production-microservices-should-use-circuit-breakers-and-how-to-implement-one-in-50-lines-42n2</link>
      <guid>https://dev.to/mindinu/why-your-production-microservices-should-use-circuit-breakers-and-how-to-implement-one-in-50-lines-42n2</guid>
      <description>&lt;p&gt;If you are building distributed systems, network calls will fail. It’s not a matter of &lt;em&gt;if&lt;/em&gt;, but &lt;em&gt;when&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Whether it's an API rate limit, a transient database spike, or a downstream service deploying buggy code, external network calls are non-deterministic. The real problem isn't the single failed request—it's &lt;strong&gt;cascading failures&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;When Service A synchronously calls Service B, and Service B begins hanging due to high latency, Service A runs out of available threads or memory waiting for responses. Suddenly, Service A crashes, taking down Service C, and dragging your entire infrastructure into a full outage.&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;Circuit Breaker Pattern&lt;/strong&gt; becomes vital.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is a Circuit Breaker?
&lt;/h2&gt;

&lt;p&gt;Inspired by the electrical circuit breaker in your home that cuts power during a current overload, a software circuit breaker wraps an expensive or network-bound call and monitors for failures.&lt;/p&gt;

&lt;p&gt;A circuit breaker operates in three distinct states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         +--------------------------------------------+
         |                                            |
         v                                            |
   +-----------+   Failures &amp;gt; Threshold   +----------+ |
---|   CLOSED  |-------------------------&amp;gt;|   OPEN   | | Success
   +-----------+                          +----------+ |
         ^                                     |       |
         |         Timeout Expired             |       |
         |      +-------------------+          |       |
         +------|     HALF-OPEN     |&amp;lt;---------+       |
                +-------------------+                  |
                          |                            |
                          +----------------------------+
                                   Failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CLOSED (Normal Operation):&lt;/strong&gt; All requests pass through to the downstream service. The breaker records successes and failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OPEN (Failing Fast):&lt;/strong&gt; If the failure rate crosses a specified threshold within a time window, the circuit trips &lt;strong&gt;OPEN&lt;/strong&gt;. All incoming requests immediately return a fallback error or cached data &lt;strong&gt;without actually making a network call&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HALF-OPEN (Testing the Waters):&lt;/strong&gt; After a cooldown period, the breaker allows a limited number of test requests through. If they succeed, the circuit resets to &lt;strong&gt;CLOSED&lt;/strong&gt;. If they fail, it trips back to &lt;strong&gt;OPEN&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Implementing a Minimal Circuit Breaker
&lt;/h2&gt;

&lt;p&gt;Here is a lightweight, dependency-free implementation of a Circuit Breaker in Go (the exact same pattern applies in TypeScript, Python, or Java).&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
go
package main

import (
    "errors"
    "sync"
    "time"
)

type State int

const (
    StateClosed State = iota
    StateOpen
    StateHalfOpen
)

type CircuitBreaker struct {
    mu               sync.Mutex
    state            State
    failureThreshold int
    cooldown         time.Duration
    failures         int
    lastFailureTime  time.Time
}

func NewCircuitBreaker(threshold int, cooldown time.Duration) *CircuitBreaker {
    return &amp;amp;CircuitBreaker{
        state:            StateClosed,
        failureThreshold: threshold,
        cooldown:         cooldown,
    }
}

func (cb *CircuitBreaker) Execute(req func() error) error {
    cb.mu.Lock()

    // Check if OPEN circuit cooldown period has expired
    if cb.state == StateOpen {
        if time.Since(cb.lastFailureTime) &amp;gt; cb.cooldown {
            cb.state = StateHalfOpen
        } else {
            cb.mu.Unlock()
            return errors.New("circuit breaker is OPEN: fast-failing request")
        }
    }

    cb.mu.Unlock()

    // Execute actual network request
    err := req()

    cb.mu.Lock()
    defer cb.mu.Unlock()

    if err != nil {
        cb.failures++
        cb.lastFailureTime = time.Now()

        if cb.failures &amp;gt;= cb.failureThreshold {
            cb.state = StateOpen
        }
        return err
    }

    // Reset state on successful request
    if cb.state == StateHalfOpen || cb.state == StateClosed {
        cb.failures = 0
        cb.state = StateClosed
    }

    return nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>WebAssembly Beyond the Browser: Building a Sandboxed Plugin System in Node.js &amp; Go</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Fri, 18 Sep 2026 16:35:15 +0000</pubDate>
      <link>https://dev.to/mindinu/webassembly-beyond-the-browser-building-a-sandboxed-plugin-system-in-nodejs-go-eo0</link>
      <guid>https://dev.to/mindinu/webassembly-beyond-the-browser-building-a-sandboxed-plugin-system-in-nodejs-go-eo0</guid>
      <description>&lt;p&gt;For years, WebAssembly (WASM) was pitched primarily as a way to bring high-performance C++ or Rust graphics to the web browser. But in modern backend engineering, WASM’s most compelling application has shifted entirely: &lt;strong&gt;safe, near-native sandboxed plugin execution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are building a system where third-party developers (or internal teams) need to execute custom logic—like webhook transformers, custom authorization rules, or pipeline data formatters—running untrusted code safely is a nightmare. &lt;/p&gt;

&lt;p&gt;Traditional approaches come with heavy trade-offs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;eval()&lt;/code&gt; or Node &lt;code&gt;vm&lt;/code&gt; module:&lt;/strong&gt; Insecure. Process isolation is weak, memory leak-prone, and susceptible to prototype pollution or host system access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Containers / Micro-VMs (Firecracker):&lt;/strong&gt; Maximum isolation, but massive overhead. Spin-up latency takes tens to hundreds of milliseconds, and memory consumption scales poorly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded JS Interpreters (V8 Isolate / QuickJS):&lt;/strong&gt; Better, but locks your plugin authors into a single language ecosystem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Enter &lt;strong&gt;WebAssembly on the Server&lt;/strong&gt;. By embedding a lightweight WASM runtime (like Wasmtime or Extism) into your host application, you get isolated execution with near-zero cold starts (&amp;lt; 1ms) and predictable memory boundaries.&lt;/p&gt;

&lt;p&gt;Here is a practical look at how server-side WASM sandboxing works and how to design a safe plugin host.&lt;/p&gt;




&lt;h2&gt;
  
  
  The WASM Sandbox Architecture
&lt;/h2&gt;

&lt;p&gt;When executing a WASM plugin inside a host process, the WASM runtime creates an isolated instance with explicit memory bounds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------------------------------------+
| HOST APPLICATION (Node.js / Go / Rust)                      |
|                                                             |
|   +-----------------------------------------------------+   |
|   | WASM RUNTIME INSTANCE (e.g., Wasmtime / Extism)     |   |
|   |                                                     |   |
|   |   - Linear Memory: Fixed Max Allocation (e.g. 16MB) |   |
|   |   - System Calls: DENIED by default                 |   |
|   |   - Disk / Network: Isolated / Whitelisted Host ABI |   |
|   |                                                     |   |
|   |   [ Plugin Code (Compiled from Rust/Go/Zig) ]       |   |
|   +-----------------------------------------------------+   |
|                                                             |
+-------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, a WASM module is &lt;strong&gt;deny-by-default&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It cannot read or write to the host file system.&lt;/li&gt;
&lt;li&gt;It cannot open network sockets.&lt;/li&gt;
&lt;li&gt;It cannot access the host machine’s memory outside its allocated linear memory block.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Designing the Host-Plugin Contract (ABI)
&lt;/h2&gt;

&lt;p&gt;Because WASM natively only understands basic numeric types (&lt;code&gt;i32&lt;/code&gt;, &lt;code&gt;i64&lt;/code&gt;, &lt;code&gt;f32&lt;/code&gt;, &lt;code&gt;f64&lt;/code&gt;), passing complex data (like JSON strings or binary payloads) across the host-guest boundary requires an Application Binary Interface (ABI) convention.&lt;/p&gt;

&lt;p&gt;Here is how data flows across the boundary:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Host Allocates Memory:&lt;/strong&gt; The host writes the input payload (e.g., JSON bytes) directly into a segment of the WASM instance's linear memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host Passes Pointers:&lt;/strong&gt; The host calls the WASM exported function, passing the &lt;strong&gt;memory pointer&lt;/strong&gt; and &lt;strong&gt;length&lt;/strong&gt; as integer arguments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guest Processes Data:&lt;/strong&gt; The plugin reads the memory segment, executes its transformation logic, and writes the output payload to another memory segment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guest Returns Pointer:&lt;/strong&gt; The WASM function returns an integer pointer pointing to the result location in memory for the host to consume.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Practical Example: Embedding a WASM Plugin Host in Go
&lt;/h2&gt;

&lt;p&gt;Using open-source frameworks like &lt;strong&gt;Extism&lt;/strong&gt; or &lt;strong&gt;Wazero&lt;/strong&gt;, setting up a host runtime takes less than 20 lines of code:&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;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"[github.com/extism/go-sdk](https://github.com/extism/go-sdk)"&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;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&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;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// 1. Configure memory bounds and plugin source&lt;/span&gt;
    &lt;span class="n"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;extism&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Wasm&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;extism&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wasm&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;extism&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WasmFile&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"./plugins/transform_user_payload.wasm"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;extism&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManifestMemory&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;MaxPages&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// Cap total memory at 2MB (64KB per page)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// 2. Instantiate the sandboxed plugin&lt;/span&gt;
    &lt;span class="n"&gt;plugin&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;extism&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPlugin&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;manifest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extism&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginConfig&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="nb"&gt;panic&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="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// 3. Call exported function with raw JSON payload&lt;/span&gt;
    &lt;span class="n"&gt;inputJSON&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"user_id": 1042, "raw_role": "admin_v2"}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;exitCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&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;plugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"transform_data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputJSON&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="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Plugin execution failed with exit code: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exitCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Plugin Output: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Trade-Offs You Must Consider
&lt;/h2&gt;

&lt;p&gt;While WASM-based plugin systems offer incredible performance and security benefits, they aren't a silver bullet:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Garbage Collection Boundary
&lt;/h3&gt;

&lt;p&gt;Languages with runtime GC (like Go or AssemblyScript) embed their GC engine into the compiled &lt;code&gt;.wasm&lt;/code&gt; binary, increasing binary size. Languages like &lt;strong&gt;Rust&lt;/strong&gt;, &lt;strong&gt;Zig&lt;/strong&gt;, or &lt;strong&gt;C&lt;/strong&gt; compile down to minimal WASM binaries (often under 100KB) and are far better suited for writing lightweight plugins.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fuel &amp;amp; Execution Limits
&lt;/h3&gt;

&lt;p&gt;A malicious or buggy plugin could contain an infinite loop: &lt;code&gt;while(true) {}&lt;/code&gt;. To prevent CPU starvation, WASM runtimes use &lt;strong&gt;Fuel Consumption&lt;/strong&gt; algorithms. The host assigns a fixed number of "fuel units" to a invocation. Every WASM instruction consumes fuel—when fuel runs out, the runtime instantly terminates the instance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sandboxing:&lt;/strong&gt; WASM provides hardware-level memory boundaries without requiring full containerization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold Start Speed:&lt;/strong&gt; Instantiating a compiled WASM module takes microseconds compared to milliseconds/seconds for containers or V8 isolates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language Flexibility:&lt;/strong&gt; Plugin authors can write in Rust, C, Go, Zig, or TypeScript (via AssemblyScript) as long as it targets &lt;code&gt;wasm32-unknown-unknown&lt;/code&gt; or &lt;code&gt;wasm32-wasi&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're architecting developer tools, workflow automation engines, or multi-tenant API gateways, embedding a WASM engine is one of the cleanest patterns available today for safe, high-throughput extensibility.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>security</category>
      <category>webassembly</category>
    </item>
    <item>
      <title>The Anatomy of a Slow Database Query: How to Diagnose, Deconstruct, and Fix It</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Mon, 14 Sep 2026 10:53:56 +0000</pubDate>
      <link>https://dev.to/mindinu/the-anatomy-of-a-slow-database-query-how-to-diagnose-deconstruct-and-fix-it-3ndj</link>
      <guid>https://dev.to/mindinu/the-anatomy-of-a-slow-database-query-how-to-diagnose-deconstruct-and-fix-it-3ndj</guid>
      <description>&lt;p&gt;Every backend developer has experienced that sinking feeling: a user reports that a dashboard is loading slowly, you check the application logs, and you find a database query taking upwards of 3,000 milliseconds. &lt;/p&gt;

&lt;p&gt;In a local environment with three rows of test data, everything ran instantaneously. But in production, with millions of rows, that single unoptimized query brings the entire application to its knees.&lt;/p&gt;

&lt;p&gt;In this deep dive, we are going to dissect the anatomy of a slow database query. We will look at how relational databases process queries, how to read an execution plan, spot missing indexes, and optimize JOIN operations to get your response times back into the single-digit milliseconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Lifecycle of a Query
&lt;/h2&gt;

&lt;p&gt;When your application sends a SQL query to a database, it doesn't just scan the hard drive blindly. The database engine goes through a rigorous pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parser &amp;amp; Translator:&lt;/strong&gt; Validates syntax and checks if the tables and columns exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewriter:&lt;/strong&gt; Optimizes the query structure logically (e.g., rewriting subqueries into joins).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query Optimizer (The Brain):&lt;/strong&gt; This is where the magic (or tragedy) happens. The optimizer analyzes statistical data about your tables and indexes to generate the &lt;strong&gt;Execution Plan&lt;/strong&gt;—the cheapest, fastest path to retrieve the requested data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution Engine:&lt;/strong&gt; Executes the plan, fetching data from disk or buffer pool memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a query is slow, 99% of the time it’s because &lt;strong&gt;the Query Optimizer chose a suboptimal execution plan&lt;/strong&gt;, usually forced into doing so by missing indexes, outdated statistics, or poor query design.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Reading the Execution Plan (&lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Never guess why a query is slow. Always ask the database. &lt;/p&gt;

&lt;p&gt;In PostgreSQL, MySQL, and SQLite, prefixing your query with &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; (or &lt;code&gt;EXPLAIN&lt;/code&gt; depending on the flavor) tells the database to run the query and output the execution steps it took along with actual timing.&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;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt; 
&lt;span class="k"&gt;SELECT&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;id&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;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; 
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;ON&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you look at the output, you want to hunt down two major red flags:&lt;/p&gt;

&lt;h3&gt;
  
  
  Red Flag A: The Sequential Scan (&lt;code&gt;Seq Scan&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;If you see a &lt;code&gt;Seq Scan&lt;/code&gt; on a massive table, the database is reading &lt;strong&gt;every single row&lt;/strong&gt; from disk from start to finish to check if it matches your &lt;code&gt;WHERE&lt;/code&gt; clause. If your &lt;code&gt;users&lt;/code&gt; table has 10 million rows, a sequential scan means reading 10 million rows, regardless of whether you only wanted 5 of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Red Flag B: High Cost and Actual Time Discrepancies
&lt;/h3&gt;

&lt;p&gt;Execution plans show estimated costs and actual time. If the optimizer estimated 5 rows and got 500,000 rows, your table statistics are stale, leading the optimizer to choose a disastrously bad execution strategy (like a Nested Loop join instead of a Hash join).&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Culprit: Missing or Inefficient Indexes
&lt;/h2&gt;

&lt;p&gt;Indexes are the primary weapon against slow queries. Think of an index like the index at the back of a textbook: instead of reading every page to find a keyword, you look up the word alphabetically and jump straight to the correct page.&lt;/p&gt;

&lt;h3&gt;
  
  
  The B-Tree Index Structure
&lt;/h3&gt;

&lt;p&gt;Most databases use &lt;strong&gt;B-Trees&lt;/strong&gt; by default. A B-Tree keeps data sorted and allows logarithmic time searches (O(log n)) rather than linear searches (O(n)).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Trap: Leftmost Prefix Rule
&lt;/h3&gt;

&lt;p&gt;Composite indexes (indexes on multiple columns) require care. If you create an index on &lt;code&gt;(status, created_at, user_id)&lt;/code&gt;, the database can use it if you query by &lt;code&gt;status&lt;/code&gt;, or &lt;code&gt;status AND created_at&lt;/code&gt;. However, if you query &lt;em&gt;only&lt;/em&gt; by &lt;code&gt;created_at&lt;/code&gt;, &lt;strong&gt;the index is completely ignored&lt;/strong&gt; because the B-Tree requires the leftmost column to navigate.&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="c1"&gt;-- GOOD: Uses the composite index on (status, created_at)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- BAD: Bypasses the index because 'status' is skipped&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. The Trap of Hidden Operations (Functions on Columns)
&lt;/h2&gt;

&lt;p&gt;Look at this seemingly harmless query:&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;YEAR&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why is this slow? Because you wrapped the column &lt;code&gt;created_at&lt;/code&gt; in a function (&lt;code&gt;EXTRACT&lt;/code&gt;). The database can no longer use a standard B-Tree index built on &lt;code&gt;created_at&lt;/code&gt; because it doesn't store the extracted year value—it stores the raw timestamp. To evaluate this, the database must evaluate the function on &lt;em&gt;every single row&lt;/em&gt; (causing a Sequential Scan).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: SARGable Queries
&lt;/h3&gt;

&lt;p&gt;Make your queries &lt;strong&gt;SARGable&lt;/strong&gt; (Search Argument Able) by keeping columns bare:&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="c1"&gt;-- GOOD: Allows the database to use an index on created_at&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2025-01-01 00:00:00'&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01 00:00:00'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Anatomy of a Bad JOIN
&lt;/h2&gt;

&lt;p&gt;Joins are notorious for performance bottlenecks when scaling. Consider this query connecting &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;items&lt;/code&gt;, and &lt;code&gt;customers&lt;/code&gt;:&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="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&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;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_name&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="n"&gt;oi&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;oi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Canada'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How Databases Handle Joins
&lt;/h3&gt;

&lt;p&gt;Databases typically choose between three join algorithms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nested Loop:&lt;/strong&gt; Good for small datasets or when the inner table is indexed on the join key. Terrible when both tables are massive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hash Join:&lt;/strong&gt; The database builds an in-memory hash table of the smaller relation and probes it with the larger relation. Highly efficient for large unindexed sets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge Join:&lt;/strong&gt; Sorts both relations on the join key and merges them. Requires sorted inputs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your query is slow here, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Foreign Key Indexes:&lt;/strong&gt; Are &lt;code&gt;customer_id&lt;/code&gt;, &lt;code&gt;order_id&lt;/code&gt;, and &lt;code&gt;item_id&lt;/code&gt; indexed on their respective child tables? If not, the database may resort to nested loops with full table scans for &lt;em&gt;every single row&lt;/em&gt; matched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter Placement:&lt;/strong&gt; Are you filtering (&lt;code&gt;WHERE&lt;/code&gt;) after the join, or can you filter the dataset &lt;em&gt;before&lt;/em&gt; performing the join to minimize rows flowing through memory?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary Checklist for Optimizing Queries
&lt;/h2&gt;

&lt;p&gt;When you encounter a slow query in production, run through this quick mental checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt;&lt;/strong&gt; to see what the database is actually doing (look for &lt;code&gt;Seq Scan&lt;/code&gt; and high execution times).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for missing indexes&lt;/strong&gt; on columns used in &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;JOIN ON&lt;/code&gt;, and &lt;code&gt;ORDER BY&lt;/code&gt; clauses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ensure queries are SARGable&lt;/strong&gt;—avoid wrapping columns in functions or operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect table statistics&lt;/strong&gt; (&lt;code&gt;ANALYZE&lt;/code&gt; in Postgres/MySQL) to ensure the query optimizer isn't working with outdated assumptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit payload size&lt;/strong&gt;—never use &lt;code&gt;SELECT *&lt;/code&gt; if you only need two columns; reducing data width reduces disk I/O and network overhead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Database performance optimization is an iterative engineering discipline. Master your execution plans, understand your indexes, and treat your database engine as a cooperative partner rather than a black box!&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>performance</category>
      <category>sql</category>
    </item>
    <item>
      <title>⚡ Stop Defaulting to WebSockets: Why Server-Sent Events (SSE) are Usually Better</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Fri, 04 Sep 2026 04:56:51 +0000</pubDate>
      <link>https://dev.to/mindinu/stop-defaulting-to-websockets-why-server-sent-events-sse-are-usually-better-3k2g</link>
      <guid>https://dev.to/mindinu/stop-defaulting-to-websockets-why-server-sent-events-sse-are-usually-better-3k2g</guid>
      <description>&lt;p&gt;There is a moment in every web developer's career when a client asks: &lt;em&gt;"Can we make this update in real time?"&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Your mind immediately jumps to WebSockets. It is the industry buzzword. It sounds fast. You spin up &lt;code&gt;socket.io&lt;/code&gt; or &lt;code&gt;Reverb&lt;/code&gt;, spend two days fighting with your load balancer, and finally get it working.&lt;/p&gt;

&lt;p&gt;But here is the harsh truth: for about 90% of modern web applications—including AI chat streaming, live dashboards, and notification feeds—&lt;strong&gt;WebSockets are massive overkill&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Instead, you should probably be using &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt;. Here is why SSE is often the cleaner, cheaper, and more pragmatic choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Difference
&lt;/h2&gt;

&lt;p&gt;Both WebSockets and SSE exist to push data from the server to the client without the client needing to constantly poll the server. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;WebSockets&lt;/strong&gt; create a full-duplex, persistent TCP connection. Both the client and the server can shout at each other simultaneously.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SSE&lt;/strong&gt; is a unidirectional, HTTP-based stream. The server keeps a standard HTTP connection open and pushes text-based events down to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why WebSockets Are a Headache in Production
&lt;/h2&gt;

&lt;p&gt;WebSockets are amazing for multiplayer games or collaborative tools like Google Docs where clients are constantly sending high-frequency data &lt;em&gt;back&lt;/em&gt; to the server. But that power comes with a heavy infrastructure tax.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Stateful Scaling:&lt;/strong&gt; WebSockets are stateful. If you scale horizontally, your load balancer needs connection-aware routing (sticky sessions) to ensure a client's subsequent messages go to the specific server holding their connection.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Proxy Nightmares:&lt;/strong&gt; Aggressive corporate proxies and firewalls frequently drop WebSocket protocol upgrades, leaving connections in failure modes that are notoriously hard to debug. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Memory Hogs:&lt;/strong&gt; Maintaining bidirectional frame buffers and tracking protocol state means every single WebSocket connection consumes significantly more server memory than an equivalent HTTP connection.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Native Reconnect:&lt;/strong&gt; If a WebSocket connection drops (and it will), the browser does not care. You have to write all the custom logic to detect the drop, backoff, retry, and resynchronize state.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why SSE is the Underdog You Need
&lt;/h2&gt;

&lt;p&gt;SSE leans on the mature, battle-tested HTTP ecosystem. It doesn't require a protocol upgrade, it doesn't need a custom server, and it works flawlessly with standard load balancers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Native Auto-Reconnect:&lt;/strong&gt; The &lt;code&gt;EventSource&lt;/code&gt; API in the browser is brilliant. If the connection drops, the browser automatically attempts to reconnect on its own. It even sends a &lt;code&gt;Last-Event-ID&lt;/code&gt; header so your server knows exactly where to resume the stream.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Standard HTTP Routing:&lt;/strong&gt; Because SSE is just a long-lived HTTP request, it scales like any other HTTP endpoint. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Perfect for AI and Dashboards:&lt;/strong&gt; If you are streaming an LLM response or pushing live price feeds to a dashboard, the client isn't sending data &lt;em&gt;back&lt;/em&gt; through that channel (they just make a standard POST request to trigger the event). SSE perfectly models this server-push architecture.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Talk is Cheap. Look at the Code.
&lt;/h2&gt;

&lt;p&gt;Here is how simple it is to implement SSE. No massive libraries, no custom protocols. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend (Node/Express):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Set the headers to keep the connection open&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keep-alive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Push data whenever you want&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intervalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Clean up on disconnect&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intervalId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Frontend (Vanilla JS):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The browser handles connection, streaming, and auto-reconnecting!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New update:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Decision Framework
&lt;/h2&gt;

&lt;p&gt;WebSockets and SSE aren't competitors; they solve different shapes of problems. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose WebSockets if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You are building a chat app, multiplayer game, or real-time collaborative canvas.&lt;/li&gt;
&lt;li&gt;  The client needs to push data to the server at high frequencies (10+ times per second).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose SSE if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You are streaming AI responses, live notifications, news feeds, or financial tickers.&lt;/li&gt;
&lt;li&gt;  The communication is primarily one-way (Server → Client).&lt;/li&gt;
&lt;li&gt;  You want to avoid managing custom reconnections and complex load balancing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time someone asks for real-time updates, don't immediately reach for the heaviest tool in the box. Give SSE a try. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you struggled with WebSocket scaling in production? Let's talk about it in the comments! 👇&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>🚨 3 PostgreSQL Anti-Patterns That Are Silently Killing Your App's Performance</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Thu, 03 Sep 2026 17:40:23 +0000</pubDate>
      <link>https://dev.to/mindinu/3-postgresql-anti-patterns-that-are-silently-killing-your-apps-performance-1e5k</link>
      <guid>https://dev.to/mindinu/3-postgresql-anti-patterns-that-are-silently-killing-your-apps-performance-1e5k</guid>
      <description>&lt;p&gt;PostgreSQL is one of the most powerful relational databases on the planet. Out of the box, it can handle massive workloads. But as your application scales, the way you write queries and structure your schema matters more than the database engine itself.&lt;/p&gt;

&lt;p&gt;If your app is starting to feel sluggish, it might not be a lack of resources. You might be falling into one of these three common PostgreSQL anti-patterns. &lt;/p&gt;

&lt;p&gt;Here is how to spot them and how to fix them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The &lt;code&gt;SELECT *&lt;/code&gt; Trap (and why it ruins memory)
&lt;/h2&gt;

&lt;p&gt;When we are iterating quickly, it is incredibly tempting to just write &lt;code&gt;SELECT * FROM users&lt;/code&gt; and let the backend filter out the fields it doesn't need. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it’s an anti-pattern:&lt;/strong&gt;&lt;br&gt;
PostgreSQL has to read the data from the disk, load it into memory, and send it over the network to your application. If your &lt;code&gt;users&lt;/code&gt; table has 30 columns (including heavy &lt;code&gt;JSONB&lt;/code&gt; blobs or large &lt;code&gt;TEXT&lt;/code&gt; fields) and you only need the &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;, you are forcing the database to do 10x the I/O work for no reason. &lt;/p&gt;

&lt;p&gt;Furthermore, &lt;code&gt;SELECT *&lt;/code&gt; breaks index-only scans. If you have an index on &lt;code&gt;email&lt;/code&gt;, a query like &lt;code&gt;SELECT email FROM users WHERE email = 'x'&lt;/code&gt; can be resolved purely from the index without even touching the main table. &lt;code&gt;SELECT *&lt;/code&gt; forces Postgres to fetch the whole row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
Always explicitly define your columns, even if you are using an ORM.&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="c1"&gt;-- ❌ Bad&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Good&lt;/span&gt;
&lt;span class="k"&gt;SELECT&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_amount&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Over-Indexing (The "Just Add an Index" Fallacy)
&lt;/h2&gt;

&lt;p&gt;When a query is slow, the immediate reaction is usually: &lt;em&gt;"Let's just throw an index on that column!"&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it’s an anti-pattern:&lt;/strong&gt;&lt;br&gt;
Indexes are not free. Every time you &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt; a row, PostgreSQL has to update the main table &lt;em&gt;and&lt;/em&gt; every single index associated with that table. If you have a write-heavy table (like an event logger or analytics tracker) with 10 different indexes, your write latency will skyrocket.&lt;/p&gt;

&lt;p&gt;Additionally, Postgres query planners are smart. If an index isn't highly selective (e.g., a boolean column like &lt;code&gt;is_active&lt;/code&gt; where 95% of users are active), Postgres will likely ignore the index entirely and do a sequential scan anyway. You are paying the write penalty for an index that never gets used!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Periodically check for unused indexes. Postgres tracks this for you! You can run this query to find indexes that the database is ignoring:
&lt;/li&gt;
&lt;/ol&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="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indexrelname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx_scan&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_stat_user_indexes&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;idx_scan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Drop unused indexes and lean into &lt;strong&gt;composite indexes&lt;/strong&gt; for queries that frequently filter by the same multiple columns.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  3. The ORM N+1 Query Disaster
&lt;/h2&gt;

&lt;p&gt;If you are using Prisma, TypeORM, Hibernate, or Eloquent, you have probably written this exact bug without realizing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it’s an anti-pattern:&lt;/strong&gt;&lt;br&gt;
The N+1 problem occurs when your code fetches a list of records, and then loops through that list to fetch related data for &lt;em&gt;each&lt;/em&gt; record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ The N+1 Disaster in action&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 1 query&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This runs a NEW query for every single user! (N queries)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have 1,000 users, you just hit your database 1,001 times over the network for something that should have been a single round trip. This is the #1 cause of API latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
Use &lt;code&gt;JOIN&lt;/code&gt;s or rely on your ORM's eager-loading capabilities to fetch everything in a single optimized query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Good: Fetches users and their posts in a single round trip&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersWithPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Under the hood, this translates to either a &lt;code&gt;LEFT JOIN&lt;/code&gt; or exactly two queries (one for users, one for all posts matching those user IDs via an &lt;code&gt;IN&lt;/code&gt; clause).&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Scaling a database isn't just about throwing more RAM and CPU at your cloud provider. It is about respecting the network boundary, understanding your indexes, and keeping a close eye on the SQL your ORM is actually generating. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;What is the worst database performance bug you've ever had to debug? Let me know in the comments! 👇&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>sql</category>
    </item>
    <item>
      <title>🛑 Stop Wasting API Calls: How to Build a Dead-Simple Caching Layer for AI Apps</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Thu, 03 Sep 2026 07:02:59 +0000</pubDate>
      <link>https://dev.to/mindinu/stop-wasting-api-calls-how-to-build-a-dead-simple-caching-layer-for-ai-apps-ngi</link>
      <guid>https://dev.to/mindinu/stop-wasting-api-calls-how-to-build-a-dead-simple-caching-layer-for-ai-apps-ngi</guid>
      <description>&lt;p&gt;Building AI applications is incredibly fun right up until you check your API dashboard and realize you've been burning through credits by sending the exact same prompts during development and testing. &lt;/p&gt;

&lt;p&gt;Whether you are using Claude, Gemini, or OpenAI, rate limits and latency are real bottlenecks. If you are building wrappers, agents, or generation tools, &lt;strong&gt;you need a caching layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a highly effective, zero-dependency caching wrapper in TypeScript that you can drop into any project today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concept
&lt;/h2&gt;

&lt;p&gt;Instead of calling the LLM directly, we pass the prompt through a caching function. We hash the prompt (or use it as a key) and check if we already have a response stored. If yes, we return the cached string instantly. If no, we make the expensive network call, save the result, and return it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code (TypeScript)
&lt;/h2&gt;

&lt;p&gt;This uses a simple in-memory &lt;code&gt;Map&lt;/code&gt;, which is perfect for local development or single-instance edge functions.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
typescript
// Define an in-memory cache
const responseCache = new Map&amp;lt;string, string&amp;gt;();

async function fetchWithCache(prompt: string): Promise&amp;lt;string&amp;gt; {
  // 1. Check if we already have the exact prompt cached
  if (responseCache.has(prompt)) {
    console.log("⚡ Returning from Cache (0ms)");
    return responseCache.get(prompt)!;
  }

  // 2. If not, make the actual API call (using a generic fetch as an example)
  console.log("☁️ Fetching from API...");
  const response = await fetch("[https://api.your-llm-provider.com/v1/generate](https://api.your-llm-provider.com/v1/generate)", {
    method: "POST",
    headers: { "Authorization": `Bearer ${process.env.API_KEY}` },
    body: JSON.stringify({ prompt })
  });

  const data = await response.json();
  const textResult = data.choices[0].text;

  // 3. Store the result in the cache for next time
  responseCache.set(prompt, textResult);

  return textResult;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>typescript</category>
    </item>
    <item>
      <title>🔌 The 'USB-C of AI': Make Your Own MCP</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:41:48 +0000</pubDate>
      <link>https://dev.to/mindinu/the-usb-c-of-ai-make-your-own-mcp-568i</link>
      <guid>https://dev.to/mindinu/the-usb-c-of-ai-make-your-own-mcp-568i</guid>
      <description>&lt;p&gt;If you've been building AI-integrated apps recently, you know the pain: every single LLM, agent, and coding assistant needs a custom integration to read your database, check your GitHub repo, or pull Jira tickets. It's an endless cycle of writing custom API glue code.&lt;/p&gt;

&lt;p&gt;Enter the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Originally open-sourced by Anthropic, MCP is rapidly becoming the universal standard for how AI agents talk to data sources. It is quite literally the USB-C of the AI world. &lt;/p&gt;

&lt;p&gt;Here is why MCP is completely changing the modern developer stack—and how you can start using it to turbocharge your workflow today.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 What exactly is MCP?
&lt;/h2&gt;

&lt;p&gt;In simple terms, MCP is an open standard that standardizes how AI models access external context. Instead of building a custom plugin for Claude, a different one for Cursor, and another for your custom Python agent, you build &lt;strong&gt;one MCP Server&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Any MCP-compatible client (like Claude Desktop, Cursor, or your own app) can instantly connect to that server and understand what tools and data are available.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture is simple:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;MCP Hosts:&lt;/strong&gt; The application the user interacts with (e.g., Claude Desktop, Cursor IDE).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Clients:&lt;/strong&gt; The protocol layer inside the host application that manages connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Servers:&lt;/strong&gt; Lightweight programs you run locally or in the cloud that expose your data (e.g., a local SQLite database, a GitHub repo, a Slack workspace).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🛠️ The 3 Core Primitives of MCP
&lt;/h2&gt;

&lt;p&gt;When an AI connects to an MCP server, it gets access to three core primitives:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Resources (Read-only data)
&lt;/h3&gt;

&lt;p&gt;Resources are like file systems for AI. They allow the LLM to read data without modifying it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Example:&lt;/em&gt; Giving the AI read access to your local API documentation, system logs, or a Notion workspace. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Tools (Executable actions)
&lt;/h3&gt;

&lt;p&gt;Tools are functions the LLM can call to actually &lt;em&gt;do&lt;/em&gt; things. The server defines the required arguments, and the client prompts the user for permission before executing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Example:&lt;/em&gt; &lt;code&gt;execute_sql_query&lt;/code&gt;, &lt;code&gt;create_github_issue&lt;/code&gt;, or &lt;code&gt;restart_docker_container&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Prompts (Reusable templates)
&lt;/h3&gt;

&lt;p&gt;Pre-defined prompt templates that help users get the most out of the connected data. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Example:&lt;/em&gt; A "Code Review" prompt that automatically pulls the current git diff and asks the LLM to review it against your company's style guide.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Why this matters for your workflow &lt;em&gt;right now&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;You don't need to be building an AI startup to benefit from MCP. You can use it today to make your local dev environment incredibly powerful.&lt;/p&gt;

&lt;p&gt;Imagine this workflow:&lt;br&gt;
You are debugging an issue in Cursor. Instead of copying and pasting logs from your terminal, you spin up a local &lt;strong&gt;Postgres MCP Server&lt;/strong&gt; and a &lt;strong&gt;Datadog MCP Server&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;You simply ask your AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Look at the recent 500 errors in Datadog, query the users table in my local Postgres to see if their accounts are active, and find the bug in my codebase."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because the AI is connected to those MCP servers, it can autonomously fetch the logs, run the SQL query, and fix the code in one seamless interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  💻 Building your first MCP Server
&lt;/h2&gt;

&lt;p&gt;Building a server is surprisingly easy. You can write them in TypeScript or Python. Here is the conceptual skeleton of exposing a simple database tool in TypeScript:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 1. Initialize the server
const server = new McpServer({
  name: "Local-DB-Server",
  version: "1.0.0"
});

// 2. Add a Tool for the AI to use
server.tool(
  "query_users",
  "Run a search query against the local users database",
  { searchTerm: z.string() },
  async ({ searchTerm }) =&amp;gt; {
    // Run your actual DB logic here
    const results = await mockDbSearch(searchTerm);
    return {
      content: [{ type: "text", text: JSON.stringify(results) }]
    };
  }
);

// 3. Start listening over standard I/O
const transport = new StdioServerTransport();
await server.connect(transport);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>mcp</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Scaling Multi-Agent Systems: Why Your Docker Container Keeps Crashing</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:22:02 +0000</pubDate>
      <link>https://dev.to/mindinu/scaling-multi-agent-systems-why-your-docker-container-keeps-crashing-53o7</link>
      <guid>https://dev.to/mindinu/scaling-multi-agent-systems-why-your-docker-container-keeps-crashing-53o7</guid>
      <description>&lt;p&gt;If you are building autonomous AI agents, you eventually hit a scaling wall. While developing Saturn AI, I noticed that pushing past five or six simultaneous agents caused the entire X11 Docker container to choke, API requests to time out, and the system to crash. &lt;/p&gt;

&lt;p&gt;The issue was not the LLM API latency. It was OS process thrashing. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Root Cause: Process Overhead and Disk I/O
&lt;/h3&gt;

&lt;p&gt;When prototyping, it is common to rely on CLI wrappers to orchestrate agents. However, this introduces massive overhead. If six agents take a turn simultaneously, the backend spawns six heavy Node or Rust processes. If those agents invoke tools, they spawn additional child processes. &lt;/p&gt;

&lt;p&gt;The container rapidly runs out of memory, IPC pipe bandwidth, and CPU threads. Furthermore, if these wrappers maintain state by constantly reading and writing JSON session files, the disk I/O locks up completely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Architectural Fixes for Multi-Agent Stability
&lt;/h3&gt;

&lt;p&gt;To resolve this and scale efficiently, you have to treat agent turns like asynchronous network requests rather than OS shell processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Implement Concurrency Queuing&lt;/strong&gt;&lt;br&gt;
If you cannot rewrite your engine immediately, introduce an asynchronous job queue using a library like &lt;code&gt;p-queue&lt;/code&gt;. Cap the concurrency to two or three active processes at a time. When a trigger wakes up six agents, the queue allows the first few to execute while the others wait in memory. This eliminates CPU thrashing and keeps response times stable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Shift to In-Process SDK Calls&lt;/strong&gt;&lt;br&gt;
The long-term fix is removing the CLI middleman entirely. Build a custom ReAct loop using a native framework directly inside your main event loop. By executing agent turns as standard asynchronous network calls to the LLM provider, you can run dozens of concurrent agents in a single instance without spawning external processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use a Shared "Blackboard" Memory Model&lt;/strong&gt;&lt;br&gt;
Isolated JSON files for state management will bottleneck your disk. Transition to a shared state model stored directly in memory or a local Redis instance. All agents can instantly read and write their context, tasks, and tool outputs from this shared space. Additionally, boot a single persistent tool server on startup, and have all agents route through it via internal WebSockets, rather than each agent booting its own tool instances.&lt;/p&gt;

&lt;p&gt;By shifting away from process-heavy wrappers toward lightweight, async architecture, you can scale multi-agent environments reliably without burning through compute resources.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>docker</category>
    </item>
    <item>
      <title>I got into Batch0</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Tue, 01 Sep 2026 16:32:51 +0000</pubDate>
      <link>https://dev.to/mindinu/i-got-into-batch0-4idl</link>
      <guid>https://dev.to/mindinu/i-got-into-batch0-4idl</guid>
      <description>&lt;p&gt;Hey DEV community! 👋 &lt;/p&gt;

&lt;p&gt;My name is Mindinu. I am 14 years old, based in Sri Lanka, and I am the founder of &lt;strong&gt;Luveo Technologies&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Today, I am super excited to announce a massive milestone for my journey: &lt;strong&gt;Luveo Technologies just got accepted into Batch 0!&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;For those who might not know, being part of a "Batch 0" cohort means you are in the inaugural, foundational group of an accelerator or incubator program. It’s the launchpad cohort. It means the mentors and organizers saw enough raw potential in the vision to take a bet on it from the very beginning. &lt;/p&gt;

&lt;p&gt;And the product that got us here? &lt;strong&gt;Saturn AI.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🪐 What is Saturn AI?
&lt;/h2&gt;

&lt;p&gt;Saturn AI is an autonomous AI employee. &lt;/p&gt;

&lt;p&gt;You might have seen tools like OpenClaw or Viktor, but Saturn AI is being built to be better, faster, and much more deeply integrated into actual developer and business workflows. It’s not just a chatbot; it’s a self-improving AI environment that executes tasks natively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Under the hood, here is what I am building:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure:&lt;/strong&gt; The entire platform is hosted on a Contabo private VPS, allowing us to manage our own cloud container orchestration without insane cloud fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Seamless OAuth integration for secure and frictionless user onboarding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monetization:&lt;/strong&gt; A fully custom tiered subscription model currently transitioning to Dodo Payments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech Stack:&lt;/strong&gt; I'm utilizing tools like ClaudeCode, Gemini CLI, OpenCode, Vite, Bun, and Cloudflare tunnels to build out a robust, scalable backend and a lightning-fast frontend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠️ How I Will Participate in Batch 0
&lt;/h2&gt;

&lt;p&gt;I am treating Batch 0 as a high-speed sprint. Over the course of the program, my goals are strictly technical and growth-focused:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ship the MVP:&lt;/strong&gt; Move Saturn AI from a complex local/cloud environment into a stable, user-ready product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stress-Test the AI:&lt;/strong&gt; Push the autonomous agent capabilities to ensure it actually outperforms existing AI employee models in real-world coding and operational tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build in Public:&lt;/strong&gt; I am going to document the entire process right here on DEV. Expect deep dives into my Docker configurations, how I manage my VPS, and how I handle the payment gateway architecture.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🚀 The Road Ahead
&lt;/h2&gt;

&lt;p&gt;Building a SaaS product and running a startup at 14 isn't easy. I have to balance school, table tennis training, robotics competitions, and coding late into the night. But getting accepted into Batch 0 validates that the late nights are worth it.&lt;/p&gt;

&lt;p&gt;I can't wait to share this journey with you all. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the hardest technical challenge you faced when building your first SaaS? Drop some advice for a young founder in the comments! 👇&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you want to follow the journey of Saturn AI, keep an eye on &lt;a href="https://luveo.net" rel="noopener noreferrer"&gt;luveo.net&lt;/a&gt; and my personal portfolio &lt;a href="https://mindinu.luveo.net" rel="noopener noreferrer"&gt;mindinu.luveo.net&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>teendev</category>
      <category>coding</category>
    </item>
  </channel>
</rss>
