<?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: Manohar Halappa</title>
    <description>The latest articles on DEV Community by Manohar Halappa (@manoharhalappa).</description>
    <link>https://dev.to/manoharhalappa</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%2F4133819%2F0fdfd53b-295f-4e57-9609-7e0bb33f0993.jpg</url>
      <title>DEV Community: Manohar Halappa</title>
      <link>https://dev.to/manoharhalappa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/manoharhalappa"/>
    <language>en</language>
    <item>
      <title>Architecting for AI-Native Platforms: RAG, LLM Orchestration, and Agentic Patterns</title>
      <dc:creator>Manohar Halappa</dc:creator>
      <pubDate>Sun, 20 Sep 2026 08:12:06 +0000</pubDate>
      <link>https://dev.to/manoharhalappa/architecting-for-ai-native-platforms-rag-llm-orchestration-and-agentic-patterns-2ffj</link>
      <guid>https://dev.to/manoharhalappa/architecting-for-ai-native-platforms-rag-llm-orchestration-and-agentic-patterns-2ffj</guid>
      <description>&lt;p&gt;AI adoption in an enterprise SaaS platform is rarely about adding an LLM API and calling it done.&lt;/p&gt;

&lt;p&gt;The difficult part is integrating AI into an existing platform &lt;strong&gt;without weakening the properties that made the platform trustworthy in the first place&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A mature SaaS platform already has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain models&lt;/li&gt;
&lt;li&gt;Authentication and authorization&lt;/li&gt;
&lt;li&gt;Tenant isolation&lt;/li&gt;
&lt;li&gt;Data governance&lt;/li&gt;
&lt;li&gt;Audit trails&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;Event-driven workflows&lt;/li&gt;
&lt;li&gt;Operational controls&lt;/li&gt;
&lt;li&gt;Reliability mechanisms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI should not create a parallel architecture that bypasses these capabilities.&lt;/p&gt;

&lt;p&gt;It should &lt;strong&gt;inherit them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's the architectural principle I use when thinking about evolving a mature SaaS platform toward AI-native capabilities.&lt;/p&gt;




&lt;h1&gt;
  
  
  AI Should Be an Augmentation Layer, Not a Parallel Platform
&lt;/h1&gt;

&lt;p&gt;The first architectural decision is where AI belongs.&lt;/p&gt;

&lt;p&gt;A tempting approach looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existing Platform
       │
       └──────► AI Platform
                    │
                    ├── Own data
                    ├── Own permissions
                    ├── Own workflows
                    └── Own state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a dangerous divergence.&lt;/p&gt;

&lt;p&gt;Now there are effectively two systems that understand the business.&lt;/p&gt;

&lt;p&gt;The better model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  ┌─────────────────────┐
                  │   AI Capabilities   │
                  │                     │
                  │ RAG / LLM / Agents  │
                  └──────────┬──────────┘
                             │
                       Platform APIs
                             │
                  ┌──────────▼──────────┐
                  │  Canonical Domain   │
                  │       Model         │
                  └──────────┬──────────┘
                             │
                  ┌──────────▼──────────┐
                  │   Core Platform     │
                  │                     │
                  │ Auth / Tenancy /    │
                  │ Data / Audit / APIs │
                  └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core platform remains the source of truth.&lt;/p&gt;

&lt;p&gt;AI becomes another consumer and orchestrator of platform capabilities.&lt;/p&gt;

&lt;p&gt;This distinction becomes increasingly important as AI moves from simply generating answers to &lt;strong&gt;taking actions&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Pattern 1: RAG — Give the Model the Right Context
&lt;/h1&gt;

&lt;p&gt;Large language models are powerful, but they don't automatically know your organization's current data.&lt;/p&gt;

&lt;p&gt;For enterprise applications, the challenge is therefore often less:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which model should we use?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and more:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"How do we reliably provide the right context to the model?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's where Retrieval-Augmented Generation (RAG) becomes useful.&lt;/p&gt;

&lt;p&gt;A simplified RAG pipeline looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Documents / Domain Data
          │
          ▼
       Chunking
          │
          ▼
      Embedding
          │
          ▼
      Vector Index
          │
          │
      ┌───▼────┐
      │ Query  │
      └───┬────┘
          │
          ▼
      Retrieval
          │
          ▼
   Relevant Context
          │
          ▼
        LLM
          │
          ▼
       Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model isn't expected to remember everything.&lt;/p&gt;

&lt;p&gt;The application retrieves relevant information and supplies it as context.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Vector Pipeline Is the Foundation
&lt;/h2&gt;

&lt;p&gt;If you're building multiple AI features, one of the first reusable platform capabilities should be the vector pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ingest
  ↓
Normalize
  ↓
Chunk
  ↓
Embed
  ↓
Index
  ↓
Retrieve
  ↓
Rerank / Filter
  ↓
Generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specific vector technology can change.&lt;/p&gt;

&lt;p&gt;For example, depending on the architecture and requirements, this could involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon OpenSearch&lt;/li&gt;
&lt;li&gt;Pinecone&lt;/li&gt;
&lt;li&gt;Another vector-capable datastore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important architectural decision is to avoid coupling every AI feature directly to the indexing implementation.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    AI Features
                 /      |       \
                /       |        \
             Search   Assistant   Agent
                \       |        /
                 \      |       /
                  ▼     ▼      ▼
                Retrieval API
                     │
                     ▼
               Vector Pipeline
                     │
                     ▼
                Domain Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once retrieval becomes a platform capability, multiple AI features can reuse it.&lt;/p&gt;




&lt;h1&gt;
  
  
  RAG Is More Than Semantic Search
&lt;/h1&gt;

&lt;p&gt;One common mistake is to think of RAG as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Question
   ↓
Vector search
   ↓
Top 5 documents
   ↓
LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production systems usually need more controls.&lt;/p&gt;

&lt;p&gt;The retrieval layer may need to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tenant&lt;/li&gt;
&lt;li&gt;User permissions&lt;/li&gt;
&lt;li&gt;Document type&lt;/li&gt;
&lt;li&gt;Data freshness&lt;/li&gt;
&lt;li&gt;Metadata&lt;/li&gt;
&lt;li&gt;Access policies&lt;/li&gt;
&lt;li&gt;Relevance&lt;/li&gt;
&lt;li&gt;Source authority&lt;/li&gt;
&lt;li&gt;Temporal constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Query
    │
    ▼
Authorization Context
    │
    ▼
Tenant / Scope Filter
    │
    ▼
Semantic Retrieval
    │
    ▼
Metadata / Permission Filtering
    │
    ▼
Relevant Context
    │
    ▼
LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is critical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieving information that the user isn't authorized to access is still a security vulnerability—even if the LLM never intentionally exposes it.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  RAG Needs Freshness and Provenance
&lt;/h1&gt;

&lt;p&gt;Enterprise data changes.&lt;/p&gt;

&lt;p&gt;A vector index can therefore become stale.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source updated
     │
     ▼
Database = current
     │
     └──────► Vector index = old
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system now has two versions of reality.&lt;/p&gt;

