<?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: Tuấn Anh</title>
    <description>The latest articles on DEV Community by Tuấn Anh (@vesviet).</description>
    <link>https://dev.to/vesviet</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%2F1552279%2F3c8967c3-8340-4362-a0bf-f4663d852a2a.jpg</url>
      <title>DEV Community: Tuấn Anh</title>
      <link>https://dev.to/vesviet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vesviet"/>
    <language>en</language>
    <item>
      <title>Architecting 21-Service E-commerce with Golang &amp; DDD</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Thu, 09 Jul 2026 13:35:30 +0000</pubDate>
      <link>https://dev.to/vesviet/architecting-21-service-e-commerce-with-golang-ddd-3ocj</link>
      <guid>https://dev.to/vesviet/architecting-21-service-e-commerce-with-golang-ddd-3ocj</guid>
      <description>&lt;p&gt;&lt;strong&gt;Answer-first:&lt;/strong&gt; Migrating an e-commerce monolith to 21+ distributed microservices using Golang &amp;amp; DDD. Explore Kratos architecture, Saga patterns, and race conditions.&lt;/p&gt;

&lt;p&gt;Scaling an e-commerce platform past 10,000+ orders per day containing multiple SKUs across dynamic warehouses is where naive architecture breaks down. Hardware scaling ceases to be a magic bullet when distributed transactions, race conditions, and eventual consistency are involved.&lt;/p&gt;

&lt;p&gt;In this deep tech dive, we will tear apart the "Hello World" abstraction of Microservices. We will look at exactly how our &lt;strong&gt;21-service distributed ecosystem&lt;/strong&gt; interacts under the hood. I will share the exact Golang architectural patterns (Kratos), the Saga orchestration for distributed checkout, and how we handle race conditions under severe load.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Distributed Landscape
&lt;/h2&gt;

&lt;p&gt;Microservices without bounded contexts degenerate into a latency-heavy "Distributed Monolith". We bounded our ecosystem loosely around five core domains, prioritizing strict database-per-service isolation (If you are just starting out, this is exactly why you might want to start with a &lt;a href="https://dev.to/series/modular-monolith-architecture/"&gt;Modular Monolith Architecture&lt;/a&gt; before jumping to distributed extraction):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    API[API Gateway]
    API --&amp;gt; Checkout[Checkout Service]
    API --&amp;gt; Cart[Cart Service]

    subgraph "Dapr Event Mesh (Pub/Sub)"
        Checkout -- checkout.requested event --&amp;gt; Dapr[Redis / Dapr]
        Dapr --&amp;gt; Order[Order Service]
        Dapr --&amp;gt; Warehouse[Warehouse Service]
        Dapr --&amp;gt; Pricing[Pricing Service]
    end

    Warehouse -- inventory.reserved event --&amp;gt; Dapr
    Pricing -- pricing.validated event --&amp;gt; Dapr
    Order -- checkout.failed (Rollback) --&amp;gt; Dapr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diagram above encapsulates the most volatile flow: &lt;strong&gt;The Checkout Saga&lt;/strong&gt;. When a user checks out, we cannot just open a 4-table SQL transaction anymore. &lt;code&gt;Checkout&lt;/code&gt; must synchronize asynchronously with &lt;code&gt;Pricing&lt;/code&gt; (to validate totals), &lt;code&gt;Warehouse&lt;/code&gt; (to lock inventory), and &lt;code&gt;Order&lt;/code&gt; (to generate the final aggregate). &lt;/p&gt;

&lt;h2&gt;
  
  
  2. Enforcing Clean Architecture with Kratos
&lt;/h2&gt;

&lt;p&gt;To manage 21 separate codebases, consistency among the engineering team is mandatory. We utilized &lt;strong&gt;Kratos (v2)&lt;/strong&gt; to strictly enforce Clean Architecture in Golang. (You can explore the full stack we use in our &lt;a href="https://dev.to/radar/"&gt;Microservices Tech Radar&lt;/a&gt;). By physically separating boundaries, we prevent database logic from bleeding into HTTP or gRPC handlers.&lt;/p&gt;

&lt;p&gt;Here is what a standard Kratos blueprint looks like in our ecosystem:&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="c"&gt;// internal/biz/order.go (Business Logic Layer)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;OrderUsecase&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="n"&gt;OrderRepo&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uc&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;OrderUsecase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorInvalidAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order amount must be positive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// Biz layer knows NOTHING about PostgreSQL or GORM&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Save&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;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// internal/data/order.go (Data Persistence Layer)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;orderRepo&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Implement the Biz interface&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;orderRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;biz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Database transactions safely isolated here&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We tie these layers together dynamically using &lt;strong&gt;Google Wire&lt;/strong&gt; for compile-time Dependency Injection. This allows developers to write unit tests with mocked repositories effortlessly, entirely insulating the business core from transport protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Real Beast: Distributed Transactions (Saga Pattern)
&lt;/h2&gt;

&lt;p&gt;The most generic advice in microservices is "Use Pub/Sub". But how do you handle failure when Service A succeeds but Service B fails?&lt;/p&gt;

&lt;p&gt;In our ecosystem, we implemented an &lt;strong&gt;Event-Choreography Saga Pattern&lt;/strong&gt; using Dapr. Let's trace the complex &lt;code&gt;ConfirmCheckout&lt;/code&gt; flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Checkout Service&lt;/code&gt; receives the HTTP request, validates the cart, and publishes a &lt;code&gt;checkout.requested&lt;/code&gt; event to Dapr.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Warehouse Service&lt;/code&gt; and &lt;code&gt;Pricing Service&lt;/code&gt; listen to this event and act independently on the payloads. &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Handling Race Conditions in Warehouse
&lt;/h3&gt;

&lt;p&gt;Inventory race conditions happen when two sub-second requests try to buy the last iPhone. &lt;/p&gt;

&lt;p&gt;If &lt;code&gt;Warehouse Service&lt;/code&gt; just fires &lt;code&gt;SELECT stock FROM items WHERE id = ?&lt;/code&gt;, both concurrent threads will see &lt;code&gt;stock = 1&lt;/code&gt;, and both will decrement it, leading to &lt;code&gt;-1&lt;/code&gt; stock. &lt;/p&gt;

&lt;p&gt;Instead, our Warehouse service utilizes &lt;strong&gt;Optimistic Concurrency Control (OCC)&lt;/strong&gt; at the database layer:&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="c"&gt;// Optimistic Locking to prevent overselling&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`
    UPDATE inventory 
    SET reserved_stock = reserved_stock + ?, version = version + 1 
    WHERE sku_id = ? 
      AND (total_stock - reserved_stock) &amp;gt;= ? 
      AND version = ?`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skuID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentVersion&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;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowsAffected&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrStockInsufficientOrRaceCondition&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the lock fails due to an instant mismatch, &lt;code&gt;Warehouse&lt;/code&gt; publishes an &lt;code&gt;inventory.reservation.failed&lt;/code&gt; event.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Rollback (Compensation)
&lt;/h3&gt;

&lt;p&gt;Because state is distributed, if &lt;code&gt;Warehouse&lt;/code&gt; successfully locks the stock but &lt;code&gt;Pricing&lt;/code&gt; reports that the applied voucher is invalid, the entire Saga must abort. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Order Service&lt;/code&gt; often acts as the sink. If it sees &lt;code&gt;inventory.reservation.failed&lt;/code&gt; OR &lt;code&gt;pricing.validation.failed&lt;/code&gt;, it fires a massive compensation event: &lt;code&gt;checkout.failed&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Background workers (consumers) in &lt;code&gt;Warehouse Service&lt;/code&gt; catch this event and immediately trigger &lt;strong&gt;Compensation Logic&lt;/strong&gt;:&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="c"&gt;// Background Worker un-reserving stock&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WarehouseWorker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;HandleCheckoutFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="n"&gt;CheckoutFailed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Rollback the reserved stock using the original transaction ID&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inventoryRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReleaseReservedStock&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;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionID&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;
  
  
  4. Taming Eventual Consistency with Idempotency
&lt;/h2&gt;

&lt;p&gt;When you rely on network events, network retries will happen. Dapr guarantees "At-Least-Once" delivery, meaning &lt;code&gt;Warehouse Service&lt;/code&gt; might receive the same &lt;code&gt;checkout.requested&lt;/code&gt; event twice if a timeout occurs.&lt;/p&gt;

&lt;p&gt;To prevent reserving stock twice, every single Database in our ecosystem involved in transactions employs an &lt;code&gt;Idempotency Key&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&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="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before processing an incoming Dapr message, the service opens a database transaction and attempts to insert the &lt;code&gt;event_id&lt;/code&gt;. If it throws a constraint violation, the event was already processed, and the system safely acks and drops the duplicate message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Migrating an e-commerce Monolith to a 21-service ecosystem is not about setting up an API Gateway and calling it a day. The real engineering begins when you hit the edges: gracefully rolling back partial checkouts, preventing database locks under high concurrent loads, and forcing strict domain boundaries so codebases remain readable.&lt;/p&gt;

&lt;p&gt;By mapping contexts meticulously, enforcing strict separation via Kratos, and utilizing Idempotent Saga patterns over Dapr, we engineered a system that can absorb massive Black Friday traffic spikes without dropping a single order. The initial complexities of distributed state are painful, but the resulting scalability and developer isolation are profoundly worth the investment.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Continue Reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/go-microservices/"&gt;Go Microservices Architecture: Production Guide&lt;/a&gt; — the comprehensive architecture manual for the entire stack.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/deconstructing-ecommerce-service-details-domain/"&gt;Deconstructing the Ecosystem: Service Details by Domain&lt;/a&gt; — a full breakdown of all 21 services across 6 business domains.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/mastering-event-driven-architecture-dapr/"&gt;Mastering Event-Driven Architecture with Dapr Pub/Sub&lt;/a&gt; — deep dive into the Saga, DLQ, and idempotency patterns powering this ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/gitops-at-scale-kubernetes-argocd-microservices/"&gt;GitOps at Scale: Kubernetes &amp;amp; ArgoCD for Microservices&lt;/a&gt; — how we deploy all 21 services with zero manual &lt;code&gt;kubectl&lt;/code&gt; commands.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt;&lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: What is Golang?&lt;/strong&gt;&lt;br&gt;
A: &lt;br&gt;
&lt;strong&gt;Golang&lt;/strong&gt; is a critical architectural pattern or system discussed in this guide. Migrating an e-commerce monolith to 21+ distributed microservices using Golang &amp;amp; DDD. Explore Kratos architecture, Saga patterns, and race conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How does Golang compare to traditional alternatives?&lt;/strong&gt;&lt;br&gt;
A: &lt;br&gt;
Unlike legacy systems, &lt;strong&gt;Golang&lt;/strong&gt; introduces modern microservices or event-driven paradigms that scale efficiently. This article explores the exact tradeoffs and engineering constraints involved.&lt;/p&gt;

</description>
      <category>go</category>
      <category>microservices</category>
      <category>systemdesign</category>
      <category>domaindrivendesign</category>
    </item>
    <item>
      <title>Prompt Engineering vs Fine-Tuning SLM: Production Cost &amp; Latency Benchmarks</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Tue, 07 Jul 2026 12:13:42 +0000</pubDate>
      <link>https://dev.to/vesviet/prompt-engineering-vs-fine-tuning-slm-production-cost-latency-benchmarks-4501</link>
      <guid>https://dev.to/vesviet/prompt-engineering-vs-fine-tuning-slm-production-cost-latency-benchmarks-4501</guid>
      <description>&lt;h1&gt;
  
  
  Prompt Engineering vs Fine-Tuning SLM: Production Cost &amp;amp; Latency Benchmarks
&lt;/h1&gt;

&lt;p&gt;When moving LLMs (Large Language Models) or SLMs (Small Language Models) into production, the debate between Prompt Engineering and Fine-Tuning isn't just about model intelligence. It is fundamentally a battle of &lt;strong&gt;Cost&lt;/strong&gt; and &lt;strong&gt;Latency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Based on real-world data from our AI engineering team, here are the benchmarks and tipping points that dictate when you must abandon complex prompts and start fine-tuning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tipping Point: Context Window Bloat
&lt;/h2&gt;

&lt;p&gt;When does Prompt Engineering become too expensive? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Benchmark:&lt;/strong&gt; The tipping point hits around &lt;strong&gt;50,000 requests per day&lt;/strong&gt; for tasks requiring Structured Output (e.g., forcing the LLM to return strict JSON). &lt;/p&gt;

&lt;p&gt;To guarantee a strict JSON schema using a Cloud API (like GPT-4o), you often have to inject heavy Few-Shot examples and extensive system instructions. This causes &lt;strong&gt;Context Window Bloat&lt;/strong&gt;, easily inflating a single prompt to 8,000 - 10,000 input tokens.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Under 1,000 requests/day:&lt;/strong&gt; Paying for Cloud API input tokens is still cheaper and requires zero infrastructure maintenance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over 50,000 requests/day:&lt;/strong&gt; The variable cost of input tokens skyrockets. At this scale, transitioning to a fixed-cost model—renting GPUs to host a Fine-Tuned SLM (like Llama-3 8B using LoRA)—becomes significantly cheaper. The fine-tuned model understands the JSON schema inherently, completely eliminating the need for a 10k-token few-shot prompt.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Latency Benchmarks: 150ms vs 800ms TTFT
&lt;/h2&gt;

&lt;p&gt;Cost aside, User Experience (UX) is dictated by latency, specifically &lt;strong&gt;TTFT (Time To First Token)&lt;/strong&gt;. For real-time applications like Chatbots or inline coding assistants, latency is make-or-break.&lt;/p&gt;

&lt;p&gt;Here is our production TTFT benchmark:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud API (10k Token Prompt):&lt;/strong&gt; TTFT averages &lt;strong&gt;800ms to 1.2 seconds&lt;/strong&gt;. The cloud model spends a massive amount of time in the "Prefill Phase" reading your massive context window, plus network overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-Tuned SLM (7B, INT4 Quantized, Edge/Local Hosting):&lt;/strong&gt; TTFT drops to &lt;strong&gt;150ms - 250ms&lt;/strong&gt;. Because the behavior and formatting rules are baked into the model weights, the input prompt is incredibly short. The UX shifts from "painful waiting" to "instantaneous response."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Managing Tech Debt: PromptOps vs MLOps
&lt;/h2&gt;

&lt;p&gt;A surprising finding from our production deployment is that &lt;strong&gt;PromptOps generates more silent technical debt than MLOps.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We call it &lt;strong&gt;Semantic Drift&lt;/strong&gt;. When you tweak a massive system prompt to fix a bug for "Edge Case A," you almost inevitably break the structured output for "Edge Case B." Traditional CI/CD pipelines cannot catch semantic drift because it is not a syntax error.&lt;/p&gt;

&lt;p&gt;Fine-tuning (MLOps) requires a much heavier initial lift (Data Pipelines, Evaluation frameworks, GPU provisioning). However, once established, model checkpoints provide absolute, reproducible version control. &lt;em&gt;Warning: If your organization does not have a culture of clean data, Fine-Tuning will result in a "Garbage In, Garbage Out" disaster.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Golden Rule: RAG vs Fine-Tuning
&lt;/h2&gt;

&lt;p&gt;RAG (Retrieval-Augmented Generation) and Fine-Tuning are not mutually exclusive; they solve completely different problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Golden Rule:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;RAG&lt;/strong&gt; to inject &lt;strong&gt;Knowledge&lt;/strong&gt; (Facts, Documentation, Real-time data).&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Fine-Tuning&lt;/strong&gt; to teach &lt;strong&gt;Behavior and Format&lt;/strong&gt; (Tone, JSON schemas, Reasoning style).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Our Failure Case Study:&lt;/strong&gt; We once attempted to fine-tune a model by feeding it all our proprietary engineering documentation, hoping it would "memorize" the knowledge. It failed miserably. The model hallucinated wildly when asked cross-domain questions. &lt;br&gt;
Do not fine-tune a model just to cram data into it. Use RAG to fetch the data, and Fine-Tune the model so it knows exactly how to format the answer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Are you currently relying entirely on Prompt Engineering for your production AI features? Have you hit the latency wall yet? Let me know your benchmarks in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>promptengineering</category>
      <category>performance</category>
    </item>
    <item>
      <title>Exporting Magento 2 Data: Flatten EAV with SQL &amp; Node</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Mon, 06 Jul 2026 10:57:31 +0000</pubDate>
      <link>https://dev.to/vesviet/exporting-magento-2-data-flatten-eav-with-sql-node-2aoa</link>
      <guid>https://dev.to/vesviet/exporting-magento-2-data-flatten-eav-with-sql-node-2aoa</guid>
      <description>&lt;p&gt;&lt;strong&gt;Answer-first:&lt;/strong&gt; Production-grade guide to extracting data from Magento 2's EAV model. Includes direct SQL queries and a resilient Node.js streaming pipeline.&lt;/p&gt;

&lt;p&gt;When migrating off Magento 2, the first obstacle is always the database schema. Magento does not store data in clean flat rows — it uses an &lt;strong&gt;Entity-Attribute-Value (EAV)&lt;/strong&gt; model that spreads data across dozens of tables with store-scope inheritance. Understanding this before writing SQL will save you days.&lt;/p&gt;

&lt;p&gt;This guide covers two extraction problems: &lt;strong&gt;order export&lt;/strong&gt; (the simpler case) and &lt;strong&gt;product catalog export&lt;/strong&gt; (the genuinely hard case), followed by a production-grade Node.js pipeline to ingest that data into your new service databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Exporting Orders
&lt;/h2&gt;

&lt;p&gt;Order data lives across &lt;code&gt;sales_order&lt;/code&gt;, &lt;code&gt;sales_order_address&lt;/code&gt;, &lt;code&gt;sales_order_payment&lt;/code&gt;, and &lt;code&gt;sales_order_item&lt;/code&gt;. Unlike the product catalog, this is standard foreign-key joins — not full EAV pivoting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full Order + Payment + Shipping Export
&lt;/h3&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;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt;            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;increment_id&lt;/span&gt;         &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;magento_order_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;               &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;order_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grand_total&lt;/span&gt;          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_currency_code&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;           &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;order_created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_firstname&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;customer_first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_lastname&lt;/span&gt;    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;customer_last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;-- Shipping address (denormalized)&lt;/span&gt;
    &lt;span class="n"&gt;soa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ship_street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;                &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ship_city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ship_region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;postcode&lt;/span&gt;            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ship_postcode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country_id&lt;/span&gt;          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ship_country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telephone&lt;/span&gt;           &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ship_phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;-- Payment method&lt;/span&gt;
    &lt;span class="n"&gt;sop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;method&lt;/span&gt;              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;payment_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_trans_id&lt;/span&gt;       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;payment_transaction_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;-- Shipment (NULL if not yet fulfilled)&lt;/span&gt;
    &lt;span class="n"&gt;sos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt;           &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;shipment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;shipped_at&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_order&lt;/span&gt; &lt;span class="n"&gt;so&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;sales_order_address&lt;/span&gt; &lt;span class="n"&gt;soa&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;soa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;soa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'shipping'&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;sales_order_payment&lt;/span&gt; &lt;span class="n"&gt;sop&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_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;sales_shipment&lt;/span&gt; &lt;span class="n"&gt;sos&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt;

&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'canceled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fraud'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;so&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;'2022-01-01 00:00:00'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Order Line Items (Second Pass)
&lt;/h3&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;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;                &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;product_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qty_ordered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qty_shipped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qty_refunded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;               &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;row_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_item_id&lt;/span&gt;      &lt;span class="c1"&gt;-- non-null for configurable child rows&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_order_item&lt;/span&gt; &lt;span class="n"&gt;soi&lt;/span&gt;

&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_item_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;  &lt;span class="c1"&gt;-- skip phantom child rows for configurables&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;soi&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;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;soi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Join on &lt;code&gt;order_id&lt;/code&gt; in your ingestion script to reconstruct the full order object.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: Exporting the Product Catalog (The Hard Part)
&lt;/h2&gt;

&lt;p&gt;This is where most migration engineers underestimate the effort. The product catalog uses full EAV with &lt;strong&gt;store scope inheritance&lt;/strong&gt;: a value at &lt;code&gt;store_id = 0&lt;/code&gt; (Admin/Global) is the default; a value at a specific &lt;code&gt;store_id&lt;/code&gt; overrides it for that store view. A naive &lt;code&gt;SELECT *&lt;/code&gt; will return corrupted or incomplete data.&lt;/p&gt;

&lt;p&gt;The correct approach is a two-step process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Materialize Attribute IDs
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;attribute_id&lt;/code&gt; values are &lt;strong&gt;environment-specific&lt;/strong&gt; — they differ between Magento installations. Run this once and use the result to populate your export 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="n"&gt;attribute_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backend_type&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;eav_attribute&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;entity_type_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;entity_type_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;eav_entity_type&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;entity_type_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'catalog_product'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;attribute_code&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'url_key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'short_description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'special_price'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'visibility'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'weight'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Flattened Product Export with Store-Scope Fallback
&lt;/h3&gt;

&lt;p&gt;This query exports products for store &lt;code&gt;store_id = 1&lt;/code&gt;. For each attribute, it prefers the store-specific value and falls back to the global default (&lt;code&gt;store_id = 0&lt;/code&gt;). Replace the &lt;code&gt;attribute_id&lt;/code&gt; values with results from Step 1:&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="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type_id&lt;/span&gt;                                           &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;product_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;-- Name (varchar): prefer store-specific, fallback to global&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v_name_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_name_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="k"&gt;AS&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;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v_url_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_url_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;url_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;-- Status: 1=Enabled, 2=Disabled (int)&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i_status_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i_status_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;-- Visibility: 1=Not visible, 4=Catalog+Search (int)&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i_vis_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i_vis_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;-- Price (decimal — always global scope in Magento)&lt;/span&gt;
    &lt;span class="n"&gt;d_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;                                       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d_special&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;                                     &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;special_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d_weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;                                      &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;catalog_product_entity&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;

&lt;span class="c1"&gt;-- === VARCHAR: name ===&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;catalog_product_entity_varchar&lt;/span&gt; &lt;span class="n"&gt;v_name_s&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v_name_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v_name_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;73&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v_name_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;catalog_product_entity_varchar&lt;/span&gt; &lt;span class="n"&gt;v_name_g&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v_name_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v_name_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;73&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v_name_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;-- === VARCHAR: url_key ===&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;catalog_product_entity_varchar&lt;/span&gt; &lt;span class="n"&gt;v_url_s&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v_url_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v_url_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v_url_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;catalog_product_entity_varchar&lt;/span&gt; &lt;span class="n"&gt;v_url_g&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v_url_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v_url_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v_url_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;-- === INT: status ===&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;catalog_product_entity_int&lt;/span&gt; &lt;span class="n"&gt;i_status_s&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;i_status_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_status_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_status_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;catalog_product_entity_int&lt;/span&gt; &lt;span class="n"&gt;i_status_g&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;i_status_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_status_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_status_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;-- === INT: visibility ===&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;catalog_product_entity_int&lt;/span&gt; &lt;span class="n"&gt;i_vis_s&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;i_vis_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_vis_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_vis_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;catalog_product_entity_int&lt;/span&gt; &lt;span class="n"&gt;i_vis_g&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;i_vis_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_vis_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i_vis_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;-- === DECIMAL: price, special_price, weight (global only) ===&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;catalog_product_entity_decimal&lt;/span&gt; &lt;span class="n"&gt;d_price&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;d_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;77&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;d_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;catalog_product_entity_decimal&lt;/span&gt; &lt;span class="n"&gt;d_special&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d_special&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;d_special&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;d_special&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;catalog_product_entity_decimal&lt;/span&gt; &lt;span class="n"&gt;d_weight&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d_weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;d_weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;d_weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;-- Only export enabled products&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i_status_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i_status_g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; On catalogs with 25,000+ SKUs, this query will be slow. Run &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; first, ensure composite indexes exist on &lt;code&gt;(entity_id, attribute_id, store_id)&lt;/code&gt; for each EAV value table, and batch by &lt;code&gt;entity_id&lt;/code&gt; ranges (&lt;code&gt;WHERE e.entity_id BETWEEN 1 AND 5000&lt;/code&gt;) to avoid locking your production database.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Part 3: The Production Node.js Ingestion Pipeline
&lt;/h2&gt;

&lt;p&gt;With data exported to CSV, you need a streaming pipeline that handles gigabytes without OOM, with batching, retry logic, idempotency, and a dead-letter queue for failed rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CSV File → Readable Stream → csv-parse → Batch Collector → DB Upsert (with retry)
                                                         ↓ (on max retries)
                                                   Dead-Letter File (JSONL)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// migrate.js — Production-grade Magento → PostgreSQL pipeline&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Transform&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;promisify&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;util&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;csv-parse&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&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;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// your pg connection pool&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pipeline&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;BATCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&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;MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;RETRY_BASE_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&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;dlqStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./failed-rows.jsonl&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="na"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;processed&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;span class="nx"&gt;failed&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// Exponential backoff retry&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&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;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;MAX_RETRIES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;MAX_RETRIES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&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;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RETRY_BASE_MS&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&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;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n⚠ &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed (attempt &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;). Retrying in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms…`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Upsert batch — idempotent by magento_order_id&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;upsertBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;client&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="nf"&gt;connect&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BEGIN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&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;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
                INSERT INTO orders (
                    magento_order_id, magento_increment_id, status,
                    total_amount, currency, customer_email, created_at
                ) VALUES ($1,$2,$3,$4,$5,$6,$7)
                ON CONFLICT (magento_order_id) DO UPDATE SET
                    status       = EXCLUDED.status,
                    total_amount = EXCLUDED.total_amount,
                    updated_at   = NOW()
            `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;magento_order_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order_created_at&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMMIT&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;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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ROLLBACK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;throw&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="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&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;// Transform stream: collect rows into batches, flush with backpressure&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createBatchCollector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onBatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&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;flush&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;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;`batch ~row &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;processed&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="nx"&gt;processed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;stdout&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;`\r✓ &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; rows | ✗ &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed | `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s2"&gt;`&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="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;s elapsed`&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="nx"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n✗ Permanent batch failure: &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="nx"&gt;message&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="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dlqStream&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="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="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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="nf"&gt;callback&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;objectMode&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="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&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;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;batchSize&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;toFlush&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toFlush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;callback&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="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;callback&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;migrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvPath&lt;/span&gt;&lt;span class="p"&gt;)&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="s2"&gt;`\nMigrating: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;csvPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | Batch: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | Retries: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MAX_RETRIES&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;columns&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="na"&gt;skip_empty_lines&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="na"&gt;trim&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="nf"&gt;createBatchCollector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upsertBatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;dlqStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&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;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&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="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="s2"&gt;`\n\n✅ Done in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s — &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; rows | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; DLQ`&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;failed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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="s2"&gt;`   DLQ: ./failed-rows.jsonl`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;migrate&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;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./orders.csv&lt;/span&gt;&lt;span class="dl"&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="o"&gt;=&amp;gt;&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;✗ Fatal:&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;message&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="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;h3&gt;
  
  
  Key Design Decisions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Idempotency (&lt;code&gt;ON CONFLICT DO UPDATE&lt;/code&gt;):&lt;/strong&gt; The pipeline can be safely restarted. If it crashes at row 47,000, rows 1–47,000 are simply updated to the same values when you re-run. No duplicates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dead-Letter Queue:&lt;/strong&gt; Batches that exhaust all retries are written to &lt;code&gt;failed-rows.jsonl&lt;/code&gt;. After the migration, inspect the file, fix the root cause, and re-run the script pointing at the DLQ file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backpressure:&lt;/strong&gt; The &lt;code&gt;callback()&lt;/code&gt; in the Transform stream is not called until &lt;code&gt;upsertBatch&lt;/code&gt; resolves. Node.js automatically pauses the readable stream when the database is under pressure — no manual &lt;code&gt;pause()/resume()&lt;/code&gt; needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;stream.pipeline&lt;/code&gt;:&lt;/strong&gt; Using the promisified &lt;code&gt;pipeline&lt;/code&gt; instead of manually chaining &lt;code&gt;.pipe()&lt;/code&gt; ensures that if any stream in the chain errors, all other streams are automatically destroyed and file handles are released.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run migration&lt;/span&gt;