&lt;p&gt;That's why a production RAG architecture should think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incremental indexing&lt;/li&gt;
&lt;li&gt;Deletes&lt;/li&gt;
&lt;li&gt;Updates&lt;/li&gt;
&lt;li&gt;Re-indexing&lt;/li&gt;
&lt;li&gt;Document versioning&lt;/li&gt;
&lt;li&gt;Embedding version changes&lt;/li&gt;
&lt;li&gt;Source timestamps&lt;/li&gt;
&lt;li&gt;Provenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful principle is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The vector index is a derived representation, not the source of truth.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That makes lifecycle management much clearer.&lt;/p&gt;




&lt;h1&gt;
  
  
  Pattern 2: LLM Orchestration
&lt;/h1&gt;

&lt;p&gt;Once AI workflows become more sophisticated, a single model invocation isn't enough.&lt;/p&gt;

&lt;p&gt;A real enterprise workflow might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   │
   ▼
Authorize
   │
   ▼
Retrieve
   │
   ▼
Enrich
   │
   ▼
Generate
   │
   ▼
Validate
   │
   ▼
Persist
   │
   ▼
Audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where orchestration becomes important.&lt;/p&gt;

&lt;p&gt;For AWS-based architectures, workflow services such as Step Functions can provide explicit state management around multi-step operations.&lt;/p&gt;

&lt;p&gt;The key architectural idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't hide a distributed workflow inside one giant prompt or Lambda function.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Model the workflow explicitly.&lt;/p&gt;




&lt;h1&gt;
  
  
  Make AI Workflows Observable
&lt;/h1&gt;

&lt;p&gt;Traditional distributed systems already taught us that asynchronous workflows need state.&lt;/p&gt;

&lt;p&gt;AI workflows need the same discipline.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Request → ??? → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    AI Workflow
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
       Retrieve        Generate       Validate
          │              │              │
          └──────────────┼──────────────┘
                         ▼
                       Store
                         │
                         ▼
                       Audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage should have enough metadata to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What was requested?&lt;/li&gt;
&lt;li&gt;Which tenant initiated it?&lt;/li&gt;
&lt;li&gt;Which data sources were consulted?&lt;/li&gt;
&lt;li&gt;Which model/version was used?&lt;/li&gt;
&lt;li&gt;What workflow executed?&lt;/li&gt;
&lt;li&gt;Which tools were invoked?&lt;/li&gt;
&lt;li&gt;What failed?&lt;/li&gt;
&lt;li&gt;What was retried?&lt;/li&gt;
&lt;li&gt;What was ultimately returned?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI systems need &lt;strong&gt;observability at both the application and model layers&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Model Selection Should Be an Architecture Decision
&lt;/h1&gt;

&lt;p&gt;Not every request needs the largest or most expensive model.&lt;/p&gt;

&lt;p&gt;A mature AI platform can route workloads according to their requirements.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     Request
                        │
                        ▼
                  Classify Task
                        │
            ┌───────────┼───────────┐
            ▼           ▼           ▼
          Simple      Complex      Batch
            │           │           │
            ▼           ▼           ▼
        Fast/cheap   Capable LLM  Offline
          model        model       inference
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This introduces another important architectural metric:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cost per successful business outcome&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;rather than simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cost per LLM request.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The cheapest model isn't useful if it produces an answer that requires repeated retries or human correction.&lt;/p&gt;




&lt;h1&gt;
  
  
  Where SageMaker Fits
&lt;/h1&gt;

&lt;p&gt;Not every AI workload is an LLM workflow.&lt;/p&gt;

&lt;p&gt;Traditional machine-learning workloads still matter.&lt;/p&gt;

&lt;p&gt;For use cases involving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model training&lt;/li&gt;
&lt;li&gt;Feature engineering&lt;/li&gt;
&lt;li&gt;Batch inference&lt;/li&gt;
&lt;li&gt;Model evaluation&lt;/li&gt;
&lt;li&gt;MLOps&lt;/li&gt;
&lt;li&gt;Model deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;a platform such as Amazon SageMaker can provide a different execution model.&lt;/p&gt;

&lt;p&gt;A useful architectural separation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   AI Platform
                       │
          ┌────────────┴────────────┐
          │                         │
          ▼                         ▼
   Generative AI               Predictive ML
          │                         │
   LLM / RAG / Agents       Training / Inference
          │                         │
          ▼                         ▼
   Bedrock / LLM stack          SageMaker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to force every AI capability through the same technology.&lt;/p&gt;




&lt;h1&gt;
  
  
  Pattern 3: Agentic Workflows
&lt;/h1&gt;

&lt;p&gt;Agents introduce a fundamentally different capability.&lt;/p&gt;

&lt;p&gt;A traditional application does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
API
 ↓
Business Logic
 ↓
Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent can potentially do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Agent
 ↓
Decide next action
 ↓
Call tool
 ↓
Observe result
 ↓
Decide next action
 ↓
Call another tool
 ↓
Return result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That introduces a new architectural concern:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;bounded autonomy.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Agents Need Explicit Boundaries
&lt;/h1&gt;

&lt;p&gt;An agent should not automatically receive unrestricted access to the platform.&lt;/p&gt;

&lt;p&gt;Instead, define:&lt;/p&gt;

&lt;h3&gt;
  
  
  Scope
&lt;/h3&gt;

&lt;p&gt;What business problem is the agent allowed to solve?&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools
&lt;/h3&gt;

&lt;p&gt;Which APIs or actions can it invoke?&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissions
&lt;/h3&gt;

&lt;p&gt;What can it read?&lt;/p&gt;

&lt;p&gt;What can it modify?&lt;/p&gt;

&lt;h3&gt;
  
  
  Limits
&lt;/h3&gt;

&lt;p&gt;How many actions can it perform?&lt;/p&gt;

&lt;p&gt;How much can it spend?&lt;/p&gt;

&lt;h3&gt;
  
  
  Approval
&lt;/h3&gt;

&lt;p&gt;Which actions require human confirmation?&lt;/p&gt;

&lt;p&gt;A useful mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌───────────────┐
                 │     Agent     │
                 └───────┬───────┘
                         │
                  Policy / IAM
                         │
            ┌────────────┼────────────┐
            ▼            ▼            ▼
        Tool A        Tool B        Tool C
        Read          Read          Write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent doesn't get "platform access."&lt;/p&gt;

&lt;p&gt;It gets &lt;strong&gt;specific capabilities&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Human-in-the-Loop Is a Control, Not a Failure
&lt;/h1&gt;

&lt;p&gt;For high-impact actions, autonomy shouldn't necessarily mean zero human involvement.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent proposes action
        │
        ▼
Policy evaluation
        │
        ├── Low risk ──→ Execute
        │
        └── High risk ─→ Human approval
                              │
                              ▼
                           Execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can the agent do this automatically?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What level of autonomy is appropriate for this action?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the same risk-based thinking we already use in distributed systems and security architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  Audit Every Agent Action
&lt;/h1&gt;

&lt;p&gt;Once an AI system can take actions, logging the final response isn't enough.&lt;/p&gt;

&lt;p&gt;You need to understand the chain of execution.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
  ↓
Agent decision
  ↓
Tool selected
  ↓
Tool parameters
  ↓
Authorization check
  ↓
Tool result
  ↓
Next decision
  ↓
Final action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact implementation will depend on the platform and privacy requirements, but the architectural principle is straightforward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An action taken by an agent should be as auditable as an action taken by a human or traditional service.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This becomes particularly important for regulated or enterprise environments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security Must Flow Through the AI Stack
&lt;/h1&gt;

&lt;p&gt;One of the most dangerous architectural mistakes is treating AI as a separate security domain.&lt;/p&gt;

&lt;p&gt;Your existing platform may already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;RBAC&lt;/li&gt;
&lt;li&gt;ABAC&lt;/li&gt;
&lt;li&gt;Tenant isolation&lt;/li&gt;
&lt;li&gt;IAM&lt;/li&gt;
&lt;li&gt;API authorization&lt;/li&gt;
&lt;li&gt;Audit logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI layer should inherit these controls.&lt;/p&gt;

&lt;p&gt;Consider a multi-tenant SaaS application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 User
                   │
                   ▼
             Authentication
                   │
                   ▼
            Tenant Context
                   │
                   ▼
           Authorization
                   │
             ┌─────┴─────┐
             ▼           ▼
           RAG          Agent
             │           │
             ▼           ▼
          Retrieval     Tools
             │           │
             └─────┬─────┘
                   ▼
              Domain APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI system should not create a backdoor around the authorization model.&lt;/p&gt;

&lt;p&gt;This is especially important for RAG.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tenant isolation must exist in retrieval itself, not merely in the user interface.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Guardrails Are Architecture
&lt;/h1&gt;

&lt;p&gt;AI guardrails shouldn't be an afterthought added after the first production incident.&lt;/p&gt;

&lt;p&gt;They belong in the architecture.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Prompt validation&lt;/li&gt;
&lt;li&gt;Input size limits&lt;/li&gt;
&lt;li&gt;Abuse detection&lt;/li&gt;
&lt;li&gt;Sensitive-data handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Retrieval controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tenant filtering&lt;/li&gt;
&lt;li&gt;Authorization checks&lt;/li&gt;
&lt;li&gt;Source validation&lt;/li&gt;
&lt;li&gt;Freshness requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Output controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Schema validation&lt;/li&gt;
&lt;li&gt;Content validation&lt;/li&gt;
&lt;li&gt;Confidence or quality checks&lt;/li&gt;
&lt;li&gt;Business-rule validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Operational controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Token budgets&lt;/li&gt;
&lt;li&gt;Rate limits&lt;/li&gt;
&lt;li&gt;Model routing&lt;/li&gt;
&lt;li&gt;Timeout limits&lt;/li&gt;
&lt;li&gt;Retry limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful pipeline looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input
  ↓
Validate
  ↓
Authorize
  ↓
Retrieve
  ↓
Generate
  ↓
Validate Output
  ↓
Business Rules
  ↓
Persist / Act
  ↓
Audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM is one component inside the workflow.&lt;/p&gt;

&lt;p&gt;It shouldn't become the workflow itself.&lt;/p&gt;




&lt;h1&gt;
  
  
  Don't Let the LLM Become the Source of Truth
&lt;/h1&gt;

&lt;p&gt;This is perhaps the most important design principle.&lt;/p&gt;

&lt;p&gt;An LLM should generally &lt;strong&gt;reason over authoritative data&lt;/strong&gt;, not replace it.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌───────────────┐
                    │ Source of     │
                    │ Truth         │
                    └───────┬───────┘
                            │
                            ▼
                       AI Context
                            │
                            ▼
                           LLM
                            │
                            ▼
                    Proposed Answer /
                         Action
                            │
                            ▼
                    Domain Validation
                            │
                            ▼
                       Platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model generates a result.&lt;/p&gt;

&lt;p&gt;The platform determines whether that result is valid.&lt;/p&gt;

&lt;p&gt;This distinction becomes critical when AI starts taking actions rather than simply answering questions.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I Tell Engineering Teams
&lt;/h1&gt;

&lt;p&gt;I use a simple rule when reviewing AI architecture:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The AI layer should inherit the trust properties of the core platform.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your platform has strong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multitenancy&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;Auditability&lt;/li&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;li&gt;Idempotency&lt;/li&gt;
&lt;li&gt;Resilience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then AI should inherit those properties.&lt;/p&gt;

&lt;p&gt;If those properties are weak, introducing AI doesn't hide the weakness.&lt;/p&gt;

&lt;p&gt;It can amplify it.&lt;/p&gt;

&lt;p&gt;An AI system that can access ten times more data or execute ten times more actions can turn a small authorization mistake into a much larger incident.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Practical AI-Native Evolution Path
&lt;/h1&gt;

&lt;p&gt;You don't need to build an autonomous agent platform on day one.&lt;/p&gt;

&lt;p&gt;A pragmatic evolution can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Phase 1
Canonical data + APIs
        │
        ▼
Phase 2
RAG / Retrieval
        │
        ▼
Phase 3
LLM-powered workflows
        │
        ▼
Phase 4
Tool-enabled assistants
        │
        ▼
Phase 5
Bounded agentic workflows
        │
        ▼
Phase 6
Selective autonomous actions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each phase builds on the previous one.&lt;/p&gt;

&lt;p&gt;This is important because the hardest part of AI adoption isn't usually the model.&lt;/p&gt;

&lt;p&gt;It's building the &lt;strong&gt;platform capabilities around the model&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  The AI Platform Capabilities I'd Build First
&lt;/h1&gt;

&lt;p&gt;Before investing heavily in autonomous agents, establish the foundations.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Canonical data access
&lt;/h2&gt;

&lt;p&gt;AI should consume well-defined domain APIs and data products.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Retrieval platform
&lt;/h2&gt;

&lt;p&gt;Build reusable ingestion, chunking, embedding, indexing, retrieval, and authorization capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Model gateway
&lt;/h2&gt;

&lt;p&gt;Centralize model access where practical so applications don't each implement their own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Model selection&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Cost tracking&lt;/li&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;li&gt;Safety controls&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Workflow orchestration
&lt;/h2&gt;

&lt;p&gt;Use explicit workflows for multi-step AI operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Tool layer
&lt;/h2&gt;

&lt;p&gt;Expose controlled business capabilities as tools rather than giving agents unrestricted database or infrastructure access.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Evaluation
&lt;/h2&gt;

&lt;p&gt;Build repeatable evaluation datasets and quality metrics.&lt;/p&gt;

&lt;p&gt;An AI feature isn't production-ready simply because it works for ten manually tested prompts.&lt;/p&gt;

&lt;p&gt;You need to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does it work?
How often does it fail?
When does it fail?
Which tenants/data types are affected?
Did a model or prompt change make it worse?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Metrics That Matter
&lt;/h1&gt;

&lt;p&gt;Traditional application metrics aren't enough.&lt;/p&gt;

&lt;p&gt;An AI-native platform should track several dimensions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quality
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Retrieval precision / relevance&lt;/li&gt;
&lt;li&gt;Groundedness&lt;/li&gt;
&lt;li&gt;Task success rate&lt;/li&gt;
&lt;li&gt;Validation failure rate&lt;/li&gt;
&lt;li&gt;Human correction rate&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;End-to-end latency&lt;/li&gt;
&lt;li&gt;Retrieval latency&lt;/li&gt;
&lt;li&gt;Model latency&lt;/li&gt;
&lt;li&gt;Token usage&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reliability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Workflow failures&lt;/li&gt;
&lt;li&gt;Tool failures&lt;/li&gt;
&lt;li&gt;Retry rates&lt;/li&gt;
&lt;li&gt;Timeout rates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Authorization failures&lt;/li&gt;
&lt;li&gt;Cross-tenant retrieval attempts&lt;/li&gt;
&lt;li&gt;Policy violations&lt;/li&gt;
&lt;li&gt;Blocked tool calls&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Economics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Cost per request&lt;/li&gt;
&lt;li&gt;Cost per successful task&lt;/li&gt;
&lt;li&gt;Cost per tenant&lt;/li&gt;
&lt;li&gt;Cost by model&lt;/li&gt;
&lt;li&gt;Cost by workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to optimize &lt;strong&gt;business outcomes&lt;/strong&gt;, not simply model metrics.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Changes When AI Becomes Agentic?
&lt;/h1&gt;