node migrate.js ./exports/magento-orders.csv

&lt;span class="c"&gt;# Replay only failed rows&lt;/span&gt;
node migrate.js ./failed-rows.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the full architectural context of where this extracted data lands in a microservice ecosystem, see &lt;a href="https://dev.to/posts/why-migrate-magento-to-microservices/"&gt;Why You Should Migrate from Magento to Microservices&lt;/a&gt; and the &lt;a href="https://dev.to/posts/moving-from-magento-to-microservices/"&gt;Zero-Downtime Migration Blueprint&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go deeper:&lt;/strong&gt; &lt;a href="https://dev.to/posts/architecting-21-service-ecommerce-golang-ddd/"&gt;Architecting a 21-Service E-commerce Ecosystem with Golang &amp;amp; DDD&lt;/a&gt; — the distributed microservices architecture that this data pipeline feeds into.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt;&lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: What is Magento?&lt;/strong&gt;&lt;br&gt;
A: &lt;br&gt;
&lt;strong&gt;Magento&lt;/strong&gt; is a critical architectural pattern or system discussed in this guide. Production-grade guide to extracting data from Magento 2's EAV model. Includes direct SQL queries and a resilient Node.js streaming pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How does Magento compare to traditional alternatives?&lt;/strong&gt;&lt;br&gt;
A: &lt;br&gt;
Unlike legacy systems, &lt;strong&gt;Magento&lt;/strong&gt; introduces modern microservices or event-driven paradigms that scale efficiently. This article explores the exact tradeoffs and engineering constraints involved.&lt;/p&gt;

</description>
      <category>magento</category>
      <category>sql</category>
      <category>node</category>
      <category>datamigration</category>
    </item>
    <item>
      <title>Migrating Magento to Microservices: When &amp; Why</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Thu, 02 Jul 2026 23:30:40 +0000</pubDate>
      <link>https://dev.to/vesviet/migrating-magento-to-microservices-when-why-g7h</link>
      <guid>https://dev.to/vesviet/migrating-magento-to-microservices-when-why-g7h</guid>
      <description>&lt;p&gt;&lt;strong&gt;Answer-first:&lt;/strong&gt; Honest breakdown of why Magento's monolithic architecture becomes a liability at scale and the exact reasons to migrate to a microservice ecosystem.&lt;/p&gt;

&lt;p&gt;Let's be direct: Magento is not a bad platform. For thousands of businesses, it is the right tool. It has a mature plugin ecosystem, a large developer community, and a proven track record across enterprise e-commerce.&lt;/p&gt;

&lt;p&gt;But there is a ceiling. And when you hit it, you feel it everywhere — in your deployment pipeline, in your database query times, in your team's ability to ship features independently, and ultimately in your ability to serve customers reliably at scale.&lt;/p&gt;

&lt;p&gt;This post is about what that ceiling looks like technically, why it exists architecturally, and what a migration to microservices actually solves — and what it doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem: Magento is a Shared-State Monolith
&lt;/h2&gt;

&lt;p&gt;Magento's architecture is fundamentally a single application with a single shared MySQL database. Every module — catalog, orders, payments, inventory, customers, promotions — reads and writes to the same database cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TB
    subgraph "Magento Monolith"
        APP["Single PHP Application&amp;lt;br&amp;gt;Catalog · Orders · Payment&amp;lt;br&amp;gt;Inventory · Customers · CMS"]
        APP --&amp;gt; DB[("Single MySQL DB&amp;lt;br&amp;gt;300+ tables")]
        APP --&amp;gt; CACHE["Varnish / Redis Cache"]
    end

    CLIENT["Web / Mobile"] --&amp;gt; APP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design works well at low-to-medium scale. The problem surfaces when you need to grow.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. You Cannot Scale Selectively
&lt;/h3&gt;

&lt;p&gt;During a flash sale, your &lt;code&gt;Order&lt;/code&gt; and &lt;code&gt;Checkout&lt;/code&gt; modules get hammered. Your &lt;code&gt;Catalog&lt;/code&gt; module is mostly idle. In Magento, you cannot scale just the checkout flow — you must scale the entire application. Every PHP worker you spin up carries the full weight of every module, whether it's under load or not.&lt;/p&gt;

&lt;p&gt;In a microservice architecture, you scale surgically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Scale only the Order service during flash sale&lt;/span&gt;
&lt;span class="c1"&gt;# Other services remain at baseline&lt;/span&gt;
&lt;span class="na"&gt;order-service:    replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;   &lt;span class="c1"&gt;# 10x during sale&lt;/span&gt;
&lt;span class="na"&gt;checkout-service: replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
&lt;span class="na"&gt;payment-service:  replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;
&lt;span class="na"&gt;catalog-service:  replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;   &lt;span class="c1"&gt;# Unchanged&lt;/span&gt;
&lt;span class="na"&gt;analytics-service: replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Unchanged&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cost difference at scale is measurable. In our production environment, selective scaling during flash sale events reduced EC2 compute spend by approximately 60% compared to scaling the full Magento stack uniformly — because we only scaled the 3 services under load, not all 21.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A Single Failure Brings Down Everything
&lt;/h3&gt;

&lt;p&gt;In Magento, a misbehaving extension, a slow database query, or a memory leak in one module can cascade into a full site outage. The application shares a process space and a database connection pool.&lt;/p&gt;

&lt;p&gt;In a distributed system, failure is contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Magento:          Review module crashes → entire site down
Microservices:    Review service crashes → customers still browse, add to cart, and pay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not theoretical. The &lt;code&gt;Review&lt;/code&gt; service going down should never affect the &lt;code&gt;Payment&lt;/code&gt; service. Database isolation enforces this at the infrastructure level — each service owns its own PostgreSQL instance. A slow query in the &lt;code&gt;Analytics&lt;/code&gt; database cannot lock rows in the &lt;code&gt;Order&lt;/code&gt; database.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The EAV Schema Becomes a Performance Liability
&lt;/h3&gt;

&lt;p&gt;Magento's product catalog uses an Entity-Attribute-Value (EAV) model. Instead of storing product data in flat rows, it spreads attributes across multiple tables: &lt;code&gt;catalog_product_entity_varchar&lt;/code&gt;, &lt;code&gt;catalog_product_entity_int&lt;/code&gt;, &lt;code&gt;catalog_product_entity_decimal&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;Fetching a single product with 30 attributes can require joining 5+ tables. At 25,000+ SKUs with complex attribute sets, this becomes a measurable latency problem — especially for search and listing pages. The SQL to export even a basic order manifest from Magento looks like this:&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;-- Just to get orders with payment and shipment IDs — already 3 JOINs&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;sales_order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt;        &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Order ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sales_order_payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Payment ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sales_shipment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt;      &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Shipment ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sales_order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Order Status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sales_order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grand_total&lt;/span&gt;       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Total"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_order&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;sales_order_payment&lt;/span&gt; 
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sales_order_payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_id&lt;/span&gt;&lt;span class="p"&gt;)&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;sales_shipment&lt;/span&gt; 
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sales_shipment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sales_order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is just orders. The product catalog EAV joins are significantly worse — fetching a single product with 30 attributes touches &lt;code&gt;catalog_product_entity_varchar&lt;/code&gt;, &lt;code&gt;catalog_product_entity_int&lt;/code&gt;, &lt;code&gt;catalog_product_entity_decimal&lt;/code&gt;, and more in a single query. For a full breakdown of how to extract and flatten this data during migration, see &lt;a href="https://dev.to/posts/exporting-magento-2-data-flat-sql-nodejs/"&gt;Exporting Magento 2 Orders: Bypassing the EAV Model with Clean SQL &amp;amp; Node.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A dedicated &lt;code&gt;Catalog Service&lt;/code&gt; with a purpose-built schema and an Elasticsearch read model solves this cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writes go to a normalized PostgreSQL schema owned by the Catalog service&lt;/li&gt;
&lt;li&gt;A CQRS read model in Elasticsearch serves product listings and search with sub-100ms response times&lt;/li&gt;
&lt;li&gt;Price and stock updates propagate via Dapr events, keeping the search index fresh in near real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CQRS flow works like this: when the &lt;code&gt;Catalog&lt;/code&gt; or &lt;code&gt;Pricing&lt;/code&gt; service updates a product, it publishes a &lt;code&gt;catalog.product.updated&lt;/code&gt; or &lt;code&gt;pricing.price.updated&lt;/code&gt; event to the Dapr event mesh. The &lt;code&gt;Search&lt;/code&gt; service subscribes to these topics and rebuilds the Elasticsearch document for that SKU — no cron jobs, no full reindex, no stale data windows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph LR
    CAT[Catalog Service] -- "catalog.product.updated" --&amp;gt; DAPR[Dapr PubSub]
    PRC[Pricing Service] -- "pricing.price.updated" --&amp;gt; DAPR
    WH[Warehouse Service] -- "warehouse.stock.changed" --&amp;gt; DAPR
    DAPR --&amp;gt; SEARCH[Search Service Worker]
    SEARCH --&amp;gt; ES[(Elasticsearch)]
    ES -- "sub-100ms reads" --&amp;gt; GW[API Gateway]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Teams Step on Each Other