&lt;p&gt;RAG primarily changes how applications &lt;strong&gt;retrieve information&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;LLM orchestration changes how applications &lt;strong&gt;coordinate AI-powered workflows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Agents change how applications &lt;strong&gt;take actions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means the risk profile evolves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAG
 │
 └── Information risk

LLM workflows
 │
 └── Information + workflow risk

Agents
 │
 └── Information + workflow + action risk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more autonomy you introduce, the stronger your controls need to become.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Real Lesson
&lt;/h1&gt;

&lt;p&gt;AI-native architecture isn't about putting an LLM at the center of everything.&lt;/p&gt;

&lt;p&gt;It's about creating a platform where AI capabilities can evolve &lt;strong&gt;without bypassing the engineering disciplines that already protect the business&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The architecture I want looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         AI Applications
                               │
                ┌──────────────┼──────────────┐
                ▼              ▼              ▼
               RAG          Workflows       Agents
                │              │              │
                └──────────────┼──────────────┘
                               ▼
                         AI Platform
                               │
                 ┌─────────────┼─────────────┐
                 ▼             ▼             ▼
             Retrieval      Models         Tools
                 │             │             │
                 └─────────────┼─────────────┘
                               ▼
                        Core Platform
                               │
              ┌────────────────┼────────────────┐
              ▼                ▼                ▼
          Identity         Domain Data       Audit
              │                │                │
              └────────────────┼────────────────┘
                               ▼
                        Source of Truth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to make the platform "AI-powered."&lt;/p&gt;

&lt;p&gt;The goal is to make AI a &lt;strong&gt;first-class capability of the platform without making it a special exception to the platform's rules&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's the difference between adding AI features and building an &lt;strong&gt;AI-native platform&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI should inherit your platform's trust model—not replace it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




</description>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
      <category>rag</category>
    </item>
    <item>
      <title>From Monolith to Serverless: How We Cut Infrastructure Costs by 30–35%:</title>
      <dc:creator>Manohar Halappa</dc:creator>
      <pubDate>Sun, 20 Sep 2026 08:04:06 +0000</pubDate>
      <link>https://dev.to/manoharhalappa/from-monolith-to-serverless-how-we-cut-infrastructure-costs-by-30-35-3pn</link>
      <guid>https://dev.to/manoharhalappa/from-monolith-to-serverless-how-we-cut-infrastructure-costs-by-30-35-3pn</guid>
      <description>&lt;h1&gt;
  
  
  From Monolith to Serverless: How We Cut Infrastructure Costs by 30–35%
&lt;/h1&gt;

&lt;p&gt;Modernizing a legacy monolith is rarely a matter of replacing servers with Lambda.&lt;/p&gt;

&lt;p&gt;The difficult part is deciding &lt;strong&gt;what should become serverless, what should remain where it is, and in what order to make the change without disrupting the business&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In two modernization programs, I found that the biggest gains came from treating modernization as an &lt;strong&gt;architecture and economics problem&lt;/strong&gt;, not simply a technology migration.&lt;/p&gt;

&lt;p&gt;The result was a &lt;strong&gt;30–35% reduction in infrastructure costs&lt;/strong&gt; for the workloads we migrated.&lt;/p&gt;

&lt;p&gt;The interesting part wasn't Lambda itself.&lt;/p&gt;

&lt;p&gt;It was changing the system from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Always running
      ↓
Wait for work
      ↓
Process work
      ↓
Wait again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Event arrives
      ↓
Compute starts
      ↓
Process
      ↓
Compute stops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fundamental shift was from &lt;strong&gt;paying for capacity&lt;/strong&gt; to &lt;strong&gt;paying for useful work&lt;/strong&gt; where the workload characteristics made that model appropriate.&lt;/p&gt;




&lt;h1&gt;
  
  
  Start With Boundaries, Not Code
&lt;/h1&gt;

&lt;p&gt;One of the easiest mistakes in modernization is starting with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which class should we extract first?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's usually the wrong question.&lt;/p&gt;

&lt;p&gt;Start with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Which business capability has a clear boundary and a workload that benefits from independent scaling?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before touching the code, map the existing system.&lt;/p&gt;

&lt;p&gt;Look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request volume&lt;/li&gt;
&lt;li&gt;Traffic patterns&lt;/li&gt;
&lt;li&gt;CPU and memory utilization&lt;/li&gt;
&lt;li&gt;Processing duration&lt;/li&gt;
&lt;li&gt;Dependency relationships&lt;/li&gt;
&lt;li&gt;Database access patterns&lt;/li&gt;
&lt;li&gt;Failure characteristics&lt;/li&gt;
&lt;li&gt;Scaling behavior&lt;/li&gt;
&lt;li&gt;Deployment frequency&lt;/li&gt;
&lt;li&gt;Business criticality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The resulting workload map often looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       Workload Characteristics

              ┌───────────────────────────────────┐
              │ High volume + predictable traffic  │
              │                                   │
              │ Existing service may be suitable │
              │ for optimized container compute  │
              └───────────────────────────────────┘

              ┌───────────────────────────────────┐
              │ Spiky + stateless + short-lived   │
              │                                   │
              │ → Lambda / API Gateway           │
              └───────────────────────────────────┘

              ┌───────────────────────────────────┐
              │ Async + long-running              │
              │                                   │
              │ → SQS + Step Functions + Lambda  │
              └───────────────────────────────────┘

              ┌───────────────────────────────────┐
              │ Large batch / data processing     │
              │                                   │
              │ → S3 + event-driven processing   │
              └───────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This exercise prevents one of the most common modernization mistakes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Forcing every workload into the same architecture.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Serverless is a tool, not an ideology.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Extraction Strategy
&lt;/h1&gt;

&lt;p&gt;Rather than rewriting the monolith, we extracted &lt;strong&gt;specific capabilities&lt;/strong&gt; behind well-defined interfaces.&lt;/p&gt;

&lt;p&gt;A simplified evolution looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    BEFORE

              ┌─────────────────┐
              │                 │
              │    MONOLITH     │
              │                 │
              │  API            │
              │  Business Logic │
              │  Batch Jobs     │
              │  Integrations   │
              │                 │
              └───────┬─────────┘
                      │
                   Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    AFTER

                ┌──────────────┐
                │ API Gateway  │
                └──────┬───────┘
                       │
                  ┌────▼─────┐
                  │  Lambda  │
                  └────┬─────┘
                       │
                       ▼
                ┌─────────────┐
                │  Services   │
                └─────────────┘

 Events ──→ SQS ──→ Lambda ──→ DynamoDB/S3

 Events ──→ Step Functions
                 │
          ┌──────┼──────┐
          ▼      ▼      ▼
       Lambda  Lambda  Lambda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The monolith didn't disappear overnight.&lt;/p&gt;