&lt;/h3&gt;

&lt;p&gt;At scale, multiple squads need to work on the same platform simultaneously. In Magento, this means multiple teams modifying the same codebase, the same database schema, and deploying together.&lt;/p&gt;

&lt;p&gt;Conway's Law is real: your system architecture mirrors your team structure. A monolith forces teams to coordinate deployments, negotiate schema changes, and share release cycles. One team's bug blocks another team's feature.&lt;/p&gt;

&lt;p&gt;Bounded contexts solve this. When the &lt;code&gt;Payment&lt;/code&gt; team owns their service end-to-end — their codebase, their database, their deployment pipeline — they ship independently. A bug in the &lt;code&gt;Loyalty&lt;/code&gt; service does not block a &lt;code&gt;Checkout&lt;/code&gt; release.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Distributed Transactions Require Explicit Design
&lt;/h3&gt;

&lt;p&gt;Magento handles checkout as a synchronous database transaction: reserve stock, create order, capture payment — all in one &lt;code&gt;BEGIN ... COMMIT&lt;/code&gt; block. This is simple and correct for a single database.&lt;/p&gt;

&lt;p&gt;At scale, this becomes a liability. A slow payment gateway response holds a database transaction open, consuming connection pool slots. Under load, this cascades into connection exhaustion.&lt;/p&gt;

&lt;p&gt;The microservice answer is the &lt;strong&gt;Saga pattern&lt;/strong&gt;: each step is a local transaction, and failures trigger compensating transactions rather than database rollbacks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant CK as Checkout Service
    participant WH as Warehouse Service
    participant PAY as Payment Service
    participant ORD as Order Service

    CK-&amp;gt;&amp;gt;WH: Reserve stock (TTL 15 min)
    WH--&amp;gt;&amp;gt;CK: Stock reserved ✅

    CK-&amp;gt;&amp;gt;PAY: Authorize payment
    PAY--&amp;gt;&amp;gt;CK: Authorized ✅

    CK-&amp;gt;&amp;gt;ORD: Create order
    ORD--&amp;gt;&amp;gt;CK: Order created ✅

    Note over CK,ORD: If payment fails at any point:
    CK-&amp;gt;&amp;gt;WH: Release reservation (compensation)
    CK-&amp;gt;&amp;gt;PAY: Void authorization (compensation)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No long-lived database transactions. No connection pool exhaustion. Each service handles its own state, and failures trigger explicit rollback logic rather than implicit database rollbacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Microservices Actually Deliver
&lt;/h2&gt;

&lt;p&gt;Based on a production 21-service Go ecosystem handling 10,000+ orders per day, here is what the architecture concretely delivers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Magento&lt;/th&gt;
&lt;th&gt;Microservices&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per-module scaling&lt;/td&gt;
&lt;td&gt;❌ Scale entire app&lt;/td&gt;
&lt;td&gt;✅ Scale only what's under load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault isolation&lt;/td&gt;
&lt;td&gt;❌ One crash = site down&lt;/td&gt;
&lt;td&gt;✅ Isolated failure domains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database isolation&lt;/td&gt;
&lt;td&gt;❌ 300+ shared tables&lt;/td&gt;
&lt;td&gt;✅ Separate DB per service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Independent deploys&lt;/td&gt;
&lt;td&gt;❌ Full app deployment&lt;/td&gt;
&lt;td&gt;✅ Deploy one service at a time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment resilience&lt;/td&gt;
&lt;td&gt;❌ Sync, no retry logic&lt;/td&gt;
&lt;td&gt;✅ Saga + DLQ + compensation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search performance&lt;/td&gt;
&lt;td&gt;⚠️ EAV joins at query time&lt;/td&gt;
&lt;td&gt;✅ Pre-indexed Elasticsearch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event reliability&lt;/td&gt;
&lt;td&gt;❌ Sync observers&lt;/td&gt;
&lt;td&gt;✅ Transactional outbox, at-least-once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-downtime deploy&lt;/td&gt;
&lt;td&gt;⚠️ Maintenance mode&lt;/td&gt;
&lt;td&gt;✅ Rolling updates per service&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference between these two event models is worth unpacking. In Magento, events are synchronous PHP observers — if the observer is slow or throws an exception, it blocks the entire request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Magento: Synchronous observer — blocks the HTTP request&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderPlaceAfterObserver&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ObserverInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Observer&lt;/span&gt; &lt;span class="nv"&gt;$observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$observer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getOrder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// If this call to an external API is slow or fails,&lt;/span&gt;
        &lt;span class="c1"&gt;// the customer's checkout request hangs or errors out&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;loyaltyService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;awardPoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCustomerId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getGrandTotal&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;analyticsService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;trackPurchase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Another blocking call&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;In the microservice model, the &lt;code&gt;Order&lt;/code&gt; service writes the event to an outbox table in the same database transaction as the order itself — then a background worker publishes it asynchronously:&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="c"&gt;// Go: Transactional Outbox — event is guaranteed, non-blocking&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uc&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;OrderUsecase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTx&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="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// 1. Save the order&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="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SaveOrder&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;o&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="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c"&gt;// 2. Write event to outbox in the SAME transaction&lt;/span&gt;
        &lt;span class="c"&gt;// If the DB commits, the event is guaranteed to be published&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SaveOutboxEvent&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="s"&gt;"orders.order.created"&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="p"&gt;})&lt;/span&gt;
    &lt;span class="c"&gt;// Background worker picks up outbox events and publishes to Dapr&lt;/span&gt;
    &lt;span class="c"&gt;// Checkout request returns immediately — no blocking on downstream services&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The outbox guarantees delivery even if the Dapr broker is temporarily unavailable. The Magento observer has no such guarantee — a failed observer silently drops the event.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost of Migration
&lt;/h2&gt;

&lt;p&gt;This is where most migration posts stop being honest. Microservices are not free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operational complexity increases dramatically.&lt;/strong&gt; You are now running 21+ services, each with its own database, deployment pipeline, and failure modes. You need Kubernetes, a service mesh, distributed tracing, centralized logging, and a team that understands all of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distributed systems introduce new failure modes.&lt;/strong&gt; Network partitions, event ordering issues, idempotency bugs, and eventual consistency edge cases do not exist in a monolith. They require explicit engineering investment to handle correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The migration itself is high-risk.&lt;/strong&gt; A naive "big bang" rewrite is how multimillion-dollar projects fail. The only safe path is an incremental migration using the Strangler Fig pattern — routing traffic gradually from the monolith to new services while maintaining data consistency through CDC pipelines and bidirectional sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team size matters.&lt;/strong&gt; A team of 2-3 developers cannot maintain 21 services. The operational overhead alone requires dedicated platform engineering capacity. Shopify or a managed Magento cloud is the right answer for small teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Migrate (And When Not To)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Migrate when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have 5+ developers and dedicated DevOps capacity&lt;/li&gt;
&lt;li&gt;You are hitting Magento's scaling ceiling (slow deploys, shared DB contention, module conflicts)&lt;/li&gt;
&lt;li&gt;You need independent team autonomy across multiple squads&lt;/li&gt;
&lt;li&gt;You require custom payment flows, multi-warehouse WMS, or VN-specific integrations that Magento handles poorly&lt;/li&gt;
&lt;li&gt;You want full source ownership with zero vendor licensing costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Do not migrate when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your team is under 5 engineers&lt;/li&gt;
&lt;li&gt;You need to launch in weeks, not months&lt;/li&gt;
&lt;li&gt;Your traffic is manageable on a well-tuned Magento stack&lt;/li&gt;
&lt;li&gt;You rely heavily on Magento's plugin ecosystem&lt;/li&gt;
&lt;li&gt;You do not have the operational maturity to run Kubernetes in production&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Magento's monolithic architecture is not a flaw — it is a deliberate design choice that optimizes for simplicity and ecosystem richness. For the majority of e-commerce businesses, it is the correct choice. (If you are evaluating alternatives to Magento but aren't ready for full microservices, evaluating the &lt;a href="https://dev.to/series/modular-monolith-architecture/"&gt;Modular Monolith Architecture&lt;/a&gt; alternative is highly recommended).&lt;/p&gt;

&lt;p&gt;The migration to microservices makes sense when the cost of that simplicity — shared database contention, inability to scale selectively, coupled deployments, cascading failures — exceeds the cost of distributed systems complexity.&lt;/p&gt;

&lt;p&gt;That crossover point is real, and when you hit it, the architectural investment pays for itself in deployment velocity, operational resilience, and the ability to scale exactly what needs scaling — nothing more.&lt;/p&gt;

&lt;p&gt;For the exact playbook on how to execute this migration safely — including the 3-phase Strangler Fig pattern, Debezium CDC pipelines, and bidirectional sync — read &lt;a href="https://dev.to/posts/moving-from-magento-to-microservices/"&gt;The Zero-Downtime Blueprint: Moving from Magento to Microservices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are still evaluating team capability before a migration, read our core guide on &lt;a href="https://dev.to/posts/magento-vietnam/"&gt;Magento Development in Vietnam: 2026 Hiring Guide&lt;/a&gt;. For the destination stack, explore the complete &lt;a href="https://dev.to/posts/go-microservices/"&gt;Go Microservices Architecture: Production Guide&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt;&lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: When should you migrate from Magento to microservices?&lt;/strong&gt;&lt;br&gt;
A: &lt;br&gt;
Migrate from Magento to microservices when you have &lt;strong&gt;5+ engineers with dedicated DevOps capacity&lt;/strong&gt;, you are hitting Magento's scaling ceiling (slow deploys, shared database contention, module conflicts blocking independent team deployments), and you require fine-grained fault isolation — where a failure in one domain (e.g., reviews, loyalty) should never bring down the entire checkout flow. Do &lt;strong&gt;not&lt;/strong&gt; migrate if your team is under 5 engineers, your traffic is manageable on a well-tuned Magento stack, or you do not have the operational maturity to run Kubernetes in production. The operational overhead of 21+ services is real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is the Strangler Fig pattern for Magento migration?&lt;/strong&gt;&lt;br&gt;
A: &lt;br&gt;
The &lt;strong&gt;Strangler Fig pattern&lt;/strong&gt; is an incremental migration strategy where new microservices gradually wrap around the legacy Magento monolith, intercepting traffic domain by domain until the monolith becomes a hollow shell. In practice: Phase 1 routes reads to new services while writes still hit Magento; Phase 2 migrates write APIs incrementally (Customer first, then Catalog, then Order) with bidirectional sync keeping Magento in sync; Phase 3 cuts over all traffic and keeps Magento as a hot standby for 30 days before terminating. No big-bang rewrite. Each phase is independently reversible with a feature flag toggle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is the EAV schema problem in Magento?&lt;/strong&gt;&lt;br&gt;
A: &lt;br&gt;
Magento's &lt;strong&gt;Entity-Attribute-Value (EAV)&lt;/strong&gt; model stores product attributes across multiple tables (&lt;code&gt;catalog_product_entity_varchar&lt;/code&gt;, &lt;code&gt;catalog_product_entity_int&lt;/code&gt;, &lt;code&gt;catalog_product_entity_decimal&lt;/code&gt;, etc.) instead of flat rows. Fetching a single product with 30 attributes requires joining 5+ tables. At 25,000+ SKUs under load, this becomes a measurable latency problem — especially for search and listing pages. During migration, this means you cannot do a naive &lt;code&gt;SELECT *&lt;/code&gt; export; you need an ETL pipeline to flatten EAV data into the normalized schemas your new microservices expect. Debezium CDC handles ongoing delta sync after the initial ETL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How does the Saga pattern replace Magento's database transactions in microservices?&lt;/strong&gt;&lt;br&gt;
A: &lt;br&gt;
Magento handles checkout as a &lt;strong&gt;synchronous database transaction&lt;/strong&gt;: reserve stock, create order, capture payment — all in one &lt;code&gt;BEGIN ... COMMIT&lt;/code&gt;. This works for a single database but breaks in a distributed system because a slow payment gateway response holds a database transaction open, consuming connection pool slots and cascading into connection exhaustion under load. The &lt;strong&gt;Saga pattern&lt;/strong&gt; replaces this with local transactions per service and explicit compensating transactions on failure: if payment authorization fails after stock was reserved, a compensation message triggers &lt;code&gt;release_reservation&lt;/code&gt; on the Warehouse service. No long-lived database locks, no connection pool exhaustion, and each failure case is explicitly handled rather than silently dropped.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ready to Execute the Migration?
&lt;/h2&gt;

&lt;p&gt;If you have decided to migrate — or are building the business case to get executive sign-off — the next step is the technical execution plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/posts/moving-from-magento-to-microservices/"&gt;Zero-Downtime: Moving from Magento to Microservices →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That guide covers the three-phase Strangler Fig execution: the Read-Only Gateway, the Dual-Write sync layer, and the Full Cutover with hot standby — all without dropping a single order.&lt;/p&gt;

</description>
      <category>magento</category>
      <category>microservices</category>
      <category>systemdesign</category>
      <category>migration</category>
    </item>
    <item>
      <title>[AI] Optimizing vLLM Serving: AWQ, GPTQ, &amp; GGUF | SLM Playbook</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Thu, 02 Jul 2026 13:31:02 +0000</pubDate>
      <link>https://dev.to/vesviet/ai-optimizing-vllm-serving-awq-gptq-gguf-slm-playbook-5756</link>
      <guid>https://dev.to/vesviet/ai-optimizing-vllm-serving-awq-gptq-gguf-slm-playbook-5756</guid>
      <description>&lt;p&gt;Successfully training and aligning a Small Language Model (SLM) is only half the battle. In enterprise environments, deploying a model to production serving requires solving three major challenges: &lt;strong&gt;high request concurrency&lt;/strong&gt;, &lt;strong&gt;low response latency&lt;/strong&gt;, and &lt;strong&gt;minimized compute cost&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To achieve this, we must master model compression (&lt;strong&gt;Quantization&lt;/strong&gt;) and high-performance serving configurations using &lt;strong&gt;vLLM&lt;/strong&gt;—the state-of-the-art serving engine for LLMs.&lt;/p&gt;

&lt;p&gt;This final article in &lt;strong&gt;The SLM Playbook&lt;/strong&gt; series compares the technical attributes of AWQ, GPTQ, and GGUF quantization formats, details how to set up &lt;strong&gt;Dynamic LoRA serving&lt;/strong&gt; to conserve VRAM, and outlines a resilient enterprise-grade serving architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Comparing Quantization Formats: AWQ vs. GPTQ vs. GGUF
&lt;/h2&gt;

&lt;p&gt;Quantization is the process of compressing model weights from 16-bit floating-point (FP16/BF16) to lower-bit integer representations (such as INT8 or INT4). This drastically reduces VRAM requirements and accelerates hardware compute operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────┐
│                Quantization Format Comparison                │
├──────────────────┬──────────────────┬────────────────────────┤
│ Format           │ Primary Target   │ Technical Attributes   │
├──────────────────┼──────────────────┼────────────────────────┤
│ AWQ (Recommended)│ GPU Serving      │ Preserves the top 1%   │
│                  │                  │ salient weights in     │
│                  │                  │ FP16. Retains accuracy.│
├──────────────────┼──────────────────┼────────────────────────┤
│ GPTQ             │ GPU Serving      │ Calibration-based      │
│                  │                  │ linear quantization.   │
│                  │                  │ Minor accuracy loss.   │
├──────────────────┼──────────────────┼────────────────────────┤
│ GGUF             │ CPU / Edge       │ Supports dynamic layer │
│                  │                  │ offloading to host CPU │
│                  │                  │ RAM (via llama.cpp).   │
└──────────────────┴──────────────────┴────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.1. AWQ (Activation-aware Weight Quantization)
&lt;/h3&gt;

&lt;p&gt;Not all weights in a neural network contribute equally to its output representation. AWQ discovered that protecting just &lt;strong&gt;1% of the most salient weight channels&lt;/strong&gt; from quantization preserves the majority of model capability.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Mechanism:&lt;/em&gt; AWQ identifies these salient weight channels, keeps them in their native 16-bit format, and quantizes the remaining 99% of non-salient channels to 4-bit.&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Verdict:&lt;/em&gt; AWQ consistently yields lower perplexity (better accuracy) compared to GPTQ on reasoning tasks while executing fast on NVIDIA GPUs using optimized CUDA kernels.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1.2. GPTQ (Generalized Post-Training Quantization)
&lt;/h3&gt;

&lt;p&gt;GPTQ utilizes a calibration dataset to compute second-order weight influences (the Hessian matrix), adjusting remaining weights to compensate for quantization errors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Verdict:&lt;/em&gt; Widely supported across all serving engines. However, for smaller models (under 8B parameters), GPTQ can occasionally introduce noticeable degradation on complex math or programming tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1.3. GGUF (GPT-Generated Unified Format)
&lt;/h3&gt;

&lt;p&gt;Developed by the open-source community surrounding &lt;code&gt;llama.cpp&lt;/code&gt;, GGUF is a single-file model format optimized for mixed CPU/GPU execution.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Verdict:&lt;/em&gt; The standard for running models on local developer machines (MacBooks, laptops) or edge deployments lacking dedicated datacenter GPUs. It is not recommended for high-throughput enterprise backend clusters.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Designing a Dynamic LoRA Architecture
&lt;/h2&gt;

&lt;p&gt;In enterprise deployments, different teams require distinct fine-tuned behaviors (e.g., accounting needs JSON invoice classification, while engineering needs code debugging).&lt;/p&gt;

&lt;p&gt;Hosting separate model instances on individual GPUs drives up infrastructure budgets exponentially. vLLM's &lt;strong&gt;Dynamic LoRA Serving&lt;/strong&gt; resolves this issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   ┌────────────────┐
                   │  User Request  │
                   └───────┬────────┘
                           │
             [Determine Target Adapter via Headers]
             [e.g., 'X-Lora-Adapter: accounting']
                           │
                           ▼
         ┌──────────────────────────────────────┐
         │        vLLM Server Container         │
         │                                      │
         │        ┌───────────────────┐         │
         │        │   Base Model 8B   │         │ (Shared in VRAM)
         │        │   (FP16 or AWQ)   │         │
         │        └─────────┬─────────┘         │
         │                  │                   │
         │     ┌────────────┼────────────┐      │
         │     ▼            ▼            ▼      │ (Loaded dynamically
         │ ┌───────┐    ┌───────┐    ┌───────┐  │  on-demand)
         │ │Lora A │    │Lora B │    │Lora C │  │
         │ └───────┘    └───────┘    └───────┘  │
         └──────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.1. How Dynamic LoRA Operates
&lt;/h3&gt;

&lt;p&gt;vLLM loads a single, shared base model (e.g., Llama 3 8B AWQ) into GPU VRAM. When a request specifies a target LoRA adapter, vLLM dynamically loads the adapter parameters from disk or system RAM and computes the delta weight adjustment ($\Delta W$) on-the-fly during the forward pass.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Advantage:&lt;/em&gt; Reduces memory overhead by up to 90%. Dozens of fine-tuned task-specific adapters can be served simultaneously on a single 24GB GPU.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.2. vLLM Production Command for Dynamic LoRA
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; vllm.entrypoints.openai.api_server &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--model&lt;/span&gt; meta-llama/Meta-Llama-3-8B-Instruct &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--quantization&lt;/span&gt; awq &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--enable-lora&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--max-loras&lt;/span&gt; 8 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--max-lora-rank&lt;/span&gt; 16 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--lora-dtype&lt;/span&gt; auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When invoking the API, clients simply specify their target adapter in the request payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"accounting-lora-adapter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Analyze this invoice..."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Production Serving Benchmarks
&lt;/h2&gt;

&lt;p&gt;The following benchmarks demonstrate the memory and throughput gains achieved on a single NVIDIA A10G (24GB VRAM) running &lt;strong&gt;Llama 3 8B&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────┐
│                     Serving Benchmark Results                │
├──────────────────┬──────────────────┬────────────────────────┤
│ Format           │ Throughput (tps) │ Peak VRAM Usage        │
├──────────────────┼──────────────────┼────────────────────────┤
│ FP16 (Baseline)  │ 32 tokens/sec    │ 16.2 GB (Low batch     │
│                  │                  │ limits, prone to OOM)  │
├──────────────────┼──────────────────┼────────────────────────┤
│ GPTQ 4-bit       │ 74 tokens/sec    │ 6.4 GB (Supports high  │
│                  │                  │ concurrency batches)   │
├──────────────────┼──────────────────┼────────────────────────┤
│ AWQ 4-bit        │ 78 tokens/sec    │ 6.1 GB (15% faster     │
│                  │                  │ TTFT than GPTQ)        │
└──────────────────┴──────────────────┴────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Takeaway:&lt;/strong&gt; Compressing your model to &lt;strong&gt;AWQ 4-bit&lt;/strong&gt; saves over &lt;strong&gt;60% of GPU VRAM&lt;/strong&gt;, increasing sustained serving throughput by &lt;strong&gt;2.4x&lt;/strong&gt; compared to FP16. This provides a resilient foundation for serving high-concurrency enterprise workloads.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary of The SLM Playbook
&lt;/h2&gt;

&lt;p&gt;Our 6-part playbook equips you with the complete workflow needed to customize and serve Small Language Models within your private enterprise infrastructure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Architecture Design:&lt;/strong&gt; Balance cost and capability by deploying local SLMs alongside cloud frontier models via a Hybrid Router Gateway.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Data Engineering:&lt;/strong&gt; Mitigate memorization and clean instruction data using NEFTune noise injection and SemDeDup semantic pruning.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;High-Performance Training:&lt;/strong&gt; Execute LoRA/QLoRA training loops using Axolotl and Unsloth to optimize GPU utilization.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Knowledge Distillation:&lt;/strong&gt; Distill structured reasoning paths (Chain of Thought) from deep models like DeepSeek-R1.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Preference Alignment:&lt;/strong&gt; Align outputs and safety parameters using sample-efficient DPO and GRPO reinforcement learning.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Enterprise Serving:&lt;/strong&gt; Quantize models to 4-bit AWQ and serve multiple tasks concurrently via Dynamic LoRA on vLLM.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By combining hardware optimization with targeted alignment, your team can deploy private, highly optimized models that guarantee data privacy at a fraction of the cost of public APIs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Access the complete source code and configs on the &lt;a href="https://dev.to/series/slm-playbook/"&gt;&lt;strong&gt;SLM Playbook Home Page&lt;/strong&gt;&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;{{&amp;lt; author-cta &amp;gt;}}&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was originally published on my blog at &lt;a href="https://tanhdev.com/series/slm-playbook/part-6-vllm-deployment-evals/" rel="noopener noreferrer"&gt;Optimizing vLLM Serving: AWQ, GPTQ, &amp;amp; GGUF | SLM Playbook&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/strong&gt; &lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt; &lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>devops</category>
      <category>llm</category>
    </item>
    <item>
      <title>[AI] Practical QLoRA Fine-tuning: Axolotl &amp; Unsloth | SLM Playbook</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Tue, 30 Jun 2026 23:39:00 +0000</pubDate>
      <link>https://dev.to/vesviet/ai-practical-qlora-fine-tuning-axolotl-unsloth-slm-playbook-37d1</link>
      <guid>https://dev.to/vesviet/ai-practical-qlora-fine-tuning-axolotl-unsloth-slm-playbook-37d1</guid>
      <description>&lt;p&gt;&lt;em&gt;Author's Note: This is Part 3 of the &lt;a href="https://tanhdev.com/series/slm-playbook/" rel="noopener noreferrer"&gt;SLM &amp;amp; GenAI Playbook&lt;/a&gt;. You can read &lt;a href="https://tanhdev.com/series/slm-playbook/part-2-sft-data-engineering/" rel="noopener noreferrer"&gt;Part 2: SFT Data Engineering&lt;/a&gt; or jump to &lt;a href="https://tanhdev.com/series/slm-playbook/part-4-knowledge-distillation-r1/" rel="noopener noreferrer"&gt;Part 4: Knowledge Distillation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Full-parameter fine-tuning of a large language model is a luxury. For even an 8B model like Llama 3, updating all weights in 16-bit precision requires massive clusters far beyond the reach of mid-sized teams or startups.&lt;/p&gt;

&lt;p&gt;To resolve these hardware barriers, &lt;strong&gt;Parameter-Efficient Fine-Tuning (PEFT)&lt;/strong&gt; methods were developed, with &lt;strong&gt;LoRA&lt;/strong&gt; and &lt;strong&gt;QLoRA&lt;/strong&gt; emerging as the dominant paradigms. They allow developers to train multi-billion parameter models on a single consumer GPU (like an RTX 3090, 4090, or A10G) while maintaining near-zero performance degradation compared to full tuning.&lt;/p&gt;

&lt;p&gt;This article dissects the mathematics behind low-rank adaptation, details how to build production-grade &lt;strong&gt;Axolotl&lt;/strong&gt; configurations, and uses &lt;strong&gt;Unsloth&lt;/strong&gt; to accelerate training loops.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. LoRA: Low-Rank Adaptation Matrix Decomposition
&lt;/h2&gt;