&lt;p&gt;Instead, capabilities were progressively moved behind explicit boundaries.&lt;/p&gt;

&lt;p&gt;That reduced both technical and organizational risk.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Read-Heavy and Spiky Workloads
&lt;/h1&gt;

&lt;p&gt;The first candidates were workloads with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highly variable traffic&lt;/li&gt;
&lt;li&gt;Short execution times&lt;/li&gt;
&lt;li&gt;Stateless processing&lt;/li&gt;
&lt;li&gt;Clear API boundaries&lt;/li&gt;
&lt;li&gt;Significant idle periods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These workloads are natural candidates for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Gateway → Lambda → downstream service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The economics can be attractive because capacity scales with demand rather than requiring permanently provisioned application servers.&lt;/p&gt;

&lt;p&gt;Consider a simplified workload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traffic

       ▲
       │             ████
       │             ████
       │       ███   ████
       │       ███   ████
       │  ██   ███   ████
       │  ██   ███   ████
       └──────────────────────→ time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With always-on infrastructure, you provision for the peak:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provisioned capacity ─────────────────────
Actual workload        ▂▂▃▂▂▆▃▂▂▂▂▇▂
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gap represents potentially unused capacity.&lt;/p&gt;

&lt;p&gt;With serverless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provisioned capacity
Actual execution      ▂▂▃▂▂▆▃▂▂▂▂▇▂
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The infrastructure model more closely follows actual execution.&lt;/p&gt;

&lt;p&gt;But there is an important qualification:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serverless isn't automatically cheaper.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For continuously high utilization, long-running workloads, or workloads with substantial execution duration, other compute models can be more economical.&lt;/p&gt;

&lt;p&gt;The architecture decision should therefore be based on &lt;strong&gt;workload economics&lt;/strong&gt;, not technology preference.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Replace Polling With Events
&lt;/h1&gt;

&lt;p&gt;One of the biggest opportunities wasn't moving existing code to Lambda.&lt;/p&gt;

&lt;p&gt;It was eliminating unnecessary work.&lt;/p&gt;

&lt;p&gt;A common legacy pattern looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────┐
│ Always-on VM │
└──────┬───────┘
       │
       ▼
   Poll queue
       │
       ├── No work → sleep
       │
       └── Work → process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system was effectively paying for compute even when there was nothing to process.&lt;/p&gt;

&lt;p&gt;We moved appropriate workflows toward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Event
                   │
                   ▼
                  SQS
                   │
                   ▼
                Lambda
                   │
                   ▼
               Processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now compute existed primarily when there was work to perform.&lt;/p&gt;

&lt;p&gt;This wasn't just a cost optimization.&lt;/p&gt;

&lt;p&gt;It also reduced coupling between producers and consumers and made scaling more explicit.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Long-Running Workflows: Step Functions
&lt;/h1&gt;

&lt;p&gt;Not every workflow belongs inside a single Lambda invocation.&lt;/p&gt;

&lt;p&gt;Some operations involve multiple stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetch
  ↓
Validate
  ↓
Transform
  ↓
Persist
  ↓
Notify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting all of that into one function creates problems around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeout limits&lt;/li&gt;
&lt;li&gt;Retry behavior&lt;/li&gt;
&lt;li&gt;Partial failure&lt;/li&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;li&gt;Recovery&lt;/li&gt;
&lt;li&gt;Operational visibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, we modeled long-running workflows explicitly.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             ┌────────────┐
             │   Start    │
             └─────┬──────┘
                   ▼
             ┌────────────┐
             │   Fetch    │
             └─────┬──────┘
                   ▼
             ┌────────────┐
             │  Validate  │
             └─────┬──────┘
                   ▼
             ┌────────────┐
             │ Transform  │
             └─────┬──────┘
                   ▼
             ┌────────────┐
             │   Persist  │
             └─────┬──────┘
                   ▼
             ┌────────────┐
             │  Complete  │
             └────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow engine became responsible for orchestration, while individual functions remained focused on individual tasks.&lt;/p&gt;

&lt;p&gt;This made failures easier to reason about.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;Transform&lt;/code&gt; fails, the workflow knows exactly where it failed and what retry policy applies.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Bulk Data Processing
&lt;/h1&gt;

&lt;p&gt;Large data operations required a different approach.&lt;/p&gt;

&lt;p&gt;Instead of treating a massive batch as one application process, we used object storage and event-driven processing where appropriate.&lt;/p&gt;

&lt;p&gt;A typical pattern was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌─────────────┐
                 │     S3      │
                 │ Raw Dataset │
                 └──────┬──────┘
                        │
                     Event
                        │
                        ▼
                 ┌─────────────┐
                 │ Processing  │
                 │   Workers   │
                 └──────┬──────┘
                        │
                        ▼
                 ┌─────────────┐
                 │  Persisted  │
                 │    Data     │
                 └─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates another useful separation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage doesn't need to be coupled to compute.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Data can exist independently of the processing lifecycle.&lt;/p&gt;

&lt;p&gt;That makes retries, replay, and recovery significantly easier to design.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. The Cost Reduction Was a Consequence, Not the Architecture
&lt;/h1&gt;

&lt;p&gt;This is an important distinction.&lt;/p&gt;

&lt;p&gt;We didn't say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Let's move everything to Lambda and save money."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Where are we paying for capacity that isn't being used?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then we targeted those workloads.&lt;/p&gt;

&lt;p&gt;The cost model changed from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Always-on infrastructure
+
Provisioned capacity
+
Idle periods
+
Operational overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;toward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Actual execution
+
Event-driven scaling
+
Managed orchestration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the workloads we migrated, this contributed to a &lt;strong&gt;30–35% infrastructure cost reduction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But the number should not be interpreted as a universal serverless savings percentage.&lt;/p&gt;

&lt;p&gt;The actual economics depend on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invocation frequency&lt;/li&gt;
&lt;li&gt;Execution duration&lt;/li&gt;
&lt;li&gt;Memory allocation&lt;/li&gt;
&lt;li&gt;Concurrency&lt;/li&gt;
&lt;li&gt;Data transfer&lt;/li&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;Database costs&lt;/li&gt;
&lt;li&gt;Observability costs&lt;/li&gt;
&lt;li&gt;Existing infrastructure utilization&lt;/li&gt;
&lt;li&gt;Provisioned versus on-demand capacity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why &lt;strong&gt;cost per business transaction&lt;/strong&gt; was more useful than looking only at the monthly infrastructure bill.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Guardrails Before Migration
&lt;/h1&gt;

&lt;p&gt;Moving from a monolith to distributed services increases the number of things that can fail.&lt;/p&gt;

&lt;p&gt;We therefore treated guardrails as part of the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollback-first deployments
&lt;/h2&gt;

&lt;p&gt;Every deployment needed a safe rollback path.&lt;/p&gt;

&lt;p&gt;The question wasn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can we deploy this?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What happens if this deployment is wrong?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A deployment isn't production-ready until the recovery path is understood.&lt;/p&gt;




&lt;h2&gt;
  
  
  Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;We standardized infrastructure using tools such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;AWS CDK&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal wasn't simply automation.&lt;/p&gt;

&lt;p&gt;It was &lt;strong&gt;repeatability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A service shouldn't require an individual engineer to remember a collection of undocumented console steps.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code
  ↓