&lt;p&gt;During domain-specific fine-tuning (e.g., text-to-SQL or medical terminology), parameter weight updates do not occupy the full parameter space; they exhibit a very low &lt;strong&gt;intrinsic rank&lt;/strong&gt;. Instead of updating the massive original weight matrix 

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;∈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathbb"&gt;R&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;d&lt;/span&gt;&lt;span class="mbin mtight"&gt;×&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, LoRA freezes 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 and models the weight updates 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;Δ&lt;/span&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 as the product of two extremely low-rank matrices 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 and 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 of rank 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 (
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;r&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≪&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;min&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;d&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;k&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
):&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;Δ&lt;/span&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;B&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is the frozen pre-trained weight matrix (no gradient updates).&lt;/li&gt;
&lt;li&gt;  
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;B&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;∈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathbb"&gt;R&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;d&lt;/span&gt;&lt;span class="mbin mtight"&gt;×&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 and 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;∈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathbb"&gt;R&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;r&lt;/span&gt;&lt;span class="mbin mtight"&gt;×&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 are the trainable adapter matrices.&lt;/li&gt;
&lt;li&gt;  
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is the &lt;strong&gt;Rank&lt;/strong&gt; parameter (typically 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;r&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;∈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;[&lt;/span&gt;&lt;span class="mord"&gt;8&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;64&lt;/span&gt;&lt;span class="mclose"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        LoRA Layer Forward Pass:

             Input x 
             ┌───┴───┐
             │       │
             ▼       ▼
          ┌─────┐ ┌─────┐
          │     │ │  A  │ (Rank r, Gaussian initialized)
          │ W_0 │ └─────┘
          │     │    │ (r-dimensional vector)
          │(Frozen)  ▼
          │     │ ┌─────┐
          │     │ │  B  │ (Rank r, Zero initialized)
          └─────┘ └─────┘
             │       │
             ▼       ▼
            h_W     h_LoRA * (alpha / r)
             └───┬───┘
                 ▼
              Output y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  1.1. LoRA Forward Pass Equation
&lt;/h3&gt;

&lt;p&gt;For a given input 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, the output activation 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is computed as:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;Δ&lt;/span&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;B&lt;/span&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is a constant scaling factor that controls the adapter's influence over the base model weights.&lt;/li&gt;
&lt;li&gt;  At the start of training, 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is randomly initialized via a Gaussian distribution, and 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is initialized to zero. Consequently, 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;Δ&lt;/span&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, ensuring the model's baseline behavior is completely unchanged at step zero.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. QLoRA: Maximizing VRAM Efficiency via Double Quantization
&lt;/h2&gt;

&lt;p&gt;Introduced by Tim Dettmers in 2023, &lt;strong&gt;QLoRA (Quantized Low-Rank Adaptation)&lt;/strong&gt; takes memory efficiency a step further by quantizing the base model weights 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 to a highly compressed &lt;strong&gt;4-bit&lt;/strong&gt; representation, while keeping the active LoRA adapter weights in 16-bit precision.&lt;/p&gt;

&lt;p&gt;QLoRA relies on three key mathematical and systems innovations:&lt;/p&gt;
&lt;h3&gt;
  
  
  2.1. NormalFloat 4 (NF4) Data Type
&lt;/h3&gt;

&lt;p&gt;Neural network weights naturally follow a zero-centered normal distribution. Standard linear quantization schemes (like INT4) allocate quantization bins uniformly, wasting precision at the sparse tails of the distribution.&lt;/p&gt;

&lt;p&gt;NF4 addresses this by establishing non-linear quantization intervals such that &lt;strong&gt;each bin contains an equal number of expected parameters (equal information entropy)&lt;/strong&gt;:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;&lt;span class="mop op-symbol large-op"&gt;∫&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;q&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size3 size1 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;q&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size3 size1 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mbin mtight"&gt;+&lt;/span&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathcal"&gt;N&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mord mathnormal"&gt;d&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;const&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;This preserves the maximum information of the original FP16 weights, matching FP4/INT4 precision while cutting model weight size to 4 bits per parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2. Double Quantization (DQ)
&lt;/h3&gt;

&lt;p&gt;In standard quantization, weight blocks are scaled using a 32-bit float constant. With a block size of 64, this scaling constant introduces an overhead of 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;32/64&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 bits per parameter.&lt;/p&gt;

&lt;p&gt;Double Quantization quantizes &lt;strong&gt;these scaling constants themselves&lt;/strong&gt; from 32-bit floats to 8-bit floats with a block size of 256.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Impact:&lt;/em&gt; Reduces scaling overhead from 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 bits/parameter to 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.127&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 bits/parameter, saving approximately &lt;strong&gt;3 GB VRAM&lt;/strong&gt; on an 8B model.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2.3. Paged Optimizers
&lt;/h3&gt;

&lt;p&gt;During training with long sequence lengths or large batches, sudden gradient allocation spikes can exceed physical VRAM limits, triggering OOM crashes.&lt;/p&gt;

&lt;p&gt;Paged Optimizers leverage CUDA Unified Memory to automatically swap (page) optimizer states between GPU VRAM and CPU RAM during peak memory phases, gracefully slowing down training rather than crashing.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Hands-On: Configuring Axolotl for QLoRA
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Axolotl&lt;/strong&gt; is a robust framework for LLM fine-tuning, offering native integration with FlashAttention-2, DeepSpeed, and PyTorch FSDP.&lt;/p&gt;

&lt;p&gt;Here is a complete production-ready &lt;code&gt;qlora_llama3_8b.yml&lt;/code&gt; configuration optimized for a single NVIDIA A10G (24GB VRAM):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Model &amp;amp; Training Mode Config&lt;/span&gt;
&lt;span class="na"&gt;base_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meta-llama/Meta-Llama-3-8B-Instruct&lt;/span&gt;
&lt;span class="na"&gt;model_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LlamaForCausalLM&lt;/span&gt;
&lt;span class="na"&gt;tokenizer_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PreTrainedTokenizerFast&lt;/span&gt;

&lt;span class="c1"&gt;# Enable QLoRA (4-bit NF4 Quantization)&lt;/span&gt;
&lt;span class="na"&gt;load_in_8bit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;load_in_4bit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;gptq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;# Precision settings&lt;/span&gt;
&lt;span class="na"&gt;bf16&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;fp16&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;tf32&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# LoRA Adapter Configuration&lt;/span&gt;
&lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qlora&lt;/span&gt;
&lt;span class="na"&gt;lora_r&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;
&lt;span class="na"&gt;lora_alpha&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;
&lt;span class="na"&gt;lora_dropout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.05&lt;/span&gt;
&lt;span class="na"&gt;lora_target_modules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;q_proj&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;k_proj&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;v_proj&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;o_proj&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gate_proj&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;up_proj&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;down_proj&lt;/span&gt;

&lt;span class="c1"&gt;# Dataset Configurations&lt;/span&gt;
&lt;span class="na"&gt;datasets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./temp_cleaned_dataset.jsonl&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpaca&lt;/span&gt;
    &lt;span class="na"&gt;shards&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="na"&gt;dataset_prepared_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./last_run_prepared&lt;/span&gt;
&lt;span class="na"&gt;val_set_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.05&lt;/span&gt;
&lt;span class="na"&gt;output_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./lora-llama3-8b-output&lt;/span&gt;

&lt;span class="c1"&gt;# Memory &amp;amp; Speed Optimizations&lt;/span&gt;
&lt;span class="na"&gt;sequence_len&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8192&lt;/span&gt;
&lt;span class="na"&gt;sample_packing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;pad_to_sequence_len&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;flash_attention&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# Hyperparameters&lt;/span&gt;
&lt;span class="na"&gt;gradient_accumulation_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
&lt;span class="na"&gt;micro_batch_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;num_epochs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="na"&gt;optimizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paged_adamw_8bit&lt;/span&gt;
&lt;span class="na"&gt;lr_scheduler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cosine&lt;/span&gt;
&lt;span class="na"&gt;learning_rate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0002&lt;/span&gt;
&lt;span class="na"&gt;weight_decay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.01&lt;/span&gt;
&lt;span class="na"&gt;max_grad_norm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;

&lt;span class="c1"&gt;# Checkpointing &amp;amp; Logs&lt;/span&gt;
&lt;span class="na"&gt;save_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
&lt;span class="na"&gt;eval_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
&lt;span class="na"&gt;logging_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Accelerating Loops: 3x Speedup with Unsloth
&lt;/h2&gt;

&lt;p&gt;While Axolotl is highly configurable, standard PyTorch backward passes for attention layers leave performance on the table. &lt;strong&gt;Unsloth&lt;/strong&gt; rewrites the attention and MLP backward steps in raw &lt;strong&gt;OpenAI Triton&lt;/strong&gt;, achieving a &lt;strong&gt;3x speedup&lt;/strong&gt; while reducing memory consumption by &lt;strong&gt;60%&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete Python script to execute QLoRA using Unsloth:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unsloth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastLanguageModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datasets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dataset&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;trl&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SFTTrainer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TrainingArguments&lt;/span&gt;

&lt;span class="n"&gt;max_seq_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt; &lt;span class="c1"&gt;# Limit context length to optimize speed on 24GB GPUs
&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="c1"&gt;# Auto-detect (Float16 or Bfloat16)
&lt;/span&gt;&lt;span class="n"&gt;load_in_4bit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt; &lt;span class="c1"&gt;# Enable 4-bit quantization
&lt;/span&gt;
&lt;span class="c1"&gt;# 1. Initialize model and tokenizer via Unsloth
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastLanguageModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta-llama/Meta-Llama-3-8B-Instruct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_seq_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_seq_length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dtype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;load_in_4bit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load_in_4bit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Add optimized LoRA adapters
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastLanguageModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_peft_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;target_modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gate_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;up_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;down_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;lora_alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lora_dropout&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;span class="c1"&gt;# Unsloth is optimized for dropout = 0
&lt;/span&gt;    &lt;span class="n"&gt;bias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;use_gradient_checkpointing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsloth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Memory-optimized gradient checkpointing
&lt;/span&gt;    &lt;span class="n"&gt;random_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3407&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Format SFT Prompts (Alpaca style)
&lt;/span&gt;&lt;span class="n"&gt;alpaca_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Below is an instruction that describes a task. Write a response that appropriately completes the request.

### Instruction:
{}

### Response:
{}&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;formatting_prompts_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;examples&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;examples&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instruction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;outputs&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;examples&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alpaca_prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eos_token&lt;/span&gt;
        &lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;texts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Load semantic deduplicated dataset from Part 2
&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temp_cleaned_dataset.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;train&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatting_prompts_func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 4. Setup SFT Trainer
&lt;/span&gt;&lt;span class="n"&gt;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SFTTrainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;train_dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dataset_text_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_seq_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_seq_length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dataset_num_proc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;packing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Set to True to pack short sequences and speed up training
&lt;/span&gt;    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TrainingArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;per_device_train_batch_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;gradient_accumulation_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;warmup_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Number of training steps for test run
&lt;/span&gt;        &lt;span class="n"&gt;learning_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2e-4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;fp16&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_bf16_supported&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;bf16&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_bf16_supported&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;logging_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;optim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adamw_8bit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;weight_decay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;lr_scheduler_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3407&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;output_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputs&lt;/span&gt;&lt;span class="sh"&gt;"&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;# Execute training run
&lt;/span&gt;&lt;span class="n"&gt;trainer_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 5. Save model adapter weights
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lora_model_adapter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lora_model_adapter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Training complete! Model saved.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Merging LoRA Weights for Serving
&lt;/h2&gt;

&lt;p&gt;Fine-tuning via LoRA outputs a directory of adapter weights (typically 50MB - 500MB). To run high-performance inference serving with engines like vLLM, you should merge these adapter matrices back into the 16-bit base model weights.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python Script to Merge Weights:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unsloth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastLanguageModel&lt;/span&gt;

&lt;span class="c1"&gt;# Load the base model and model adapter in native 16-bit
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastLanguageModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta-llama/Meta-Llama-3-8B-Instruct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_seq_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dtype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;load_in_4bit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Must be False to export back to native 16-bit float
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_adapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lora_model_adapter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Execute weights merge and save to disk
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Merging weights and saving to disk...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_pretrained_merged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;merged_model_fp16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;merged_16bit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Merge complete! Ready for vLLM serving.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output in &lt;code&gt;merged_model_fp16&lt;/code&gt; is a standalone 16-bit Hugging Face model directory ready to be loaded by &lt;code&gt;vllm serve&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next Chapter
&lt;/h2&gt;

&lt;p&gt;Supervised Fine-Tuning instructs your model on formatting styles and conversational behavior. However, complex, multi-step logical operations (Reasoning) benefit from structured channelling of reasoning steps.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://tanhdev.com/series/slm-playbook/part-4-knowledge-distillation-r1/" rel="noopener noreferrer"&gt;Part 4: Task &amp;amp; Knowledge Distillation&lt;/a&gt;, we explore how to extract reasoning traces (Chain of Thought - CoT) from larger teacher models like &lt;strong&gt;DeepSeek-R1&lt;/strong&gt; into small student models.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was originally published on my blog at &lt;a href="https://tanhdev.com/series/slm-playbook/part-3-lora-qlora-tuning/" rel="noopener noreferrer"&gt;Practical QLoRA Fine-tuning: Axolotl &amp;amp; Unsloth | SLM Playbook&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/strong&gt; &lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt; &lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>[AI] Context Engineering for AI Coding: AGENTS.md, Cursor Rules &amp; RAG</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 23:42:03 +0000</pubDate>
      <link>https://dev.to/vesviet/ai-context-engineering-for-ai-coding-agentsmd-cursor-rules-rag-17lb</link>
      <guid>https://dev.to/vesviet/ai-context-engineering-for-ai-coding-agentsmd-cursor-rules-rag-17lb</guid>
      <description>&lt;p&gt;In 2025, METR — an AI safety and capability research organization — ran a rigorous randomized controlled trial. Sixteen experienced open-source developers worked on 246 real-world tasks, each randomly assigned to either use AI coding tools freely or not at all.&lt;/p&gt;

&lt;p&gt;The result was counterintuitive: developers using AI tools were &lt;strong&gt;19% slower&lt;/strong&gt; on complex tasks.&lt;/p&gt;

&lt;p&gt;Before the study, those same developers predicted AI would make them &lt;strong&gt;24% faster&lt;/strong&gt;. After completing the experiment — still believing they had gone faster — their subjective confidence remained completely unshaken.&lt;/p&gt;

&lt;p&gt;The finding did not make headlines for the reason people assumed. The headline was not "AI is useless." The headline was this: &lt;strong&gt;the bottleneck is not model quality. It is context quality.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The developers who slowed down were spending significant time on what researchers call "verification overhead" and "workflow friction" — the effort required to correct AI output that did not understand the architectural constraints, naming conventions, existing utility functions, and established patterns of the codebase they were working in. The AI was generating code. It was generating code for an imaginary system.&lt;/p&gt;

&lt;p&gt;This part of the series is about solving that problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series Orientation:&lt;/strong&gt; This article is Part 2 of the &lt;strong&gt;AI Code Review &amp;amp; Vibe Coding&lt;/strong&gt; series, detailing the context engineering practices needed to align AI generation with codebase conventions. For the preceding guide on initial tools and non-technical vibe coding, see &lt;a href="https://dev.to/series/ai-code-review-vibe-coding/part-1-vibe-coding-non-technical/"&gt;Part 1 — Vibe Coding &amp;amp; The Production Wall&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope note:&lt;/strong&gt; This article focuses specifically on &lt;em&gt;code-review-level&lt;/em&gt; context engineering — the practices individual engineers and teams use to make AI agents produce reviewable, architecturally correct code on an existing codebase. If you are interested in &lt;em&gt;platform-level&lt;/em&gt; context infrastructure — building an organizational AI Platform layer, internal RAG systems at scale, or enterprise knowledge management — see &lt;a href="https://dev.to/series/ai-driven-playbook/part-1-context-engineering-ddd/"&gt;Context Engineering: Domain-Driven Design for AI&lt;/a&gt; in the AI-Driven Playbook series.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Fundamental Problem: AI Operates on a Blank Slate
&lt;/h2&gt;

&lt;p&gt;Every time you open a new session with an AI coding tool, you begin from zero. The model knows nothing about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your architectural decisions and why you made them&lt;/li&gt;
&lt;li&gt;Which patterns you have standardized on and which you are migrating away from&lt;/li&gt;
&lt;li&gt;What your team calls a "service" versus a "handler" versus a "controller"&lt;/li&gt;
&lt;li&gt;Which utility functions already exist in your shared library&lt;/li&gt;
&lt;li&gt;What a "successful" PR looks like in your codebase — what passes review and what gets rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this information, the AI operates like a very fast, very confident junior developer who has never seen your codebase before and will reproduce whatever pattern was most common in its training data — not whatever pattern is correct for your system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context engineering&lt;/strong&gt; is the discipline of structuring and delivering organizational knowledge to AI agents in a form they can reliably use. It is, as the industry consensus now describes it, the "DevOps moment" for AI — the operational layer that separates experimental AI assistance from reliable production-grade AI collaboration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Context Hierarchy: From Files to RAG Pipelines
&lt;/h2&gt;

&lt;p&gt;Modern AI coding environments support context at multiple layers. Understanding the hierarchy is the foundation of any effective context strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: Rule Files (Always-On, Zero Overhead)
&lt;/h3&gt;

&lt;p&gt;Rule files are plain-text configuration files that are automatically injected into every AI interaction. They are the most important and most underutilized form of context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AGENTS.md (or CLAUDE.md / GEMINI.md)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These files — stored at the root of your repository — are read by AI agents before they begin any task. They function as the agent's standing orders: architectural constraints, behavioral standards, and explicit prohibitions that apply to everything the agent does.&lt;/p&gt;

&lt;p&gt;A well-structured &lt;code&gt;AGENTS.md&lt;/code&gt; covers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project Architecture&lt;/span&gt;
This is a Kratos v2 microservice using Clean Architecture.
Layer rules:
&lt;span class="p"&gt;-&lt;/span&gt; api/ = contracts only (proto + generated code)
&lt;span class="p"&gt;-&lt;/span&gt; internal/service/ = adapter layer only, no business logic
&lt;span class="p"&gt;-&lt;/span&gt; internal/biz/ = business logic, NO direct database calls
&lt;span class="p"&gt;-&lt;/span&gt; internal/data/ = persistence only, GORM + PostgreSQL

&lt;span class="gh"&gt;# Mandatory Standards&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; All context must propagate through function parameters
&lt;span class="p"&gt;-&lt;/span&gt; Use errgroup for managed goroutines only
&lt;span class="p"&gt;-&lt;/span&gt; SQL queries must use parameterized inputs — NEVER string concatenation
&lt;span class="p"&gt;-&lt;/span&gt; Secrets come from environment variables or Kratos Config — NEVER hardcode

&lt;span class="gh"&gt;# What NOT To Do&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Do not use global state
&lt;span class="p"&gt;-&lt;/span&gt; Do not expose raw database errors to HTTP/gRPC responses
&lt;span class="p"&gt;-&lt;/span&gt; Do not create new patterns without checking internal/util first
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specificity is the point. A generic instruction like "follow clean architecture" produces inconsistent results. A specific instruction like "the biz layer must never import &lt;code&gt;gorm.DB&lt;/code&gt; directly" produces deterministic ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor Rules (&lt;code&gt;.cursorrules&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cursor's rule files work similarly to AGENTS.md but are native to the Cursor IDE. They support scoped rules — you can define different behavior for different file patterns, enforce language-specific standards, and specify which files should never be modified by the AI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[rules]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;Go&lt;/span&gt; &lt;span class="err"&gt;Microservice&lt;/span&gt; &lt;span class="err"&gt;Standards&lt;/span&gt;
&lt;span class="py"&gt;glob&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;**/*.go&lt;/span&gt;

&lt;span class="nn"&gt;[security]&lt;/span&gt;
&lt;span class="py"&gt;never_hardcode_secrets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;require_parameterized_queries&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;forbid_global_state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[architecture]&lt;/span&gt;
&lt;span class="py"&gt;enforce_layer_boundaries&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;require_context_propagation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical effect: your AI assistant now operates with your standards embedded, not as an afterthought you patch into every prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Rule Files Prevent Architectural Leakage (A Go / Kratos Example)
&lt;/h3&gt;

&lt;p&gt;Consider a request: &lt;em&gt;"Retrieve a user profile by email in the service layer."&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Without a Rule File (&lt;code&gt;AGENTS.md&lt;/code&gt;)&lt;/strong&gt;: The AI will write a GORM query directly inside the adapter service layer, bypassing Clean Architecture design:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// File: internal/service/user.go&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetProfileByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProfileReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProfileReply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;biz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;
    &lt;span class="c"&gt;// VIOLATION: Direct database access leaking into the service layer&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="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"email = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&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="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProfileReply&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&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;Email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;With a Rule File (&lt;code&gt;AGENTS.md&lt;/code&gt;)&lt;/strong&gt;: The AI enforces layer isolation, routing GORM access exclusively through the persistence domain (repository) and business use case:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// File: internal/service/user.go&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetProfileByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProfileReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProfileReply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// CORRECT: Service calls the biz layer orchestrator (UseCase)&lt;/span&gt;
    &lt;span class="n"&gt;user&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;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userUseCase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FindByEmail&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;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProfileReply&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&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;Email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Layer 2: Session Management (Active Context Control)
&lt;/h3&gt;

&lt;p&gt;Even with rule files in place, long sessions degrade. This is the "context rot" phenomenon: as a session accumulates failed attempts, corrected errors, and discarded planning notes, the signal-to-noise ratio in the context window drops. The model may prioritize recent noise over foundational constraints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fresh Session Strategy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;High-performing engineering teams treat AI sessions like stateless functions: one distinct task per session. When you complete a bug fix, close the session. When you begin a new feature, open a fresh one. The operational rule: task boundaries are session boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured Handovers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a session grows long before the task is complete, perform a structured handover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask the AI to summarize: "What decisions have we made? What is the current state? What remains to be done?"&lt;/li&gt;
&lt;li&gt;Capture that output in a &lt;code&gt;PLAN.md&lt;/code&gt; or &lt;code&gt;HANDOVER.md&lt;/code&gt; file in your project directory&lt;/li&gt;
&lt;li&gt;Open a fresh session and load the summary alongside your core rule files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This eliminates context rot while preserving all meaningful progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compaction Commands&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modern coding agents (Claude Code, Cursor) include &lt;code&gt;/compact&lt;/code&gt; or &lt;code&gt;/summarize&lt;/code&gt; commands. Use them proactively when a session runs long — before the model hits its context limit and before performance degrades. A compacted summary is a much higher-quality input than an accumulating stream of raw conversation.&lt;/p&gt;




&lt;h3&gt;
  
  
  Layer 3: Repository Indexing (Selective Context Injection)
&lt;/h3&gt;

&lt;p&gt;Rule files establish standards. Session management controls noise. Repository indexing solves a different problem: giving the AI accurate knowledge of what already exists in your codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The N+1 Discovery Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without repository context, AI agents routinely implement functions that already exist. They create new database tables that duplicate existing ones. They define error types that collide with established patterns. They import packages that violate your dependency graph. Not because they are incapable of doing better — because they do not know what already exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual Selection vs. Full-Repo Scanning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most AI coding tools offer the ability to scan an entire repository automatically. This sounds valuable and is often counterproductive. A large codebase injected wholesale into context adds significant noise — irrelevant files, outdated patterns, deprecated modules. The principle: manually select only the files directly relevant to the task.&lt;/p&gt;

&lt;p&gt;For a task modifying user authentication, the relevant context is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The authentication service interface&lt;/li&gt;
&lt;li&gt;The existing user repository implementation&lt;/li&gt;
&lt;li&gt;The session management middleware&lt;/li&gt;
&lt;li&gt;The relevant error type definitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not the entire codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic Memory Banks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;More sophisticated teams maintain curated "memory bank" files — structured markdown documents that describe the codebase's architecture, key patterns, and important decisions in a form optimized for AI consumption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Memory Bank: Authentication Domain&lt;/span&gt;

&lt;span class="gu"&gt;## Architecture&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Auth service handles JWT issuance and validation
&lt;span class="p"&gt;-&lt;/span&gt; User identity stored in PostgreSQL via GORM, users table
&lt;span class="p"&gt;-&lt;/span&gt; Sessions use Redis with 24h TTL (see internal/data/session_repo.go)
&lt;span class="p"&gt;-&lt;/span&gt; MFA implemented via TOTP (internal/service/mfa_service.go)

&lt;span class="gu"&gt;## Key Patterns&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; All auth errors return domain errors, never raw DB errors
&lt;span class="p"&gt;-&lt;/span&gt; Rate limiting is middleware-level (internal/middleware/rate_limiter.go)
&lt;span class="p"&gt;-&lt;/span&gt; Refresh tokens are hashed before storage (see HashToken in internal/util/crypto.go)

&lt;span class="gu"&gt;## Common Mistakes to Avoid&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Do NOT check password directly — always use bcrypt.CompareHashAndPassword
&lt;span class="p"&gt;-&lt;/span&gt; Do NOT log token values — only log token IDs
&lt;span class="p"&gt;-&lt;/span&gt; Do NOT implement new crypto — use internal/util/crypto.go exclusively
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These memory banks are updated when significant architectural decisions are made and committed to the repository alongside code.&lt;/p&gt;




&lt;h3&gt;
  
  
  Layer 4: RAG Pipelines (Enterprise-Scale Context)
&lt;/h3&gt;

&lt;p&gt;For large engineering organizations — those with hundreds of services, mature documentation, and complex architectural standards — static rule files are insufficient. The relevant context for any given task changes too rapidly and exists in too many places to manage manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval-Augmented Generation (RAG)&lt;/strong&gt; for code context works by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Indexing your codebase, Architecture Decision Records (ADRs), runbooks, and internal documentation into a vector database&lt;/li&gt;
&lt;li&gt;When an AI agent begins a task, automatically querying that index for the most semantically relevant context&lt;/li&gt;
&lt;li&gt;Injecting retrieved context into the session alongside the task description&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The operational result: an AI agent working on a payments feature automatically retrieves the relevant payment service interfaces, the ADR explaining why you chose the current transaction model, and the runbook for the payment provider integration — without the engineer manually curating that context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ADRs as Machine-Readable Judgment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Architecture Decision Records deserve special attention. When committed in a structured format and indexed into a RAG pipeline, ADRs transform from static documentation into active constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# ADR-047: Event-Sourcing for Order State Transitions&lt;/span&gt;

&lt;span class="gu"&gt;## Status: Accepted (2025-03)&lt;/span&gt;
&lt;span class="gu"&gt;## Context&lt;/span&gt;
Direct state mutation of order records creates audit trail gaps and makes rollback scenarios complex.
&lt;span class="gu"&gt;## Decision&lt;/span&gt;
All order state transitions are implemented as events, appended to the events table.
The current state is derived by replaying events, not by direct column updates.
&lt;span class="gu"&gt;## Consequences&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; New order state logic MUST add new event types, NOT modify existing ones
&lt;span class="p"&gt;-&lt;/span&gt; Order queries require projection logic (see internal/projection/order_projector.go)
&lt;span class="p"&gt;-&lt;/span&gt; Do NOT write directly to orders.status — always publish an OrderStateTransitioned event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An AI agent with access to this ADR will not generate direct &lt;code&gt;UPDATE orders SET status = ?&lt;/code&gt; queries for order state changes. Without it, it almost certainly will.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP Servers as Context Infrastructure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Model Context Protocol (MCP), released by Anthropic and now adopted across the industry, provides a standardized interface for serving context to AI agents. Rather than building bespoke integrations for each AI tool, organizations build MCP servers — lightweight services that expose specific organizational knowledge (documentation, code patterns, ticket context) through a standard protocol.&lt;/p&gt;

&lt;p&gt;The shift this enables: context infrastructure becomes a shared organizational asset rather than a per-engineer configuration problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ContextOps Discipline
&lt;/h2&gt;

&lt;p&gt;The industry now has a name for operating context infrastructure at organizational scale: &lt;strong&gt;ContextOps&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The operational loop is: &lt;strong&gt;Ingest → Validate → Structure → Serve → Audit → Refine&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ingest&lt;/strong&gt;: Pull from Confluence, Notion, ADR files, runbooks, Slack architectural discussions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate&lt;/strong&gt;: Confirm content is accurate, up-to-date, and not contradictory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt;: Format for AI consumption — clear headers, explicit "do/do not" sections, structured code examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serve&lt;/strong&gt;: Via MCP servers, rule files, or RAG retrieval&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit&lt;/strong&gt;: Monitor whether AI outputs are adhering to the context (if the agent keeps making mistakes the context prohibits, the context is either wrong or unclear)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refine&lt;/strong&gt;: Update context based on what the audit reveals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Organizations that treat context as throwaway configuration — updated ad hoc, inconsistently formatted, stored in unindexed markdown files — experience the METR result: AI that slows teams down. Organizations that treat context as infrastructure — versioned, validated, monitored — experience meaningfully different outcomes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Implementation: Starting From Zero
&lt;/h2&gt;

&lt;p&gt;If your team does not have any context infrastructure today, the practical starting point is a three-step sequence:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Write an AGENTS.md (one afternoon)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Focus on the highest-value content first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your layer structure and the key rules for each layer&lt;/li&gt;
&lt;li&gt;Your top 5 "never do this" patterns (the ones your code reviewers catch most often)&lt;/li&gt;
&lt;li&gt;Your top 5 "always use this instead" patterns (the shared utilities and established conventions)&lt;/li&gt;
&lt;li&gt;Your security non-negotiables (no hardcoded secrets, parameterized queries, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Establish session discipline (one team discussion)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agree on task-based session boundaries. Add a compaction step to your team norms: before any session exceeds 20 substantive exchanges, compact and continue in a fresh session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Build your first memory bank (one sprint)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pick your most critical domain — authentication, payments, whatever carries the highest risk. Document it in a memory bank format. Add a rule to your code review checklist: "Was the relevant memory bank file updated as part of this PR?"&lt;/p&gt;

&lt;p&gt;The marginal improvement from even basic context infrastructure is significant. Teams that complete these three steps report substantially fewer AI-generated PRs that violate architectural standards, require significant rework, or introduce security issues the memory bank explicitly prohibits.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Good Context Engineering Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;Consider a task: "Implement a new endpoint to export user transaction history as a CSV."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without context engineering&lt;/strong&gt;, an AI agent will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new database query that joins &lt;code&gt;users&lt;/code&gt; and &lt;code&gt;transactions&lt;/code&gt; directly in the service layer&lt;/li&gt;
&lt;li&gt;Generate all transactions at once rather than streaming (OOM risk at scale)&lt;/li&gt;
&lt;li&gt;Write the CSV logic inline rather than using your existing &lt;code&gt;internal/util/csv_writer.go&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Skip the rate limiting middleware your architecture requires on all export endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With effective context engineering&lt;/strong&gt;, the same agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Knows that data access must go through the repository layer, not direct DB queries in the service&lt;/li&gt;
&lt;li&gt;Retrieves the existing &lt;code&gt;csv_writer.go&lt;/code&gt; and uses it rather than reimplementing&lt;/li&gt;
&lt;li&gt;Finds the streaming pagination pattern used by &lt;code&gt;internal/service/report_service.go&lt;/code&gt; and applies it&lt;/li&gt;
&lt;li&gt;Applies the export endpoint rate limit from &lt;code&gt;internal/middleware/&lt;/code&gt; as specified in your AGENTS.md&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a different model. It is the same model with correct context. The output difference is substantial.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting to the Review Pipeline
&lt;/h2&gt;

&lt;p&gt;Context engineering is not a replacement for code review. It is a force multiplier on code review. When AI agents operate with accurate, comprehensive context, the output they produce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contains fewer architectural violations (the context prohibits them explicitly)&lt;/li&gt;
&lt;li&gt;Reuses existing utilities more consistently (the context surfaces them)&lt;/li&gt;
&lt;li&gt;Makes security mistakes less frequently (the context specifies the security requirements)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: human reviewers spend less time on pattern violations and architectural corrections, and more time on the genuinely high-value review tasks — logical correctness, edge case handling, and the security behaviors that require judgment rather than rule application.&lt;/p&gt;

&lt;p&gt;Part 3 covers what those high-value review tasks are: the full taxonomy of AI-generated bugs, from the ones automated tools catch to the ones that only careful human review finds.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next: &lt;a href="https://dev.to/series/ai-code-review-vibe-coding/part-3-ai-bug-taxonomy/"&gt;Part 3 — AI Bug Taxonomy: From Silent Logic Failures to Slopsquatting&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was originally published on my blog at &lt;a href="https://tanhdev.com/series/ai-code-review-vibe-coding/part-2-context-engineering-codebase/" rel="noopener noreferrer"&gt;Context Engineering for AI Coding: AGENTS.md, Cursor Rules &amp;amp; RAG&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/strong&gt; &lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt; &lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>productivity</category>
    </item>
    <item>
      <title>[Go] Go pprof in Kubernetes: Remote CPU &amp; Memory Profiling Without Restarting Pods</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Sun, 28 Jun 2026 11:39:07 +0000</pubDate>
      <link>https://dev.to/vesviet/go-go-pprof-in-kubernetes-remote-cpu-memory-profiling-without-restarting-pods-1901</link>
      <guid>https://dev.to/vesviet/go-go-pprof-in-kubernetes-remote-cpu-memory-profiling-without-restarting-pods-1901</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prerequisite:&lt;/strong&gt; This guide covers how to profile and diagnose complex performance issues in production. If you are specifically dealing with unbounded goroutine growth, ensure you first understand the foundational concepts in &lt;a href="https://dev.to/posts/goroutine-leak-detection-production-golang/"&gt;Goroutine Leak Detection and Fix in Production Go Services&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Performance degradation in production is inevitable. When a Go microservice suddenly spikes to 90% CPU utilization or triggers an Out-Of-Memory (OOM) kill in Kubernetes, guessing the root cause by staring at the code is rarely effective. You need data.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;&lt;code&gt;pprof&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Built directly into the Go standard library, &lt;code&gt;pprof&lt;/code&gt; is an incredibly powerful diagnostic tool that samples your application’s execution to identify exactly where CPU time is being spent and where memory is being allocated. While many developers use &lt;code&gt;pprof&lt;/code&gt; locally, doing it safely in a high-throughput production environment requires understanding sampling rates, overhead, and secure exposure.&lt;/p&gt;

&lt;p&gt;This tutorial is a deep-dive into production-ready Go profiling. We will explore how to safely expose endpoints, compare CPU profiling against the Execution Tracer, dissect memory metrics (&lt;code&gt;alloc_space&lt;/code&gt; vs &lt;code&gt;inuse_space&lt;/code&gt;), and leverage advanced features like custom profiling labels and the experimental Go 1.26 goroutine leak profiler.&lt;/p&gt;




&lt;h2&gt;
  
  
  Safely Exposing pprof Endpoints in Production
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Answer-first:&lt;/strong&gt; Go pprof is the standard library profiling tool for diagnosing CPU usage, memory allocation, and goroutine leaks in production Go services, with safe exposure via internal HTTP endpoints and minimal performance overhead when configured correctly.&lt;/p&gt;

&lt;p&gt;The most common way to enable profiling is to import the &lt;code&gt;net/http/pprof&lt;/code&gt; package. As a side effect of the import, this package automatically registers its HTTP handlers to the default &lt;code&gt;http.DefaultServeMux&lt;/code&gt;.&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="c"&gt;// Exposing pprof safely on an internal port&lt;/span&gt;
&lt;span class="c"&gt;// Purpose: Starts an isolated HTTP server dedicated to pprof endpoints&lt;/span&gt;
&lt;span class="c"&gt;// ensuring that diagnostic data is not exposed to the public internet.&lt;/span&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;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"net/http/pprof"&lt;/span&gt; &lt;span class="c"&gt;// Automatically registers /debug/pprof/&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="c"&gt;// ... your main application logic ...&lt;/span&gt;

    &lt;span class="c"&gt;// Run pprof in a background goroutine on a completely separate,&lt;/span&gt;
    &lt;span class="c"&gt;// internal-only port (e.g., blocked by your VPC or Ingress rules).&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting pprof server on localhost:6060"&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="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost:6060"&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="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="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pprof server failed: %v"&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="p"&gt;}()&lt;/span&gt;

    &lt;span class="c"&gt;// block forever or wait for graceful shutdown&lt;/span&gt;
    &lt;span class="k"&gt;select&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;h3&gt;
  
  
  Production Security and Overhead
&lt;/h3&gt;

&lt;p&gt;Never expose &lt;code&gt;/debug/pprof/&lt;/code&gt; to the public internet. Exposing it can lead to information disclosure (revealing your source code structure) and Denial of Service (DoS) if an attacker repeatedly triggers expensive CPU profiles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it safe to run in production?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Heap (Memory) Profiling:&lt;/strong&gt; Extremely safe. It runs continuously by default with negligible overhead (statistically sampling 1 in every 512 KB allocated).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU Profiling:&lt;/strong&gt; Safe for short bursts. Running a 30-second CPU profile samples the stack at 100Hz and generally adds less than 2% overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block &amp;amp; Mutex Profiling:&lt;/strong&gt; Disabled by default. Setting their rates to &lt;code&gt;1&lt;/code&gt; (capturing every event) can add 5–20% overhead. Use them surgically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once exposed, you can capture a profile using the &lt;code&gt;go tool pprof&lt;/code&gt; command from your local machine (via port-forwarding):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Capture a 30-second CPU profile and open the interactive web UI&lt;/span&gt;
go tool pprof &lt;span class="nt"&gt;-http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;:8080 http://localhost:6060/debug/pprof/profile?seconds&lt;span class="o"&gt;=&lt;/span&gt;30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Profiling Go Applications in Kubernetes (Without Restarting Pods)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The core challenge:&lt;/strong&gt; pprof endpoints run inside a Kubernetes pod on &lt;code&gt;localhost:6060&lt;/code&gt;. To reach them from your machine, you cannot connect directly — you need &lt;code&gt;kubectl port-forward&lt;/code&gt; to bridge the network. No pod restart required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Find the Pod Name
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List pods and find the one you want to profile&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; production &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;orders-service

&lt;span class="c"&gt;# Example output:&lt;/span&gt;
&lt;span class="c"&gt;# orders-service-7d9f4b8c6-xk9pz   1/1   Running   0   3h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 — Open a Port-Forward Tunnel
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Forward pod port 6060 to your local machine&lt;/span&gt;
&lt;span class="c"&gt;# This does NOT restart the pod or affect production traffic&lt;/span&gt;
kubectl port-forward pod/orders-service-7d9f4b8c6-xk9pz 6060:6060 &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# Keep this terminal open. In a second terminal:&lt;/span&gt;
go tool pprof &lt;span class="nt"&gt;-http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;:8080 http://localhost:6060/debug/pprof/profile?seconds&lt;span class="o"&gt;=&lt;/span&gt;30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; &lt;code&gt;port-forward&lt;/code&gt; uses an authenticated Kubernetes API server tunnel. No inbound rule change is needed. Never add a &lt;code&gt;NodePort&lt;/code&gt; or &lt;code&gt;LoadBalancer&lt;/code&gt; service just to expose pprof — that is a critical security mistake.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3 — Lock Down the pprof Port with a NetworkPolicy
&lt;/h3&gt;

&lt;p&gt;Even on &lt;code&gt;localhost:6060&lt;/code&gt;, other pods in the same namespace can reach the pprof port via pod-to-pod networking. Add a Kubernetes &lt;code&gt;NetworkPolicy&lt;/code&gt; to restrict access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Allow pprof access only from pods with the "monitoring" label&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NetworkPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restrict-pprof-access&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders-service&lt;/span&gt;
  &lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;       &lt;span class="c1"&gt;# only observability pods may reach pprof&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6060&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4 — Automate Profile Capture on OOM or High CPU
&lt;/h3&gt;

&lt;p&gt;For recurring issues (OOM kills, CPU spikes at 03:00), manually running &lt;code&gt;kubectl port-forward&lt;/code&gt; is too slow. The open-source &lt;strong&gt;pprof-operator&lt;/strong&gt; watches for threshold-based alerts and automatically captures profiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install pprof-operator (Kubernetes Operator)&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/josepdcs/kubectl-prof/releases/latest/download/install.yaml

&lt;span class="c"&gt;# Trigger a CPU profile remotely without port-forward:&lt;/span&gt;
kubectl prof orders-service-7d9f4b8c6-xk9pz &lt;span class="nt"&gt;--lang&lt;/span&gt; go &lt;span class="nt"&gt;--type&lt;/span&gt; cpu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the production pattern used by teams running Go at scale on Kubernetes — profile on-demand, no pod disruption, no always-on overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  CPU Profiling vs. Execution Tracer (trace)
&lt;/h2&gt;

&lt;p&gt;When a service is slow, the first instinct is to pull a CPU profile. But CPU profiles only tell you what the CPU is &lt;em&gt;actively doing&lt;/em&gt;. If your service is slow because it is &lt;em&gt;waiting&lt;/em&gt; (e.g., waiting for a database lock, blocked on channel I/O, or paused by the Garbage Collector), the CPU profile will look surprisingly empty.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to use &lt;code&gt;pprof&lt;/code&gt; (CPU Profile)
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;pprof&lt;/code&gt; when you have &lt;strong&gt;High CPU Utilization&lt;/strong&gt;. It identifies "hot paths"—the loops, expensive algorithms, or massive JSON decoding blocks that are burning through clock cycles.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to use &lt;code&gt;go tool trace&lt;/code&gt; (Execution Tracer)
&lt;/h3&gt;

&lt;p&gt;Use the tracer when you have &lt;strong&gt;High Latency but Low CPU Utilization&lt;/strong&gt;. &lt;br&gt;
The tracer hooks directly into the Go runtime and records an event log of every goroutine scheduling decision, syscall, and garbage collection pause.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Capture a 5-second trace&lt;/span&gt;
curl &lt;span class="nt"&gt;-o&lt;/span&gt; trace.out http://localhost:6060/debug/pprof/trace?seconds&lt;span class="o"&gt;=&lt;/span&gt;5

&lt;span class="c"&gt;# View the trace in the browser&lt;/span&gt;
go tool trace trace.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Overhead Warning:&lt;/strong&gt; The Execution Tracer is heavy. It generates massive files and can introduce 10–20% performance overhead. Do not run it continuously; use it for brief 1–5 second windows when actively debugging a latency spike.&lt;/p&gt;




&lt;h2&gt;
  
  
  Memory Profiling: alloc_space vs inuse_space
&lt;/h2&gt;

&lt;p&gt;Understanding the difference between allocation and retention is the biggest hurdle for engineers learning &lt;code&gt;pprof&lt;/code&gt;. The &lt;code&gt;heap&lt;/code&gt; profile tracks two fundamentally different metrics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;inuse_space&lt;/code&gt; (Retention):&lt;/strong&gt; The amount of memory currently held by your application and not yet garbage collected. If this number climbs infinitely, you have a &lt;strong&gt;Memory Leak&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;alloc_space&lt;/code&gt; (Allocation Churn):&lt;/strong&gt; The total amount of memory ever allocated over the lifetime of the program, even if it was immediately garbage collected. If this number is astronomically high, you have &lt;strong&gt;High GC Pressure&lt;/strong&gt;, which consumes CPU cycles to constantly clean up short-lived objects.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Debugging Workflow
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario A: The OOM Killer (Finding Leaks)&lt;/strong&gt;&lt;br&gt;
If Kubernetes is killing your pod for exceeding memory limits, you want to look at &lt;code&gt;inuse_space&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Focus explicitly on retained memory&lt;/span&gt;
go tool pprof &lt;span class="nt"&gt;-inuse_space&lt;/span&gt; http://localhost:6060/debug/pprof/heap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the interactive UI, type &lt;code&gt;top&lt;/code&gt; to see the functions holding onto the most memory. Often, memory leaks in Go are actually &lt;strong&gt;goroutine leaks&lt;/strong&gt;—a goroutine is blocked forever on a channel, keeping all of its local variables alive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario B: Optimizing CPU through Memory (Fixing Churn)&lt;/strong&gt;&lt;br&gt;
If your CPU usage is high, but the CPU profile shows &lt;code&gt;runtime.mallocgc&lt;/code&gt; at the top, your program is spending all its time allocating and collecting memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Focus explicitly on historical allocation volume&lt;/span&gt;
go tool pprof &lt;span class="nt"&gt;-alloc_space&lt;/span&gt; http://localhost:6060/debug/pprof/allocs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To fix this, you optimize by reducing allocations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pre-allocate slices:&lt;/strong&gt; &lt;code&gt;make([]int, 0, expectedCapacity)&lt;/code&gt; prevents multiple underlying array re-allocations as the slice grows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;sync.Pool&lt;/code&gt;:&lt;/strong&gt; Cache and reuse temporary objects (like &lt;code&gt;bytes.Buffer&lt;/code&gt; or JSON encoders) to completely bypass the GC.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Finding Goroutine Leaks (and Go 1.26 Features)
&lt;/h2&gt;

&lt;p&gt;A standard way to check for goroutine leaks is to compare the baseline number of goroutines against the current number. If it steadily grows from 100 to 10,000 without traffic increasing, you have a leak.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:6060/debug/pprof/goroutine?debug&lt;span class="o"&gt;=&lt;/span&gt;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"goroutine profile: total"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Go 1.26 &lt;code&gt;goroutineleak&lt;/code&gt; Profile (Experimental)
&lt;/h3&gt;

&lt;p&gt;Historically, finding &lt;em&gt;which&lt;/em&gt; of the 10,000 goroutines was leaked required manual inspection of stack traces. Go 1.26 introduces a revolutionary experimental profile: &lt;code&gt;/debug/pprof/goroutineleak&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This profile leverages the Garbage Collector's reachability analysis. It mathematically proves whether a goroutine blocked on a channel or mutex can &lt;em&gt;ever&lt;/em&gt; be unblocked. If the synchronization primitive it is waiting on is unreachable by any active, runnable code, the runtime flags the goroutine as permanently leaked.&lt;/p&gt;

&lt;p&gt;To use it in Go 1.26, you must compile your service with the experiment flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEXPERIMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;goroutineleakprofile go build &lt;span class="nt"&gt;-o&lt;/span&gt; myapp main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, simply curl the endpoint to get a precise list of deadlocked, leaked goroutines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go tool pprof http://localhost:6060/debug/pprof/goroutineleak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Advanced: Custom Profiling Labels with pprof.Do
&lt;/h2&gt;

&lt;p&gt;In a massive multi-tenant microservice, looking at a generic CPU profile is often unhelpful. You might see &lt;code&gt;json.Unmarshal&lt;/code&gt; taking 40% of the CPU, but you don't know &lt;em&gt;which&lt;/em&gt; API route or &lt;em&gt;which&lt;/em&gt; tenant is triggering it.&lt;/p&gt;

&lt;p&gt;Go supports &lt;strong&gt;Custom Profiling Labels&lt;/strong&gt;, allowing you to attach arbitrary key-value pairs to the execution context.&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="c"&gt;// Tagging goroutines with custom pprof labels&lt;/span&gt;
&lt;span class="c"&gt;// Purpose: Allows filtering CPU and allocation profiles by tenant or HTTP route&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;handlers&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;"runtime/pprof"&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;ProcessOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenantID&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;route&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// 1. Create a LabelSet (must be key-value pairs)&lt;/span&gt;
    &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pprof&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Labels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tenant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenantID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// 2. Wrap the execution block with pprof.Do&lt;/span&gt;
    &lt;span class="c"&gt;// Any CPU samples or allocations collected inside this closure &lt;/span&gt;
    &lt;span class="c"&gt;// will be permanently tagged with these labels.&lt;/span&gt;
    &lt;span class="n"&gt;pprof&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Expensive processing goes here...&lt;/span&gt;
        &lt;span class="n"&gt;decodeHeavyPayload&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;When you download the profile, you can open the Web UI (&lt;code&gt;go tool pprof -http=:8080 profile.out&lt;/code&gt;) and use the &lt;strong&gt;Focus&lt;/strong&gt; menu to filter by &lt;code&gt;tenant=xyz&lt;/code&gt;. The Flame Graph will instantly redraw to show only the CPU cycles consumed by that specific tenant!&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions (FAQ)
&lt;/h2&gt;

&lt;p&gt;{{&amp;lt; faq q="What is the performance overhead of Go pprof?" &amp;gt;}}&lt;br&gt;
Heap profiling uses probabilistic sampling (default &lt;code&gt;runtime.MemProfileRate&lt;/code&gt; is 512 KB) and is practically free (&amp;lt; 1% overhead). CPU profiling (100Hz sampling) is also very lightweight (&amp;lt; 2%). However, setting Block or Mutex profile rates to capture 100% of events can add 5-20% overhead. Execution tracing (&lt;code&gt;go tool trace&lt;/code&gt;) is the heaviest, adding 10-20% overhead while actively running.&lt;br&gt;
{{&amp;lt; /faq &amp;gt;}}&lt;/p&gt;

&lt;p&gt;{{&amp;lt; faq q="When should I use go tool trace instead of pprof?" &amp;gt;}}&lt;br&gt;
Use &lt;code&gt;pprof&lt;/code&gt; to find functions actively burning CPU or allocating memory. Use &lt;code&gt;go tool trace&lt;/code&gt; when you need to diagnose latency spikes, scheduler delays, or lock contention where the CPU is mostly idle but requests are taking too long to complete.&lt;br&gt;
{{&amp;lt; /faq &amp;gt;}}&lt;/p&gt;

&lt;p&gt;{{&amp;lt; faq q="How do I profile mutex contention in Go?" &amp;gt;}}&lt;br&gt;
First, enable it in your application code via &lt;code&gt;runtime.SetMutexProfileFraction(100)&lt;/code&gt; (which samples 1% of contention events). Then, access the data via &lt;code&gt;go tool pprof http://localhost:6060/debug/pprof/mutex&lt;/code&gt;. Look for functions waiting the longest for a &lt;code&gt;sync.Mutex&lt;/code&gt; to unlock.&lt;br&gt;
{{&amp;lt; /faq &amp;gt;}}&lt;/p&gt;

&lt;p&gt;{{&amp;lt; faq q="What's the difference between alloc_space and inuse_space?" &amp;gt;}}&lt;br&gt;
&lt;code&gt;inuse_space&lt;/code&gt; measures the memory currently held by the application (useful for finding memory leaks), whereas &lt;code&gt;alloc_space&lt;/code&gt; measures the total memory allocated over the program's lifetime (useful for finding high garbage collection pressure).&lt;br&gt;
{{&amp;lt; /faq &amp;gt;}}&lt;/p&gt;

&lt;p&gt;{{&amp;lt; faq q="How do you enable the Go 1.26 goroutine leak profiler?" &amp;gt;}}&lt;br&gt;
You must compile your service with the experiment flag: &lt;code&gt;GOEXPERIMENT=goroutineleakprofile go build -o myapp main.go&lt;/code&gt;. After that, you can fetch the profile via &lt;code&gt;/debug/pprof/goroutineleak&lt;/code&gt;.&lt;br&gt;
{{&amp;lt; /faq &amp;gt;}}&lt;/p&gt;




&lt;p&gt;🔗 &lt;strong&gt;Related Reading:&lt;/strong&gt; Profiling tells you &lt;em&gt;why&lt;/em&gt; a function is slow, but detecting goroutine growth early is the first line of defence. Read the companion guide &lt;a href="https://dev.to/posts/goroutine-leak-detection-production-golang/"&gt;Goroutine Leak Detection and Fix in Production Go Services&lt;/a&gt; for a deep-dive into goroutine lifecycle management. For distributing observability across your entire microservices fleet, see &lt;a href="https://dev.to/posts/mastering-event-driven-architecture-dapr/"&gt;Mastering Event-Driven Architecture with Dapr&lt;/a&gt; which covers tracing, retry, and DLQ patterns end-to-end.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; author-cta &amp;gt;}}&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was originally published on my blog at &lt;a href="https://tanhdev.com/posts/golang-pprof-profiling-memory-cpu-tutorial/" rel="noopener noreferrer"&gt;Go pprof in Kubernetes: Remote CPU &amp;amp; Memory Profiling Without Restarting Pods&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/strong&gt; &lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt; &lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>go</category>
      <category>performance</category>
      <category>profiling</category>
    </item>
    <item>
      <title>[System Design] Chapter 3: Traffic Shield - Peak Shaving with Kafka and Graceful Degradation</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Sat, 27 Jun 2026 13:41:12 +0000</pubDate>
      <link>https://dev.to/vesviet/system-design-chapter-3-traffic-shield-peak-shaving-with-kafka-and-graceful-degradation-28g4</link>
      <guid>https://dev.to/vesviet/system-design-chapter-3-traffic-shield-peak-shaving-with-kafka-and-graceful-degradation-28g4</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/series/shopee-architecture/"&gt;← Series hub&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev.to/series/shopee-architecture/02-flash-sale-engine/"&gt;← Prev&lt;/a&gt; • &lt;a href="https://dev.to/series/shopee-architecture/04-database-scale/"&gt;Next →&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Chapter 3: Peak Shaving - The Power of Apache Kafka and Graceful Degradation
&lt;/h1&gt;

&lt;p&gt;In Chapter 2, we utilized Redis to deduct inventory in a fraction of a millisecond. However, the purchase journey isn't over. The system still needs to: Create the order record in MySQL, generate an invoice, deduct money from ShopeePay, calculate shipping, and award Shopee Coins.&lt;/p&gt;

&lt;p&gt;If we attempt to perform all these steps &lt;strong&gt;Synchronously&lt;/strong&gt; while the user waits, the system will collapse due to database lock timeouts or slow third-party API responses. The secret is: &lt;strong&gt;Asynchronous Processing&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Peak Shaving with Apache Kafka
&lt;/h2&gt;

&lt;p&gt;The core philosophy of Flash Sale design is: &lt;strong&gt;Accept requests blazingly fast, process them slowly&lt;/strong&gt;. Shopee uses &lt;strong&gt;Apache Kafka&lt;/strong&gt;—a massive, high-throughput message broker—as a massive buffer funnel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once Redis successfully deducts inventory, a lightweight message stating "User A ordered an iPhone" is pushed into a Kafka Topic.&lt;/li&gt;
&lt;li&gt;The system immediately returns a success response to the app: "You are in queue. Your order is being processed." The user experience takes just milliseconds.&lt;/li&gt;
&lt;li&gt;Behind the scenes, Backend Workers slowly pull messages from Kafka and insert them into the actual Database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Result:&lt;/strong&gt; Even if a spike of 1 million orders arrives in a single second, it won't crash the Database. The 1 million messages are safely stored in Kafka. If the workers process at 10,000 orders/second, the backlog is cleared in 100 seconds. The massive traffic spike has been successfully "shaved" into a flat, manageable horizontal line.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph LR
    subgraph Traffic Storm
        Users((Millions of Users)) --&amp;gt;|1 Million Req/s| Checkout[Checkout Service]
    end

    Checkout --&amp;gt;|Write| Kafka[(Apache Kafka&amp;lt;br/&amp;gt;Message Broker)]

    subgraph Async Processing
        Kafka --&amp;gt;|Pull at 10k/s| Worker1[Order Worker]
        Kafka --&amp;gt;|Pull at 10k/s| Worker2[Payment Worker]
        Worker1 --&amp;gt; DB[(MySQL / TiDB)]
        Worker2 --&amp;gt; API[External APIs]
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Eventual Consistency
&lt;/h2&gt;

&lt;p&gt;Shopee embraces the philosophy of &lt;strong&gt;Eventual Consistency&lt;/strong&gt; for distributed systems. Do not attempt to force 100% Strong Consistency across all microservices instantly. There will be a slight delay from the moment you tap "Buy" to the moment the invoice fully appears in your "To Ship" tab. This minor time trade-off is the key to preserving the high availability of the entire e-commerce platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Graceful Degradation
&lt;/h2&gt;

&lt;p&gt;During the midnight rush of 11.11, Shopee enforces a strict policy: &lt;strong&gt;Protect the Core Flow at all costs&lt;/strong&gt; (Search -&amp;gt; Add to Cart -&amp;gt; Checkout). Everything else can die, but the checkout system must survive!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Circuit Breakers:&lt;/strong&gt; When an internal system (e.g., the Promotions Service) becomes overloaded and slow, a Circuit Breaker (like Hystrix or Sentinel) automatically trips. It severs the connection to the failing service for a set duration, instantly returning a default fallback response. This prevents slow services from creating cascading timeouts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature Toggling:&lt;/strong&gt; Engineers pre-configure "switches" in their centralized configuration system. When traffic crosses a critical threshold, the system automatically degrades the user experience by turning off non-essential, heavy features:

&lt;ul&gt;
&lt;li&gt;Disabling historical purchase viewing.&lt;/li&gt;
&lt;li&gt;Hiding Seller analytics dashboards.&lt;/li&gt;
&lt;li&gt;Pausing heavy AI Recommendation engines.&lt;/li&gt;
&lt;li&gt;Disabling avatar updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Developer Takeaway:&lt;/strong&gt; Message Queues (Kafka/RabbitMQ) are the key to decoupling monolithic processes into independent pipelines. In high-concurrency design, you must embrace trade-offs: Be willing to sacrifice auxiliary features to keep the primary money-making flow alive.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; author-cta &amp;gt;}}&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was originally published on my blog at &lt;a href="https://tanhdev.com/series/shopee-architecture/03-traffic-shield/" rel="noopener noreferrer"&gt;Chapter 3: Traffic Shield - Peak Shaving with Kafka and Graceful Degradation&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/strong&gt; &lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt; &lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>ratelimiting</category>
      <category>go</category>
    </item>
    <item>
      <title>Escaping the Monolith: How We Replaced Magento with 21 Go Microservices</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/vesviet/escaping-the-monolith-how-we-replaced-magento-with-21-go-microservices-30je</link>
      <guid>https://dev.to/vesviet/escaping-the-monolith-how-we-replaced-magento-with-21-go-microservices-30je</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Answer-first:&lt;/strong&gt; How we escaped Magento's licensing and scaling walls by migrating to a Composable Commerce Platform built on 21 Go microservices, Dapr PubSub, and a 3-phase Strangler Fig strategy without dropping a single order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At exactly midnight during a major campaign, a monolithic Magento server can quickly become your single point of failure. Every engineering team that builds seriously on Magento eventually hits the same walls: the licensing wall ($100k-$200k/year for Enterprise), the scaling wall (scaling the entire massive codebase just to handle a traffic spike on the checkout page), and the developer velocity wall.&lt;/p&gt;

&lt;p&gt;When we decided to migrate away from Magento, the standard industry advice was to split the monolith into "4 to 6 microservices". We ignored that advice. For serious e-commerce at scale, that approach inevitably leads to a distributed monolith — where services are deployed separately but remain tightly coupled through HTTP chains or shared database tables.&lt;/p&gt;

&lt;p&gt;Instead, we built a Composable Commerce Platform using 21 Go microservices, Google's Kratos v2 framework, and Dapr PubSub. Here is the blueprint of how we structured it, and how we migrated with zero downtime.&lt;/p&gt;

&lt;p&gt;For the complete architecture deep-dive across all domains, see the &lt;a href="https://tanhdev.com/series/composable-commerce-migration/" rel="noopener noreferrer"&gt;Composable Commerce Migration Series&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 21-Service Blueprint: Domain-Driven Design in Practice
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of Go code, we established boundaries using Domain-Driven Design (DDD). Every domain owns its own PostgreSQL database. There are absolutely no cross-domain queries.&lt;/p&gt;

&lt;p&gt;Here is the breakdown of our 6 core domains:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Commerce Flow (Checkout, Order, Payment):&lt;/strong&gt; The highly critical money path, orchestrated using Saga patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product &amp;amp; Content (Catalog, Pricing, Promotion, Search):&lt;/strong&gt; Read-heavy, heavily cached, requiring sub-50ms latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logistics (Warehouse, Fulfillment, Shipping):&lt;/strong&gt; Integration with the physical world and 3PLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-Purchase (Returns, Loyalty):&lt;/strong&gt; Customer retention and post-sale state machines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity &amp;amp; Access (Auth, User, Customer):&lt;/strong&gt; Strict separation between internal staff (RBAC) and external customer PII.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform Operations (Gateway, Analytics, Notification):&lt;/strong&gt; Shared infrastructure utilities.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Here is how traffic flows through the ecosystem. &lt;em&gt;(For the full traffic anatomy and Elasticsearch CQRS implementation, see the &lt;a href="https://tanhdev.com/posts/blueprint-ecommerce-microservices-architecture-diagram/" rel="noopener noreferrer"&gt;Architecture Blueprint&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 Rules of Decoupling: Avoiding the Distributed Monolith
&lt;/h2&gt;

&lt;p&gt;The most common failure mode when migrating from a monolith is building a system where a single failure in a non-critical service cascades and takes down the checkout flow. We avoided this with three hard rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No cross-domain database queries.&lt;/strong&gt; If the Order service needs product data, it cannot query the Catalog database. It must either hold a denormalized copy (via CQRS) or call the Catalog service's gRPC API. This ensures schema changes in one domain never break another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No synchronous calls in the async event path.&lt;/strong&gt; Once an event (e.g., &lt;code&gt;order.paid&lt;/code&gt;) enters the Dapr PubSub mesh, it is processed independently. Downstream services like Fulfillment or Loyalty react to the event, but they never synchronously call back into the producer. If the Loyalty service is down, the order still succeeds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No shared deployment pipelines.&lt;/strong&gt; Every service lives in a Rush monorepo but has its own ArgoCD Application and container registry path. A bug or a blocked deployment in the Notification service cannot block a hotfix for the Checkout service.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Zero-Downtime Migration Strategy: The 3-Phase Strangler Fig
&lt;/h2&gt;

&lt;p&gt;Magento's EAV (Entity-Attribute-Value) schema is a trap. You cannot just run an ETL job over the weekend and cut over traffic. We used a strict 3-Phase Strangler Fig approach to ensure zero dropped orders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1 (Read-Only via CDC):&lt;/strong&gt; We deployed the Go microservices in read-only mode behind our API Gateway. We used Debezium to stream Magento MySQL changes to our Go services via Dapr. Writes still went to Magento.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2 (Dual-Write &amp;amp; Conflict Resolution):&lt;/strong&gt; Microservices started accepting writes, persisting to their own Postgres DBs, and publishing domain events. A sync-adapter service listened to these events and wrote back to Magento. Conflicts were resolved by explicit policies (e.g., timestamp-wins).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 3 (Full Cutover via GitOps):&lt;/strong&gt; Using ArgoCD, we gradually shifted traffic per service: 25% → 50% → 75% → 100%. Magento was kept on "hot standby" for a 30-day rollback window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Establish boundaries before writing code. Extract services based on business domains (DDD) and data ownership, not UI pages.&lt;/li&gt;
&lt;li&gt;Enforce the DB-per-service rule strictly. &lt;/li&gt;
&lt;li&gt;CDC is your best friend during migration. &lt;/li&gt;
&lt;li&gt;Do not do a "big bang" release. &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why Go (Golang) instead of Node.js or Java for microservices?
&lt;/h3&gt;

&lt;p&gt;Go provides the perfect balance for e-commerce microservices: it compiles to a single static binary (fast startup for scaling), uses very little memory compared to JVM languages, and has excellent concurrency primitives (goroutines) for handling high-throughput API gateways and event consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you handle distributed transactions across 21 services?
&lt;/h3&gt;

&lt;p&gt;We don't do distributed locks or 2PC (Two-Phase Commit). We use the Saga Pattern orchestrated by the Checkout service, leveraging Dapr PubSub. If a step fails, the orchestrator fires compensating events to roll back previous steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the API Gateway become a new monolith?
&lt;/h3&gt;

&lt;p&gt;No. The gateway only handles Auth, Rate Limiting, and basic routing. We strictly forbid placing business logic in the API Gateway. It acts purely as a traffic shield and BFF (Backend-For-Frontend).&lt;/p&gt;

&lt;p&gt;For the full engineering blueprint — including the exact EAV extraction SQL and our Dapr Saga implementation — read the complete &lt;a href="https://tanhdev.com/series/composable-commerce-migration/" rel="noopener noreferrer"&gt;Composable Commerce Migration Series&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: How do you migrate from Magento to microservices without downtime?&lt;/strong&gt;&lt;br&gt;
A: The safest path is the &lt;strong&gt;3-Phase Strangler Fig pattern&lt;/strong&gt;: Phase 1 deploys new microservices alongside Magento in read-only mode — reads hit the new services, writes still go to Magento, with Debezium CDC syncing Magento's MySQL binlog to the new services in real time. Phase 2 gradually migrates write APIs (starting with lower-risk domains like Customer, then Catalog, then Order), using bidirectional Dapr Pub/Sub sync to keep Magento's legacy Fulfillment module in sync. Phase 3 cuts all traffic to microservices but keeps Magento as a hot standby with reverse sync for 30 days before termination. Each phase includes a feature flag for sub-10-second rollback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is Debezium and why is it used in Magento migration?&lt;/strong&gt;&lt;br&gt;
A: &lt;strong&gt;Debezium&lt;/strong&gt; is a Change Data Capture (CDC) tool that streams MySQL binary log (binlog) events to a message broker in real time. In a Magento migration, it solves the data consistency problem during the transition period: instead of batch ETL jobs that create race conditions, Debezium captures every INSERT, UPDATE, and DELETE from Magento's MySQL database the moment it happens and publishes it to Dapr Pub/Sub. The new microservices subscribe to these events and keep their own databases synchronized. This creates a continuous, event-driven data bridge between the legacy system and the new architecture with no polling loops or cron jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How do you handle Magento's integer IDs vs UUIDs in microservices migration?&lt;/strong&gt;&lt;br&gt;
A: Magento uses sequential integer &lt;code&gt;entity_id&lt;/code&gt; values as primary keys across all tables. Modern distributed microservices use UUIDs to avoid ID collisions across independent databases and services. The solution is a &lt;strong&gt;&lt;code&gt;magento_id_map&lt;/code&gt; cross-reference table&lt;/strong&gt; maintained during the migration period: every Magento integer ID is mapped to a generated UUID before insertion into the new service's database. All new writes from microservices generate UUIDs directly. The Legacy Sync Worker that writes microservice events back into Magento performs the reverse lookup — UUID to integer — when creating records in Magento's EAV schema. This mapping table is the source of truth during dual-write and is retired after the hot standby period ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is bidirectional sync in a microservices migration?&lt;/strong&gt;&lt;br&gt;
A: &lt;strong&gt;Bidirectional sync&lt;/strong&gt; is the dual-write pattern used during Phase 2 of the migration when both Magento and the new microservices are simultaneously handling writes. When a microservice (e.g., Order Service) processes a transaction, it writes an &lt;code&gt;order.created&lt;/code&gt; event to its outbox table in the same database transaction. A &lt;strong&gt;Legacy Sync Worker&lt;/strong&gt; consumes this event from the Dapr Event Mesh and writes it backward into Magento's database, translating modern payloads back into Magento's EAV schema format. Conflict resolution uses timestamp precedence — the newest write wins. This bidirectional sync allows legacy modules still running inside Magento (e.g., Fulfillment) to remain functional while the migration completes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt;&lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>go</category>
      <category>microservices</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>[System Design] Chapter 2: Flash Sale Engine - Solving Overselling and Hot Keys</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Tue, 23 Jun 2026 23:38:50 +0000</pubDate>
      <link>https://dev.to/vesviet/system-design-chapter-2-flash-sale-engine-solving-overselling-and-hot-keys-3pb9</link>
      <guid>https://dev.to/vesviet/system-design-chapter-2-flash-sale-engine-solving-overselling-and-hot-keys-3pb9</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/series/shopee-architecture/"&gt;← Series hub&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev.to/series/shopee-architecture/01-microservices-foundation/"&gt;← Prev&lt;/a&gt; • &lt;a href="https://dev.to/series/shopee-architecture/03-traffic-shield/"&gt;Next →&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Chapter 2: Flash Sale Engine - The Mystery Behind Redis and Hot Keys
&lt;/h1&gt;

&lt;p&gt;Flash Sale events are the ultimate stress test for system architecture. When an iPhone is sold for $1, millions of users will smash the "Buy Now" button in the exact same millisecond. If this massive spike hits a MySQL database directly, the system will instantly crash due to Row Locks and Deadlocks. &lt;/p&gt;
&lt;h2&gt;
  
  
  1. The Hot Key Problem and Two-Tier Caching
&lt;/h2&gt;

&lt;p&gt;A highly discounted product is known as a &lt;strong&gt;Hot Key&lt;/strong&gt;.&lt;br&gt;
Many developers mistakenly believe that "just putting inventory in Redis" solves everything. However, a single Redis node has Network Bandwidth and CPU limits (typically maxing out at ~100k Ops/sec). One million clicks on a single key will saturate the network interface card (NIC) of that Redis node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shopee's Solution: Multi-Level Caching&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tier 1 (Local Cache):&lt;/strong&gt; Built directly into the RAM of the Golang Application Servers (using tools like &lt;code&gt;sync.Map&lt;/code&gt; or &lt;code&gt;BigCache&lt;/code&gt;). This local cache only stores a boolean flag: "Is the item still in stock?". It has a TTL of just 1-2 seconds but successfully blocks 90% of useless traffic from hitting the network once the item is sold out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 2 (Distributed Cache - Redis):&lt;/strong&gt; Only when the Local Cache reports that the item is available does the request proceed to the Redis cluster.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2. Preventing Overselling with Atomic Lua Scripts
&lt;/h2&gt;

&lt;p&gt;When a user buys an item, the system must deduct the inventory. But if you use standard commands: Read stock (GET) -&amp;gt; Check if &amp;gt; 0 -&amp;gt; Write new stock (SET), you will face a critical &lt;strong&gt;Race Condition&lt;/strong&gt;. Two parallel threads might both read a stock value of 1, both decrement it, and result in selling two items when only one existed (Overselling).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt; Shopee wraps the inventory deduction logic inside &lt;strong&gt;Lua Scripts&lt;/strong&gt; running natively within Redis. Because Redis is fundamentally single-threaded, executing a Lua script acts as an &lt;strong&gt;Atomic Transaction&lt;/strong&gt;—no other requests can interrupt it mid-execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Example Lua Script for Inventory Deduction&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;stock_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stock_key&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;stock&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DECR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stock_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;-- Purchase Successful&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;-- Out of Stock&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fail Fast:&lt;/strong&gt; Thanks to this mechanism, if the Lua script returns 0, the request is immediately rejected and the user sees an "Out of Stock" message. This RAM-level operation takes mere microseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Inventory Sharding
&lt;/h2&gt;

&lt;p&gt;For mega-campaigns, a single Hot Key on a single Redis Node is still too risky. Shopee employs &lt;strong&gt;Inventory Sharding&lt;/strong&gt;.&lt;br&gt;
If there are 1,000 iPhones, they do not store the number 1,000 in a single key &lt;code&gt;iphone_stock&lt;/code&gt;. Instead, they slice it into 10 shards: &lt;code&gt;iphone_stock_1&lt;/code&gt; to &lt;code&gt;iphone_stock_10&lt;/code&gt;. Each key holds 100 items and is distributed across 10 different physical Redis Nodes.&lt;/p&gt;

&lt;p&gt;A load balancer or router randomly routes incoming user traffic to one of those 10 keys, instantly dividing the massive system pressure by 10.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant User
    participant App as Golang Server&amp;lt;br/&amp;gt;(Local Cache)
    participant Redis as Redis Cluster&amp;lt;br/&amp;gt;(Sharded)
    participant Worker as Kafka Worker

    User-&amp;gt;&amp;gt;App: Click "Buy Now"
    Note over App: Check Local Cache.&amp;lt;br/&amp;gt;Block if Out of Stock
    App-&amp;gt;&amp;gt;Redis: Route to shard (e.g. stock_3)
    Note over Redis: Execute Atomic Lua Script
    Redis--&amp;gt;&amp;gt;App: If 0: Return Error
    Redis--&amp;gt;&amp;gt;Worker: If 1: Push Order Event to Queue
    Worker--&amp;gt;&amp;gt;User: Process Order Asynchronously
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Developer Takeaway:&lt;/strong&gt; RAM and caching are your strongest weapons against heavy traffic. However, do not blindly rely on a Distributed Cache. Combine it with Local Caches on the App Server to save network bandwidth, and always use Lua Scripts to guarantee data consistency when handling sensitive numbers like inventory or wallet balances.&lt;/p&gt;




&lt;h2&gt;
  
  
  References &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@kiki.syah/inventory-system-design-to-handle-flash-sales-37fc2e8dcffb" rel="noopener noreferrer"&gt;Handling Flash Sales with Redis and Lua (Medium)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@soesah/how-to-handle-flash-sales-using-redis-c02058e0a811" rel="noopener noreferrer"&gt;Solving the Hot Key Problem with Inventory Sharding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://careers.shopee.sg/blog/" rel="noopener noreferrer"&gt;Shopee Engineering Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;{{&amp;lt; author-cta &amp;gt;}}&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was originally published on my blog at &lt;a href="https://tanhdev.com/series/shopee-architecture/02-flash-sale-engine/" rel="noopener noreferrer"&gt;Chapter 2: Flash Sale Engine - Solving Overselling and Hot Keys&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/strong&gt; &lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt; &lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>highconcurrency</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>[System Design] Banking Microservices Architecture: Event Sourcing, CQRS &amp; Saga Patterns for Core Banking</title>
      <dc:creator>Tuấn Anh</dc:creator>
      <pubDate>Sun, 21 Jun 2026 00:28:28 +0000</pubDate>
      <link>https://dev.to/vesviet/system-design-banking-microservices-architecture-event-sourcing-cqrs-saga-patterns-for-core-5720</link>
      <guid>https://dev.to/vesviet/system-design-banking-microservices-architecture-event-sourcing-cqrs-saga-patterns-for-core-5720</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series context (Part 4 of 8):&lt;/strong&gt; This article assumes familiarity with &lt;a href="https://dev.to/series/core-banking-developer/part-3-database-transactions-acid/"&gt;ACID transactions and database concurrency&lt;/a&gt;. Understanding why consistency guarantees are hard at the database layer is essential context before introducing distributed patterns here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Microservices in Banking?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Microservices in banking&lt;/strong&gt; is the architectural pattern where a core banking system is broken into independently deployable, domain-owned services (CIF, Payments, Lending, Notifications) connected by an event bus instead of direct database calls. This replaces monolithic systems like T24 or Flexcube — where a single change to the Payments module requires redeploying the entire application and risks taking down unrelated services.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High-risk deployments:&lt;/strong&gt; Modifying a small module requires redeploying the entire system. A patch to the Payments module can take down CIF.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inefficient scaling:&lt;/strong&gt; You cannot scale just the Payments module during peak loads without scaling everything else — including parts that don't need more capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technology lock-in:&lt;/strong&gt; Bound to a single programming language and database. Adding a modern ML risk engine becomes an 18-month integration project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The current trend is transitioning to Headless Core Banking&lt;/strong&gt; — decoupling the domain logic from the delivery channels (Mobile App, Internet Banking, ATM) using a banking microservices architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Overall Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────────────────────────┐
  CHANNELS          │  Mobile App  │  Internet Banking  │  ATM/POS  │
                    └──────────────────────┬──────────────────────────┘
                                           │ REST/gRPC
                    ┌──────────────────────▼──────────────────────────┐
  API GATEWAY       │         API Gateway (Auth, Rate Limit, Routing)  │
                    └──────────────────────┬──────────────────────────┘
                                           │
         ┌─────────────────────────────────┼──────────────────────────┐
         │                                 │                          │
  ┌──────▼──────┐               ┌──────────▼─────────┐    ┌──────────▼──────────┐
  │ CIF Service │               │  Account Service   │    │ Payment Service     │
  │ (Customer)  │               │  (CASA, GL)        │    │ (Transfers, Fees)   │
  └─────────────┘               └────────────────────┘    └─────────────────────┘
         │                                 │                          │
         └─────────────────────────────────┼──────────────────────────┘
                                           │ Events (Kafka/Dapr)
                    ┌──────────────────────▼──────────────────────────┐
  EVENT BUS         │              Message Broker (Kafka / Redis)      │
                    └──────────────────────┬──────────────────────────┘
                                           │
         ┌─────────────────────────────────┼──────────────────────────┐
         │                                 │                          │
  ┌──────▼──────┐               ┌──────────▼─────────┐    ┌──────────▼──────────┐
  │ Loan Service│               │ Notification Svc   │    │ Reporting Service   │
  │ (Lending)   │               │ (SMS, Push, Email) │    │ (CQRS Read Side)    │
  └─────────────┘               └────────────────────┘    └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Pattern 1: Event Sourcing for the Ledger
&lt;/h2&gt;

&lt;p&gt;In traditional architectures, we store the &lt;strong&gt;current state&lt;/strong&gt;. In Event Sourcing, we store a &lt;strong&gt;sequence of immutable events&lt;/strong&gt; that produce that state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Event Sourcing fit Core Banking?
&lt;/h3&gt;

&lt;p&gt;The ledger is already essentially Event Sourcing — every entry is an immutable event. The current balance is simply the result of &lt;strong&gt;replaying&lt;/strong&gt; all entries from the beginning.&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="c"&gt;// Events in the Account domain&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AccountOpened&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AccountID&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;CIFNumber&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Currency&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;OpenedAt&lt;/span&gt;     &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MoneyDeposited&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AccountID&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Amount&lt;/span&gt;        &lt;span class="kt"&gt;int64&lt;/span&gt;
    &lt;span class="n"&gt;TransactionID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;OccurredAt&lt;/span&gt;    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MoneyWithdrawn&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AccountID&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Amount&lt;/span&gt;        &lt;span class="kt"&gt;int64&lt;/span&gt;
    &lt;span class="n"&gt;TransactionID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;OccurredAt&lt;/span&gt;    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Calculate balance by replaying events&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;calculateBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;MoneyDeposited&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="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;MoneyWithdrawn&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="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Pattern 2: CQRS — Command Query Responsibility Segregation
&lt;/h2&gt;

&lt;p&gt;Core Banking has a unique characteristic: &lt;strong&gt;writes must be exceptionally robust (ACID)&lt;/strong&gt; but &lt;strong&gt;reads need to be lightning fast&lt;/strong&gt; (dashboards, reports). CQRS completely separates these two flows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WRITE SIDE (Command)                READ SIDE (Query)
────────────────────────            ──────────────────────────
POST /transfers            →        Materialized Views
POST /accounts             →        Elasticsearch Index
PUT /loans/repay           →        Redis Cache

↓ Event Published ↓                ↑ Subscribe &amp;amp; Update ↑
         └──────────────────────────┘
              (Event Bus / Kafka)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real-world Example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write Side:&lt;/strong&gt; Processes transfers using PostgreSQL with full ACID compliance, guaranteeing money isn't lost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read Side:&lt;/strong&gt; Dashboards display transaction history from Elasticsearch — ultra-fast queries, full-text search, and multi-condition filtering.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Pattern 3: Saga — Distributed Transactions Across Services
&lt;/h2&gt;

&lt;p&gt;When a cross-bank transfer requires coordinating 3 services: &lt;strong&gt;Account Service&lt;/strong&gt; (deduct money), &lt;strong&gt;Payment Service&lt;/strong&gt; (send to clearing house), and &lt;strong&gt;Notification Service&lt;/strong&gt; (send SMS), how do you ensure integrity?&lt;/p&gt;

&lt;h3&gt;
  
  
  Choreography Saga (Event-Driven)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Account Service                Payment Service           Notification Service
      │                               │                          │
      │── TransferInitiated ──────────▶│                          │
      │                               │── PaymentSubmitted ──────▶│
      │                               │                          │── SMS Sent
      │◀── PaymentCompleted ──────────│                          │
      │                               │                          │
   (release hold)                                            (done)

If Payment fails:
      │◀── PaymentFailed ─────────────│
      │                               │
   (cancel hold, refund)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Outbox Pattern — Guaranteeing Events are Never Lost
&lt;/h3&gt;

&lt;p&gt;Problem: What if a service successfully commits to the database but fails to publish the event to Kafka?&lt;/p&gt;

&lt;p&gt;Solution: &lt;strong&gt;Write the event to the database within the same transaction, then have a separate worker read and publish it to Kafka.&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;-- Outbox table: written in the same transaction as business data&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;outbox_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;          &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;topic&lt;/span&gt;       &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- 'account.transfer.completed'&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;     &lt;span class="n"&gt;JSONB&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;      &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'PENDING'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt;  &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Inside the same Database Transaction:&lt;/span&gt;
&lt;span class="c1"&gt;-- 1. Update account balance&lt;/span&gt;
&lt;span class="c1"&gt;-- 2. Write ledger entries  &lt;/span&gt;
&lt;span class="c1"&gt;-- 3. INSERT into outbox_events&lt;/span&gt;

&lt;span class="c1"&gt;-- Separate worker running periodically:&lt;/span&gt;
&lt;span class="c1"&gt;-- SELECT * FROM outbox_events WHERE status = 'PENDING'&lt;/span&gt;
&lt;span class="c1"&gt;-- → Publish to Kafka&lt;/span&gt;
&lt;span class="c1"&gt;-- → UPDATE status = 'PUBLISHED'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  API Design for Financial Transactions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Design Principles
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stateless APIs:&lt;/strong&gt; Every request must contain all necessary information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mandatory Idempotency headers&lt;/strong&gt; for all state-changing APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict separation between Request (commands) and Status (polling).&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /v1/transfers                    → Initiate transfer command
  Header: Idempotency-Key: &amp;lt;uuid&amp;gt;
  Body: { from, to, amount, currency }
  Response: { transfer_id, status: "PROCESSING" }

GET  /v1/transfers/{transfer_id}      → Check result
  Response: { status: "COMPLETED" | "FAILED", ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never design a transfer API as a &lt;strong&gt;synchronous block&lt;/strong&gt; because processing through a central clearing network (like SWIFT) can take anywhere from seconds to minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Stack Selection
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Popular Choices&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go (Kratos, Fiber), Java (Spring Boot)&lt;/td&gt;
&lt;td&gt;High performance, type-safe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Strong ACID, flexible JSONB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Balances, sessions, rate limiting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event Bus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apache Kafka, Dapr PubSub&lt;/td&gt;
&lt;td&gt;Durable, ordered, replayable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Mesh&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Istio, Dapr&lt;/td&gt;
&lt;td&gt;mTLS, circuit breaking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Orchestration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;Auto-scaling, self-healing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  References &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://microservices.io/" rel="noopener noreferrer"&gt;Microservices Patterns: Saga and Transactional Outbox (Chris Richardson)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mambu.com/composable-banking" rel="noopener noreferrer"&gt;Mambu: Composable Banking Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtmachine.net/vault-core" rel="noopener noreferrer"&gt;Thought Machine: Vault Core Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/cqrs.html" rel="noopener noreferrer"&gt;Martin Fowler: Event Sourcing &amp;amp; CQRS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 &lt;strong&gt;Previous Step:&lt;/strong&gt; Explore the foundational database layer in &lt;a href="https://dev.to/series/core-banking-developer/part-3-database-transactions-acid/"&gt;Part 3 — Database Design for Financial Transactions (ACID &amp;amp; Concurrency)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Next Step:&lt;/strong&gt; Now that you understand banking microservices architecture and its event-driven patterns, see how these services communicate with the outside world through international financial standards. Continue reading &lt;a href="https://dev.to/series/core-banking-developer/part-5-iso-standards-integration/"&gt;Part 5 — International Integration Standards: ISO 8583 &amp;amp; ISO 20022&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Deep Dive:&lt;/strong&gt; For a complete engineering guide to the full composable banking stack — ledger concurrency patterns, Strangler Fig migrations, RFC 8705 mTLS, and the next-gen vendor landscape — see &lt;a href="https://dev.to/posts/composable-banking-architecture"&gt;Composable Banking Architecture: From Monolith to Modular Core&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was originally published on my blog at &lt;a href="https://tanhdev.com/series/core-banking-developer/part-4-modern-core-banking-architecture/" rel="noopener noreferrer"&gt;Banking Microservices Architecture: Event Sourcing, CQRS &amp;amp; Saga Patterns for Core Banking&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hi, I'm Lê Tuấn Anh (vesviet) 👋&lt;/strong&gt; &lt;br&gt;
&lt;em&gt;I am a Senior Go Backend Architect &amp;amp; Distributed Systems Engineer with 17+ years of experience building high-traffic platforms (25M+ requests/month).&lt;/em&gt; &lt;br&gt;
&lt;em&gt;If you enjoyed this deep-dive, let's connect on &lt;a href="https://www.linkedin.com/in/vesviet" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore my consulting services at &lt;a href="https://tanhdev.com/hire" rel="noopener noreferrer"&gt;tanhdev.com/hire&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>fintech</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