Pull Request
  ↓
Validation
  ↓
Infrastructure Plan
  ↓
Deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The infrastructure becomes part of the software lifecycle.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Resilience Patterns Belong in the Platform
&lt;/h1&gt;

&lt;p&gt;Once the monolith becomes a collection of distributed services, failure modes multiply.&lt;/p&gt;

&lt;p&gt;We standardized common resilience patterns:&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeouts
&lt;/h3&gt;

&lt;p&gt;Never allow a downstream dependency to block indefinitely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retries
&lt;/h3&gt;

&lt;p&gt;Retry transient failures, but use bounded retries and appropriate backoff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dead-letter queues
&lt;/h3&gt;

&lt;p&gt;Failed asynchronous messages should have somewhere explicit to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Idempotency
&lt;/h3&gt;

&lt;p&gt;A retry should not accidentally create duplicate business operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Every workflow needs enough telemetry to answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What happened?
Where did it fail?
Why did it fail?
What was retried?
What is the current state?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These shouldn't be reinvented independently by every team.&lt;/p&gt;

&lt;p&gt;Where practical, they belong in shared platform capabilities and golden paths.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Part We Almost Got Wrong
&lt;/h1&gt;

&lt;p&gt;One of the biggest lessons from modernization is that &lt;strong&gt;distributed architecture introduces its own tax&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After extracting services, you now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More deployments&lt;/li&gt;
&lt;li&gt;More network calls&lt;/li&gt;
&lt;li&gt;More failure boundaries&lt;/li&gt;
&lt;li&gt;More observability requirements&lt;/li&gt;
&lt;li&gt;More IAM policies&lt;/li&gt;
&lt;li&gt;More infrastructure components&lt;/li&gt;
&lt;li&gt;More operational states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A badly designed microservices architecture can cost more—both financially and organizationally—than the monolith it replaced.&lt;/p&gt;

&lt;p&gt;So the target shouldn't be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Maximum number of microservices."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It should be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"The minimum number of independently scalable and independently deployable boundaries that make business sense."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a very different goal.&lt;/p&gt;




&lt;h1&gt;
  
  
  What We Measured
&lt;/h1&gt;

&lt;p&gt;Cost was only one dimension.&lt;/p&gt;

&lt;p&gt;For every migrated workload, we tracked a combination of financial, operational, and performance metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure cost per month&lt;/li&gt;
&lt;li&gt;Cost per transaction&lt;/li&gt;
&lt;li&gt;Cost per million requests&lt;/li&gt;
&lt;li&gt;Cost per processed workload&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;p50 / p95 / p99 latency&lt;/li&gt;
&lt;li&gt;Cold-start impact where relevant&lt;/li&gt;
&lt;li&gt;Queue processing latency&lt;/li&gt;
&lt;li&gt;Workflow duration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reliability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Error rate&lt;/li&gt;
&lt;li&gt;Retry rate&lt;/li&gt;
&lt;li&gt;Dead-letter volume&lt;/li&gt;
&lt;li&gt;Timeout rate&lt;/li&gt;
&lt;li&gt;Failed workflow executions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Delivery
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deployment frequency&lt;/li&gt;
&lt;li&gt;Deployment duration&lt;/li&gt;
&lt;li&gt;Rollback frequency&lt;/li&gt;
&lt;li&gt;Mean time to recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking at these metrics together prevents a common optimization mistake:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Saving money by making the system slower or less reliable isn't necessarily an improvement.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  The Real Lesson
&lt;/h1&gt;

&lt;p&gt;The most important lesson wasn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Serverless is cheaper."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Don't pay for infrastructure when your architecture doesn't need it."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The architecture evolved from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Servers
   ↓
Processes
   ↓
Polling
   ↓
Work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;toward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Events
   ↓
Managed orchestration
   ↓
On-demand compute
   ↓
Work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The infrastructure became more closely aligned with actual business activity.&lt;/p&gt;

&lt;p&gt;And that is where the savings came from.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Practical Modernization Checklist
&lt;/h1&gt;

&lt;p&gt;If you're starting a similar journey, I'd ask these questions before extracting the first service:&lt;/p&gt;

&lt;h3&gt;
  
  
  Workload
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Is the workload stateless?&lt;/li&gt;
&lt;li&gt;Is traffic spiky?&lt;/li&gt;
&lt;li&gt;Does it have long idle periods?&lt;/li&gt;
&lt;li&gt;Does it have a clear business boundary?&lt;/li&gt;
&lt;li&gt;Can it scale independently?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Data
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Does it have a clear data ownership model?&lt;/li&gt;
&lt;li&gt;Can reads and writes be isolated?&lt;/li&gt;
&lt;li&gt;What happens during partial failure?&lt;/li&gt;
&lt;li&gt;Is the operation idempotent?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reliability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What happens when a dependency times out?&lt;/li&gt;
&lt;li&gt;What happens when a message is processed twice?&lt;/li&gt;
&lt;li&gt;Where do permanently failed messages go?&lt;/li&gt;
&lt;li&gt;How is recovery performed?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Operations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Can the service be deployed independently?&lt;/li&gt;
&lt;li&gt;Can it be rolled back safely?&lt;/li&gt;
&lt;li&gt;Can operators understand its state without reading application logs?&lt;/li&gt;
&lt;li&gt;Is the infrastructure reproducible?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Economics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What is the current utilization?&lt;/li&gt;
&lt;li&gt;What are we paying for when the system is idle?&lt;/li&gt;
&lt;li&gt;What is the expected cost at current and peak volume?&lt;/li&gt;
&lt;li&gt;What additional costs will serverless introduce?&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Thought
&lt;/h1&gt;

&lt;p&gt;Modernization isn't about making a monolith disappear.&lt;/p&gt;

&lt;p&gt;It's about &lt;strong&gt;changing the economics, reliability, and delivery model of the system one boundary at a time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Serverless was valuable because it allowed us to align compute more closely with actual work.&lt;/p&gt;

&lt;p&gt;Event-driven architecture was valuable because it allowed the system to react instead of constantly polling.&lt;/p&gt;

&lt;p&gt;Infrastructure as code was valuable because it made the new architecture repeatable.&lt;/p&gt;

&lt;p&gt;And the 30–35% cost reduction was ultimately a consequence of those architectural decisions—not the reason to make them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The best modernization strategy isn't "move to serverless."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Understand the workload. Find the boundary. Remove unnecessary always-on capacity. Make failure recoverable. Measure the result. Then repeat.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




</description>
      <category>serverless</category>
      <category>aws</category>
      <category>microservices</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Scaling ETL to 25M+ Records Across 120+ School Districts: An Architecture Story</title>
      <dc:creator>Manohar Halappa</dc:creator>
      <pubDate>Sun, 20 Sep 2026 07:44:54 +0000</pubDate>
      <link>https://dev.to/manoharhalappa/scaling-etl-to-25m-records-across-120-school-districts-an-architecture-story-4bdi</link>
      <guid>https://dev.to/manoharhalappa/scaling-etl-to-25m-records-across-120-school-districts-an-architecture-story-4bdi</guid>
      <description>&lt;p&gt;When you're moving a few thousand records, ETL is mostly a data-processing problem.&lt;/p&gt;

&lt;p&gt;When you're moving &lt;strong&gt;25+ million records across 120+ school districts&lt;/strong&gt;, it becomes something else entirely:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A distributed-systems reliability problem where you need to prove that the data moved correctly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In an education platform, this distinction matters. Student, enrollment, attendance, and course data isn't just another dataset. A partial or silently corrupted load can affect downstream analytics, interventions, reporting, and operational decisions.&lt;/p&gt;

&lt;p&gt;This is the story of the architecture and reliability principles we used to approach that problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Moving Millions of Records Without Losing Trust
&lt;/h2&gt;

&lt;p&gt;Our platform needed to ingest data from &lt;strong&gt;120+ school districts&lt;/strong&gt;, each with its own data volume, timing characteristics, and potential failure modes.&lt;/p&gt;

&lt;p&gt;Across a typical sync cycle, the pipeline could process &lt;strong&gt;25M+ records&lt;/strong&gt; spanning areas such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Students&lt;/li&gt;
&lt;li&gt;Enrollments&lt;/li&gt;
&lt;li&gt;Attendance&lt;/li&gt;
&lt;li&gt;Courses&lt;/li&gt;
&lt;li&gt;Sections&lt;/li&gt;
&lt;li&gt;Staff and related relationships&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge wasn't simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can we process 25 million records?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The harder questions were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did we receive everything the source intended to send?&lt;/li&gt;
&lt;li&gt;Did every accepted record get processed?&lt;/li&gt;
&lt;li&gt;What happened when a job failed halfway through?&lt;/li&gt;
&lt;li&gt;Could we safely retry?&lt;/li&gt;
&lt;li&gt;How do we detect partial loads?&lt;/li&gt;
&lt;li&gt;Can we explain exactly what happened to a district's data days or weeks later?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That led us to four primary requirements.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Scale
&lt;/h2&gt;

&lt;p&gt;The pipeline needed to handle &lt;strong&gt;25M+ records per sync cycle&lt;/strong&gt;, while accommodating significant differences between districts.&lt;/p&gt;

&lt;p&gt;One district might have a relatively small dataset.&lt;/p&gt;

&lt;p&gt;Another could generate millions of records.&lt;/p&gt;

&lt;p&gt;That meant designing around &lt;strong&gt;variable workloads&lt;/strong&gt;, rather than assuming every sync would behave the same way.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Correctness
&lt;/h2&gt;

&lt;p&gt;Successful execution isn't the same thing as successful data ingestion.&lt;/p&gt;

&lt;p&gt;A job can return &lt;code&gt;200 OK&lt;/code&gt;, complete without throwing an exception, and still produce an incomplete dataset.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source:  1,250,000 records
Target:  1,247,831 records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From an infrastructure perspective, the job might appear healthy.&lt;/p&gt;

&lt;p&gt;From a data perspective, something went wrong.&lt;/p&gt;

&lt;p&gt;We therefore treated &lt;strong&gt;reconciliation as a first-class part of the pipeline&lt;/strong&gt;, rather than something performed manually after an incident.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Auditability
&lt;/h2&gt;

&lt;p&gt;Education data requires a strong operational audit trail.&lt;/p&gt;

&lt;p&gt;We needed to answer questions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When was this district synchronized?&lt;/p&gt;

&lt;p&gt;How many records did we receive?&lt;/p&gt;

&lt;p&gt;How many were validated?&lt;/p&gt;

&lt;p&gt;How many were successfully processed?&lt;/p&gt;

&lt;p&gt;Were any records rejected?&lt;/p&gt;

&lt;p&gt;Did the job retry?&lt;/p&gt;

&lt;p&gt;Did reconciliation pass?&lt;/p&gt;

&lt;p&gt;If something failed, where did it fail?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This pushed us toward designing observability and auditability into the pipeline rather than bolting them on afterward.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Reliability
&lt;/h2&gt;

&lt;p&gt;At this scale, failures are inevitable.&lt;/p&gt;

&lt;p&gt;Networks fail.&lt;/p&gt;

&lt;p&gt;External SIS systems become unavailable.&lt;/p&gt;

&lt;p&gt;Workers restart.&lt;/p&gt;

&lt;p&gt;Individual batches fail.&lt;/p&gt;

&lt;p&gt;Dependencies time out.&lt;/p&gt;

&lt;p&gt;A downstream service can become temporarily unavailable.&lt;/p&gt;

&lt;p&gt;The architecture therefore had to assume that &lt;strong&gt;partial failure is normal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal wasn't to eliminate failure.&lt;/p&gt;

&lt;p&gt;It was to make failure &lt;strong&gt;safe, detectable, recoverable, and explainable&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Architecture
&lt;/h1&gt;

&lt;p&gt;At a high level, the pipeline looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                ┌─────────────────────┐
                │   School Districts  │
                │      / SIS          │
                └──────────┬──────────┘
                           │
                           ▼
                ┌─────────────────────┐
                │      Ingestion      │
                │ Scheduled / Batch   │
                │      Processing     │
                └──────────┬──────────┘
                           │
                           ▼
                ┌─────────────────────┐
                │     Validation      │
                │ Schema + Integrity  │
                │       Checks        │
                └──────────┬──────────┘
                           │
                           ▼
                ┌─────────────────────┐
                │     Processing      │
                │ Transform + Load    │
                │    Idempotently     │
                └──────────┬──────────┘
                           │
                           ▼
                ┌─────────────────────┐
                │   Reconciliation    │
                │ Source vs. Target   │
                │       Counts        │
                └──────────┬──────────┘
                           │
                           ▼
                ┌─────────────────────┐
                │   Observability &amp;amp;   │
                │       Audit         │
                └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part isn't any individual technology.&lt;/p&gt;

&lt;p&gt;It's the &lt;strong&gt;control points between the stages&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Idempotent Ingestion
&lt;/h1&gt;

&lt;p&gt;The first principle we adopted was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Assume every operation can be retried.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A network timeout doesn't necessarily mean that the server didn't process the request.&lt;/p&gt;

&lt;p&gt;A worker can crash after writing data but before acknowledging completion.&lt;/p&gt;

&lt;p&gt;A scheduler can trigger the same operation more than once.&lt;/p&gt;

&lt;p&gt;If the pipeline isn't idempotent, retries can turn transient failures into permanent data corruption.&lt;/p&gt;

&lt;p&gt;We therefore designed operations around stable identifiers and idempotency keys.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;record + operation + source version
                │
                ▼
        deterministic identity
                │
                ▼
        idempotent processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A retry should result in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First attempt  → write
Second attempt → recognize existing operation
Third attempt  → same final state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First attempt  → write
Second attempt → duplicate write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This became one of the most important design principles in the system.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Validate Before Processing
&lt;/h1&gt;

&lt;p&gt;We didn't want malformed data to travel deep into the pipeline before being discovered.&lt;/p&gt;

&lt;p&gt;Validation happened as early as practical.&lt;/p&gt;

&lt;p&gt;Typical validation categories included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema validation&lt;/li&gt;
&lt;li&gt;Required-field validation&lt;/li&gt;
&lt;li&gt;Referential integrity&lt;/li&gt;
&lt;li&gt;Data-type validation&lt;/li&gt;
&lt;li&gt;Source-specific business rules&lt;/li&gt;
&lt;li&gt;Duplicate detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This created a useful separation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw input
   │
   ▼
Validation
   │
   ├── Invalid → rejected / dead-letter path
   │
   ▼
Valid records
   │
   ▼
Processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation also improved troubleshooting.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Why did this record disappear?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;we could ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Was this record rejected during validation, or was it accepted and subsequently failed during processing?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction is extremely valuable in production.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Process in Batches, Not One Giant Transaction
&lt;/h1&gt;

&lt;p&gt;With tens of millions of records, treating an entire sync as one atomic operation is usually impractical.&lt;/p&gt;

&lt;p&gt;Instead, we divided workloads into manageable units.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;25M records
    │
    ├── Batch 1
    ├── Batch 2
    ├── Batch 3
    ├── ...
    └── Batch N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides several advantages:&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure isolation
&lt;/h3&gt;

&lt;p&gt;If one batch fails, we don't necessarily need to restart the entire sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retryability
&lt;/h3&gt;

&lt;p&gt;Failed batches can be retried independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallelism
&lt;/h3&gt;

&lt;p&gt;Where appropriate, independent batches can be processed concurrently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operational visibility
&lt;/h3&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sync = FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can reason about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,248 batches completed
12 batches retried
2 batches failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much more actionable.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Reconciliation: The Most Important Control
&lt;/h1&gt;

&lt;p&gt;One of the biggest lessons from operating high-volume data pipelines is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A successful job does not prove a successful data load.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's why reconciliation became an explicit stage.&lt;/p&gt;

&lt;p&gt;At the end of a load, we compare source and target measurements.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Source       Target
Students            1,250,000    1,250,000
Enrollments         3,840,000    3,840,000
Attendance          8,910,000    8,909,997
Courses             1,120,000    1,120,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even a small mismatch matters.&lt;/p&gt;

&lt;p&gt;The goal isn't necessarily to prevent every mismatch.&lt;/p&gt;

&lt;p&gt;The goal is to ensure that &lt;strong&gt;a mismatch cannot silently pass through the system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A simplified control flow looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Sync Complete
                       │
                       ▼
              Compare source/target
                       │
              ┌────────┴────────┐
              │                 │
           Match             Mismatch
              │                 │
              ▼                 ▼
          Success            Alert
                                │
                                ▼
                         Investigate /
                         retry / repair
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This changed the operational question from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Did the job finish?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Can we prove the expected data arrived?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much stronger guarantee.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Dead-Letter Paths Are Part of the Design
&lt;/h1&gt;

&lt;p&gt;Not every record that enters the pipeline will successfully complete.&lt;/p&gt;

&lt;p&gt;Instead of allowing problematic records to disappear into logs, we need a deliberate failure path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Processing
                      │
             ┌────────┴────────┐
             │                 │
          Success             Failure
             │                 │
             ▼                 ▼
          Target          Dead-letter
                             │
                             ▼
                       Investigation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dead-letter path provides two important properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The primary pipeline can continue processing valid data.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failed records remain visible and recoverable.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is particularly important when one malformed record shouldn't block millions of valid records.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Observability Must Follow the Data
&lt;/h1&gt;

&lt;p&gt;Traditional application monitoring often focuses on infrastructure metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU&lt;/li&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Error rates&lt;/li&gt;
&lt;li&gt;Availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are important, but they aren't enough for data pipelines.&lt;/p&gt;

&lt;p&gt;We also needed &lt;strong&gt;data-level telemetry&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For each sync, useful metrics included concepts such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;records_received
records_validated
records_processed
records_rejected
records_failed
records_retried
records_loaded
reconciliation_status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets operators move from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Something failed."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"District X received 2.4M records.
2.39M were processed successfully.
4,812 were rejected during validation.
127 failed during transformation.
Reconciliation detected a 127-record mismatch."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the difference between &lt;strong&gt;monitoring infrastructure&lt;/strong&gt; and &lt;strong&gt;observing a data pipeline&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Design for Partial Failure
&lt;/h1&gt;

&lt;p&gt;Distributed systems fail partially.&lt;/p&gt;

&lt;p&gt;A pipeline processing millions of records should assume that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some batches succeed.&lt;/li&gt;
&lt;li&gt;Some batches fail.&lt;/li&gt;
&lt;li&gt;Some requests time out.&lt;/li&gt;
&lt;li&gt;Some retries succeed.&lt;/li&gt;
&lt;li&gt;Some retries fail again.&lt;/li&gt;
&lt;li&gt;External systems can disappear temporarily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to an important architectural principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't design around the assumption that the entire operation succeeds or fails together.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, explicitly model intermediate states.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QUEUED
   ↓
PROCESSING
   ↓
VALIDATED
   ↓
LOADED
   ↓
RECONCILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And failure states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROCESSING
    ↓
FAILED
    ↓
RETRYING
    ↓
PROCESSING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This state-oriented approach makes both automation and operational debugging significantly easier.&lt;/p&gt;




&lt;h1&gt;
  
  
  What We Learned
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Idempotency is non-negotiable
&lt;/h2&gt;

&lt;p&gt;Retries are inevitable.&lt;/p&gt;

&lt;p&gt;Without idempotency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retry + partial success = duplicate or inconsistent data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With idempotency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retry + partial success = recoverable operation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Design for retries from the beginning rather than adding idempotency after the first production incident.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Reconciliation catches what application monitoring misses
&lt;/h2&gt;

&lt;p&gt;A pipeline can have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No infrastructure alarms&lt;/li&gt;
&lt;li&gt;No application exceptions&lt;/li&gt;
&lt;li&gt;Successful job completion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and still have incorrect data.&lt;/p&gt;

&lt;p&gt;Count-based reconciliation and other data-quality controls provide a second line of defense.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Batch-level controls are as important as record-level controls
&lt;/h2&gt;

&lt;p&gt;Record-level validation answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is this record valid?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Batch-level reconciliation answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Did we process everything we expected to process?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You need both.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Failure should be observable, not invisible
&lt;/h2&gt;

&lt;p&gt;A rejected record isn't necessarily a disaster.&lt;/p&gt;

&lt;p&gt;A rejected record that nobody knows about is.&lt;/p&gt;

&lt;p&gt;Dead-letter queues, explicit states, metrics, and alerts turn hidden failures into manageable operational work.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Auditability should be designed, not added later
&lt;/h2&gt;

&lt;p&gt;If you wait until an audit or production incident to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What happened to this data?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you'll discover that the information you need may no longer exist.&lt;/p&gt;

&lt;p&gt;Capture the evidence while the pipeline is running.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Bigger Lesson
&lt;/h1&gt;

&lt;p&gt;Scaling ETL isn't primarily about making the pipeline faster.&lt;/p&gt;

&lt;p&gt;At 25M+ records, &lt;strong&gt;throughput is only one dimension of the problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The more important questions become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can we retry safely?
Can we detect partial failure?
Can we reconcile source and target?
Can we explain what happened?
Can we recover without reprocessing everything?
Can we prove the final state?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That changes how you design the system.&lt;/p&gt;

&lt;p&gt;The pipeline isn't simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract → Transform → Load
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract
   ↓
Validate
   ↓
Process
   ↓
Verify
   ↓
Reconcile
   ↓
Audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's probably the biggest lesson we learned:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Modern ETL isn't just about moving data. It's about being able to prove that the data moved correctly.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you're processing tens of millions of records across hundreds of data sources, &lt;strong&gt;trust becomes a system feature&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>etl</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
