<?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: Alok Deep</title>
    <description>The latest articles on DEV Community by Alok Deep (@alok1663).</description>
    <link>https://dev.to/alok1663</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%2F4097920%2F13eb2106-9686-4250-8c70-e76401460e96.png</url>
      <title>DEV Community: Alok Deep</title>
      <link>https://dev.to/alok1663</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alok1663"/>
    <language>en</language>
    <item>
      <title>How to Reduce Abandoned Carts on your Shopping Site</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Mon, 28 Sep 2026 11:34:56 +0000</pubDate>
      <link>https://dev.to/alok1663/how-to-reduce-abandoned-carts-on-your-shopping-site-1bbd</link>
      <guid>https://dev.to/alok1663/how-to-reduce-abandoned-carts-on-your-shopping-site-1bbd</guid>
      <description>&lt;p&gt;The cart already has items. The shopper clicked "Checkout."&lt;/p&gt;

&lt;p&gt;Then nothing happens for two seconds.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(I work on &lt;a href="https://getapexcache.com" rel="noopener noreferrer"&gt;ApexCache&lt;/a&gt; — edge caching for GET-heavy APIs. The checkout advice stands if you use another proxy; the invalidation details are what bite you either way.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That pause is when you lose orders that were already yours. Not because the product was wrong. Because waiting feels like something broke, and "I'll finish later" is easier than staring at a spinner.&lt;/p&gt;

&lt;p&gt;This post is about the &lt;strong&gt;read&lt;/strong&gt; side of checkout: the APIs that paint shipping, tax, and payment UI. Those are cacheable. Placing the order is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkout is a different mental mode than browsing
&lt;/h2&gt;

&lt;p&gt;On a category page, slow load is annoying. On checkout, slow load reads as risk.&lt;/p&gt;

&lt;p&gt;Shoppers ask silent questions while the UI hangs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the last step save?&lt;/li&gt;
&lt;li&gt;Will I get charged twice?&lt;/li&gt;
&lt;li&gt;Is this site legit?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mobile makes exit cheap: back button, home screen, another tab. Intent was high a moment ago. Intent decays per second of empty UI.&lt;/p&gt;

&lt;p&gt;Industry checkout studies (Baymard Institute and similar) keep pointing at the same operational issue: &lt;strong&gt;friction and delay on late funnel steps correlate with abandonment.&lt;/strong&gt; You do not need a psychology degree to see it in session replays — watch ten checkouts on 4G and count the sighs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checkout waterfall nobody optimizes
&lt;/h2&gt;

&lt;p&gt;Teams ship image CDN and font preloads for the storefront. Checkout often talks to a different service mesh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET  /api/cart
GET  /api/shipping-methods?cart_id=…&amp;amp;zip=94107
GET  /api/tax-estimate?cart_id=…
GET  /api/payment-config
POST /api/orders          ← must hit origin, always
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In traces we see the first three GETs each open a DB connection, run nearly identical joins, and return JSON that does not change until the cart changes. Under a sale, p95 on those endpoints triples while conversion on the same traffic source drops.&lt;/p&gt;

&lt;p&gt;That is not a copy problem. It is an architecture problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache the screen painters, bypass the money movers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://getapexcache.com" rel="noopener noreferrer"&gt;ApexCache&lt;/a&gt; only caches &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD&lt;/code&gt;. Everything else passes through to origin.&lt;/p&gt;

&lt;p&gt;For checkout that is the right split:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Edge cache&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /api/cart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Short TTL (15–60s) + cache tag per cart&lt;/td&gt;
&lt;td&gt;Same cart id, same JSON until line items change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /api/shipping-rates&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes, with query canonicalization&lt;/td&gt;
&lt;td&gt;Same zip + cart weight → same rates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /api/tax-estimate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes, tight TTL&lt;/td&gt;
&lt;td&gt;Recompute when address or cart changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /api/payment-methods&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Longer TTL&lt;/td&gt;
&lt;td&gt;Changes rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;POST /api/orders&lt;/code&gt;, payment capture, webhooks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Bypass&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Non-negotiable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your platform uses GraphQL for checkout, the rule is the same: &lt;strong&gt;cache only idempotent read operations&lt;/strong&gt; with explicit cache keys — not &lt;code&gt;mutation checkoutComplete&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example policy mindset (pseudo-config):&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;# Illustrative — match your dashboard or proxy rules&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;/api/shipping-rates*&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;GET&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;120s&lt;/span&gt;
  &lt;span class="na"&gt;cache_tag_header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;X-Cache-Tag&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;/api/orders&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;POST&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bypass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Query strings are why shipping-rate caches miss
&lt;/h2&gt;

&lt;p&gt;Two requests that mean the same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/shipping-rates?zip=94107&amp;amp;cart_id=abc
GET /api/shipping-rates?cart_id=abc&amp;amp;zip=94107
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A naive cache treats them as different keys. Hit rate tanks; origin still melts.&lt;/p&gt;

&lt;p&gt;Canonicalize parameter order (and strip &lt;code&gt;utm_*&lt;/code&gt; junk) before the cache key is computed. We do this at the edge so your application code stays dumb.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stale shipping price is worse than slow shipping price
&lt;/h2&gt;

&lt;p&gt;Speed without correctness loses trust faster than latency.&lt;/p&gt;

&lt;p&gt;When inventory or price changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Purge by tag (&lt;code&gt;product:SKU123&lt;/code&gt;, &lt;code&gt;cart:abc&lt;/code&gt;) via webhook or CDC — Postgres WAL, MySQL binlog, whatever your stack already emits.&lt;/li&gt;
&lt;li&gt;Do &lt;strong&gt;not&lt;/strong&gt; rely on a 30-minute TTL for stock-sensitive reads during a flash sale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We target sub-10ms invalidation on tagged entries in product docs; your job is to emit the tag when the row changes, not to manually purge CDN URLs.&lt;/p&gt;

&lt;p&gt;Tradeoff in plain terms: &lt;strong&gt;event-driven invalidation costs engineering time up front; fixed long TTLs cost you refunds and angry tweets later.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SingleFlight when everyone checks out at once
&lt;/h2&gt;

&lt;p&gt;TV ad airs. Five thousand people hit &lt;code&gt;GET /api/shipping-rates?zip=10001&lt;/code&gt; in the same second.&lt;/p&gt;

&lt;p&gt;Without request coalescing, that is five thousand identical SQL queries.&lt;/p&gt;

&lt;p&gt;With SingleFlight at the edge, one query runs; the rest wait on the in-memory result. p99 drops; the checkout step does not stutter because the database hiccuped.&lt;/p&gt;

&lt;p&gt;This matters more at checkout than on catalog browse because &lt;strong&gt;every user is on the same few endpoints at the same time&lt;/strong&gt; — shipping and tax, not long-tail SEO URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the right thing
&lt;/h2&gt;

&lt;p&gt;Lighthouse on the marketing homepage is the wrong scoreboard.&lt;/p&gt;

&lt;p&gt;Try this instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open checkout on throttled 4G in DevTools.&lt;/li&gt;
&lt;li&gt;Sort the network panel by duration for the step where users abandon (often shipping or payment).&lt;/li&gt;
&lt;li&gt;Note TTFB on the top three GETs.&lt;/li&gt;
&lt;li&gt;Replay the same GET with &lt;code&gt;curl -I&lt;/code&gt; twice; look for a cache hit header on the second call.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="s2"&gt;"https://api.yourstore.com/api/shipping-rates?cart_id=abc&amp;amp;zip=94107"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; x-apexcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First request: &lt;code&gt;MISS&lt;/code&gt; (expected). Second: &lt;code&gt;HIT&lt;/code&gt; — you removed an origin round trip from a step where hesitation is expensive.&lt;/p&gt;

&lt;p&gt;Pair that with completion rate on the same funnel step in analytics. If median load time on shipping drops 400ms and step-three completion rises, you have evidence, not a blog claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are not saying
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Edge cache does not replace a broken payment provider.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It &lt;strong&gt;does&lt;/strong&gt; remove pointless database work on reads the shopper already triggered once this session.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do next on your stack
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://getapexcache.com" rel="noopener noreferrer"&gt;Open ApexCache&lt;/a&gt;&lt;/strong&gt; — skim how bypass vs cache policies are split for write paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://app.getapexcache.com" rel="noopener noreferrer"&gt;Start free&lt;/a&gt;&lt;/strong&gt; — point a staging API hostname, add one rule for &lt;code&gt;/api/shipping-rates*&lt;/code&gt; or your hottest checkout GET.&lt;/li&gt;
&lt;li&gt;Run checkout twice; confirm the second shipping-rate request is a cache hit without changing cart contents.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Docs: &lt;a href="https://getapexcache.com/docs" rel="noopener noreferrer"&gt;getapexcache.com/docs&lt;/a&gt; · Questions: &lt;a href="https://getapexcache.com/contact" rel="noopener noreferrer"&gt;getapexcache.com/contact&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I work on ApexCache. Session replay beats industry averages — record your own checkout before you trust any conversion math from a vendor blog, including this one.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ecommerce</category>
      <category>performance</category>
      <category>caching</category>
    </item>
    <item>
      <title>Agent Marketplaces: The GPU Fleet Was Fine. The Catalog API Wasn't.</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Fri, 25 Sep 2026 11:05:46 +0000</pubDate>
      <link>https://dev.to/alok1663/agent-marketplaces-the-gpu-fleet-was-fine-the-catalog-api-wasnt-32il</link>
      <guid>https://dev.to/alok1663/agent-marketplaces-the-gpu-fleet-was-fine-the-catalog-api-wasnt-32il</guid>
      <description>&lt;p&gt;Agent platforms present themselves as AI inference companies in their pitch decks. In their server access logs, they look like high-traffic e-commerce marketplaces.&lt;/p&gt;

&lt;p&gt;Visitors spend most of their time browsing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scrolling tool directories: &lt;code&gt;GET /v1/tools?category=developer-tools&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Inspecting function calling schemas: &lt;code&gt;GET /v1/tools/github-pr-analyzer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Checking provider status: &lt;code&gt;GET /v1/providers/anthropic/models&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reading pricing feeds: &lt;code&gt;GET /v1/pricing/tiers&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reviewing community agent templates: &lt;code&gt;GET /v1/templates?sort=popular&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The actual agent execution loop (planning steps, calling tool APIs, and streaming response tokens) accounts for a small fraction of total requests. Those run requests are computationally expensive, but their query volume is low compared to the constant stream of catalog views, search queries, and bot crawlers hitting the frontend.&lt;/p&gt;

&lt;p&gt;During a viral launch on Product Hunt, one agent marketplace we worked with saw its PostgreSQL database max out connection limits while GPU cluster utilization sat at just 22%.&lt;/p&gt;

&lt;p&gt;The GPU infrastructure had plenty of headroom. The catalog API crashed the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why catalog traffic breaks database pools
&lt;/h2&gt;

&lt;p&gt;When an agent directory goes viral on social media or gets indexed by web scrapers, traffic hits catalog endpoints in sudden bursts.&lt;/p&gt;

&lt;p&gt;In most agent backends, fetching a tool manifest is an expensive query. A single tool listing joins:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The base tool record (name, author, description).&lt;/li&gt;
&lt;li&gt;The JSON Schema parameter definitions used for LLM function calling.&lt;/li&gt;
&lt;li&gt;User ratings, reviews, and verified download counts.&lt;/li&gt;
&lt;li&gt;Compatibility tags (e.g. OpenAI, LangChain, AutoGen).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Under sudden load, application servers autoscale to handle incoming HTTP requests. If your API scales from 4 pods to 32 pods, each pod opens its own database connection pool.&lt;/p&gt;

&lt;p&gt;When 32 pods each attempt to maintain 10 active database connections, they exceed PostgreSQL's &lt;code&gt;max_connections&lt;/code&gt; limit. The database throws connection errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FATAL: remaining connection slots are reserved for non-replication superuser connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that moment, the entire application fails. Even users attempting to run existing agents experience 500 errors because the platform cannot acquire a database connection to save session state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separating browse traffic from run traffic
&lt;/h2&gt;

&lt;p&gt;The solution is establishing a strict architectural separation between browse traffic and run traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming Traffic
   │
   ├── [GET /v1/tools, /v1/models, /v1/templates] ──► ApexCache Edge (Served from RAM)
   │                                                     │ (Cache miss)
   │                                                     ▼
   │                                              Origin Backend API
   │                                                     │
   └── [POST /v1/agents/run, /v1/tools/execute] ──► Direct to Origin (Bypasses Cache)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://getapexcache.com" rel="noopener noreferrer"&gt;ApexCache&lt;/a&gt; sits in front of your API as a reverse proxy. It inspects incoming HTTP methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Safe read requests (&lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;):&lt;/strong&gt; Matched against configured cache policies (e.g. &lt;code&gt;/v1/tools/*&lt;/code&gt;). Cached responses return directly from edge memory in 2ms to 5ms without reaching your origin servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateful run requests (&lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;):&lt;/strong&gt; Stream directly to your origin backend with zero caching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tool execution, OAuth callbacks, credit card checkouts, and agent run loops pass through untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  SingleFlight: preventing the thundering herd
&lt;/h2&gt;

&lt;p&gt;When a popular account shares a link to a specific tool or agent template, thousands of visitors click that link simultaneously.&lt;/p&gt;

&lt;p&gt;Without request coalescing, all incoming requests reach your origin at the exact same millisecond. If the cache is cold, your database must run the same complex query 5,000 times concurrently. This cache stampede often knocks the database offline before the cache can warm up.&lt;/p&gt;

&lt;p&gt;ApexCache solves this with built-in SingleFlight request deduplication.&lt;/p&gt;

&lt;p&gt;When 5,000 concurrent requests arrive for &lt;code&gt;/v1/tools/github-pr-analyzer&lt;/code&gt;, the edge proxy forwards only one request to your origin. The other 4,999 requests wait in memory. As soon as the origin returns the response, the edge proxy delivers that payload to all 5,000 waiting clients and stores it in cache for subsequent visitors.&lt;/p&gt;

&lt;p&gt;Your origin server experiences the load of exactly one request instead of 5,000.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing tag-based cache invalidation
&lt;/h2&gt;

&lt;p&gt;Catalog data changes whenever an author publishes a new tool, updates parameter schemas, or revises pricing.&lt;/p&gt;

&lt;p&gt;To prevent serving stale manifests to agent runtimes, configure your origin API to emit &lt;code&gt;Cache-Tag&lt;/code&gt; headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Next.js / Node.js route handler&lt;/span&gt;
&lt;span class="k"&gt;export&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;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&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;toolData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toolId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;schemas&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;author&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;ratings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public, s-maxage=600&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Tag response with specific tool ID and broad category&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Tag&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tool:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toolId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,category:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toolData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,catalog`&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 an author updates their tool definition, your backend sends an invalidation request to the ApexCache API:&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;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.getapexcache.com/api/v1/cache/invalidate"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$APEXCACHE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"tags":["tool:github-pr-analyzer"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cached tool record clears across all global edge nodes in under 10 milliseconds. Other cached tools remain unaffected in edge memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-domain setup for marketplace ecosystems
&lt;/h2&gt;

&lt;p&gt;Marketplaces frequently split functionality across multiple hostnames:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;www.marketplace.com&lt;/code&gt;: Marketing site and landing pages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;marketplace.com/tools&lt;/code&gt;: Browse and search web directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api.marketplace.com&lt;/code&gt;: Public API used by developer SDKs and CLI tools.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs.marketplace.com&lt;/code&gt;: Developer documentation and schema specifications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ApexCache allows you to attach multiple domains to a single account:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Route &lt;code&gt;api.marketplace.com&lt;/code&gt; through ApexCache with path rules targeting &lt;code&gt;/v1/tools*&lt;/code&gt;, &lt;code&gt;/v1/models*&lt;/code&gt;, and &lt;code&gt;/v1/categories*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Leave your agent execution endpoint (&lt;code&gt;/v1/agents/run&lt;/code&gt;) unmapped or set to pass-through.&lt;/li&gt;
&lt;li&gt;Route &lt;code&gt;docs.marketplace.com&lt;/code&gt; through ApexCache with a blanket &lt;code&gt;/*&lt;/code&gt; policy and a longer TTL (3600 seconds) to cache documentation pages and OpenAPI definitions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The financial impact of caching the catalog
&lt;/h2&gt;

&lt;p&gt;Here is the operational cost comparison for an agent marketplace handling 20 million monthly catalog views:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Without edge caching&lt;/th&gt;
&lt;th&gt;With edge caching (85% hit rate)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API server instances&lt;/td&gt;
&lt;td&gt;16 to 24 pods on high alert&lt;/td&gt;
&lt;td&gt;2 to 4 baseline pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database sizing&lt;/td&gt;
&lt;td&gt;AWS RDS db.r6g.xlarge + read replica&lt;/td&gt;
&lt;td&gt;AWS RDS db.r6g.large (single instance)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database connections&lt;/td&gt;
&lt;td&gt;150 to 200 persistent connections&lt;/td&gt;
&lt;td&gt;15 to 25 persistent connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak catalog latency&lt;/td&gt;
&lt;td&gt;120ms to 450ms p95&lt;/td&gt;
&lt;td&gt;4ms to 8ms p95&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Offloading 85% of read traffic from your database allows you to delay provisioning costly read replicas, reduce API pod autoscaling thresholds, and protect application stability during viral spikes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing your catalog cache
&lt;/h2&gt;

&lt;p&gt;To test your catalog caching setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add your staging API domain in the ApexCache dashboard.&lt;/li&gt;
&lt;li&gt;Configure a policy for &lt;code&gt;/v1/tools*&lt;/code&gt; with a TTL of 120 seconds.&lt;/li&gt;
&lt;li&gt;Send a test request with curl to inspect response headers:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="s2"&gt;"https://api.yourmarketplace.com/v1/tools?category=devops"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; x-apexcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that the first request returns &lt;code&gt;X-ApexCache-Status: MISS&lt;/code&gt; and the second request returns &lt;code&gt;X-ApexCache-Status: HIT&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do next on your stack
&lt;/h2&gt;

&lt;p&gt;If your catalog endpoints threaten to saturate database connection pools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://getapexcache.com" rel="noopener noreferrer"&gt;Open ApexCache&lt;/a&gt;&lt;/strong&gt; and review the SingleFlight request coalescing documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://app.getapexcache.com" rel="noopener noreferrer"&gt;Start free&lt;/a&gt;&lt;/strong&gt; by connecting a staging hostname and defining one policy rule on &lt;code&gt;/v1/tools*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run a concurrent load test using &lt;code&gt;k6&lt;/code&gt; to verify that duplicate requests coalesce cleanly without stressing origin databases.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Docs: &lt;a href="https://getapexcache.com/docs" rel="noopener noreferrer"&gt;getapexcache.com/docs&lt;/a&gt; · Contact: &lt;a href="https://getapexcache.com/contact" rel="noopener noreferrer"&gt;getapexcache.com/contact&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I work on ApexCache. Every marketplace has different traffic patterns. Verify your own endpoint volume before sizing production cache policies.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>api</category>
      <category>scaling</category>
    </item>
    <item>
      <title>RAG Apps: Your Vector DB Bill Is Mostly Déjà Vu</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Fri, 25 Sep 2026 11:05:22 +0000</pubDate>
      <link>https://dev.to/alok1663/rag-apps-your-vector-db-bill-is-mostly-deja-vu-444f</link>
      <guid>https://dev.to/alok1663/rag-apps-your-vector-db-bill-is-mostly-deja-vu-444f</guid>
      <description>&lt;p&gt;A team running a customer support RAG bot over two million internal documents noticed their Pinecone invoice climbed every month. They assumed continuous document re-indexing drove the cost.&lt;/p&gt;

&lt;p&gt;A simple query analysis showed something different: &lt;code&gt;GET /search?q=reset+password&lt;/code&gt; and its minor variations accounted for 38% of all vector lookups. The retrieval service repeatedly generated embeddings for the same twenty phrases, traversed the same HNSW index graph, and fetched the same top five markdown chunks.&lt;/p&gt;

&lt;p&gt;The model still needed to formulate the response for each user conversation, but the retrieval layer repeated identical work thousands of times per day.&lt;/p&gt;

&lt;p&gt;Here is how to cache the retrieval read path at the HTTP edge without complicating your streaming LLM generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The latency and cost anatomy of a RAG query
&lt;/h2&gt;

&lt;p&gt;Before an LLM generates its first token, a standard RAG pipeline executes a sequence of synchronous network calls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Text embedding generation:&lt;/strong&gt; 40ms to 80ms (calls OpenAI &lt;code&gt;text-embedding-3-small&lt;/code&gt; or a local TEI instance).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector index search:&lt;/strong&gt; 50ms to 150ms (approximate nearest neighbor search across vector partitions).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunk retrieval and metadata fetch:&lt;/strong&gt; 20ms to 50ms (pulling raw text from document storage).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt assembly:&lt;/strong&gt; 5ms (interpolating retrieved chunks into the system prompt).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM inference:&lt;/strong&gt; 300ms to 2000ms (streaming response back to the client).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1 through 3 represent the retrieval pipeline. They consume between 110ms and 280ms of latency and incur both embedding API charges and vector database query fees.&lt;/p&gt;

&lt;p&gt;When a customer support portal, internal documentation assistant, or technical search engine handles real users, query frequency follows a power-law distribution. A small cluster of questions (&lt;code&gt;how to configure sso&lt;/code&gt;, &lt;code&gt;pricing limits&lt;/code&gt;, &lt;code&gt;export to csv&lt;/code&gt;) generates a disproportionate share of total traffic.&lt;/p&gt;

&lt;p&gt;Repeating full vector searches for these queries adds latency and cost with zero improvement in answer quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why in-memory maps and internal Redis caches fail in production
&lt;/h2&gt;

&lt;p&gt;Teams usually attempt to fix this inside the application code before looking at network architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In-process dictionaries (&lt;code&gt;lru_cache&lt;/code&gt;):&lt;/strong&gt; In a Kubernetes cluster with 12 API pods, an in-memory dictionary creates 12 isolated caches with low hit rates. Every rolling deployment empties all 12 caches simultaneously, triggering a thundering herd against the vector database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared Redis clusters:&lt;/strong&gt; Adding Redis creates another stateful dependency to monitor, patch, and scale. Engineers must write custom key serialization logic, handle Redis connection pool timeouts, and ensure cache invalidation does not fall out of sync with document updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenant isolation risks:&lt;/strong&gt; If custom cache key logic accidentally omits an organization ID or workspace boundary, one tenant can receive retrieved chunks belonging to another tenant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Placing a dedicated HTTP caching gateway in front of your retrieval service provides a single, unified cache tier outside application memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structuring retrieval APIs for HTTP edge caching
&lt;/h2&gt;

&lt;p&gt;Edge proxies operate on standard HTTP semantics: request method, URL path, query parameters, and selected headers.&lt;/p&gt;

&lt;p&gt;Many RAG frameworks default to sending retrieval queries as HTTP &lt;code&gt;POST&lt;/code&gt; requests with JSON bodies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/v1/retrieve&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rag.example.com&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reset password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"top_k"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.82&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;p&gt;Because HTTP proxies treat &lt;code&gt;POST&lt;/code&gt; requests as unsafe and non-idempotent, they pass them directly through to the origin without caching.&lt;/p&gt;

&lt;p&gt;To enable edge caching, expose an idempotent &lt;code&gt;GET&lt;/code&gt; endpoint for retrieval queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/v1/retrieve?query=reset+password&amp;amp;top_k=5&amp;amp;threshold=0.82&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rag.example.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an example FastAPI implementation that handles retrieval and returns appropriate cache headers:&lt;br&gt;
&lt;/p&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;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/tenants/{tenant_id}/retrieve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retrieve_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="n"&gt;min_length&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;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ge&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;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Response&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="c1"&gt;# Perform vector search against Pinecone, Qdrant, or pgvector
&lt;/span&gt;    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;vector_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;query_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Instruct edge proxy to cache this lookup for 5 minutes
&lt;/span&gt;    &lt;span class="c1"&gt;# Surrogate tag allows instant invalidation when documents change
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cache-Control&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public, s-maxage=300&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cache-Tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kb:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;tenant_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The streaming chat endpoint (&lt;code&gt;POST /v1/chat&lt;/code&gt;) remains a standard &lt;code&gt;POST&lt;/code&gt; request. The chat service calls the retrieval endpoint via HTTP &lt;code&gt;GET&lt;/code&gt;. If the query was asked recently, the edge proxy returns the cached vector chunks in 2ms, completely bypassing the embedding model and vector database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcing strict tenant isolation
&lt;/h2&gt;

&lt;p&gt;When caching RAG retrieval, multi-tenant safety is critical. Never share cached retrieval results across different customers.&lt;/p&gt;

&lt;p&gt;Use one of these two routing conventions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Path-based tenant scoping:&lt;/strong&gt; Include the customer identifier directly in the URL path (&lt;code&gt;/v1/tenants/{tenant_id}/retrieve&lt;/code&gt;). Because the full URL path forms the cache key, tenant &lt;code&gt;acme&lt;/code&gt; can never access cached entries for tenant &lt;code&gt;globex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subdomain-based tenant scoping:&lt;/strong&gt; Route traffic through customer-specific hostnames (&lt;code&gt;acme.rag.example.com&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avoid using headers like &lt;code&gt;X-Tenant-ID&lt;/code&gt; for isolation unless your edge cache policy explicitly includes that header in its cache key variation rules. Path-based routing avoids accidental misconfiguration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invalidation when knowledge base documents change
&lt;/h2&gt;

&lt;p&gt;Stale retrieval data leads to hallucinations. When an editor updates a documentation page in your CMS or deletes a file from a knowledge base, the cached retrieval results must clear immediately.&lt;/p&gt;

&lt;p&gt;ApexCache supports instant tag-based invalidation. In the FastAPI example above, the origin server attached a surrogate key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Tag: kb:tenant_123
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When documents in that knowledge base update, trigger an invalidation call through the ApexCache API:&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;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.getapexcache.com/api/v1/cache/invalidate"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$APEXCACHE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"tags":["kb:tenant_123"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purge propagates across all edge locations in under 10 milliseconds. The next retrieval query fetches fresh document chunks from the vector database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended TTL rules by content type
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Knowledge base type&lt;/th&gt;
&lt;th&gt;Initial TTL&lt;/th&gt;
&lt;th&gt;Invalidation trigger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public product documentation&lt;/td&gt;
&lt;td&gt;300 to 1800 seconds&lt;/td&gt;
&lt;td&gt;Documentation build webhook (e.g. GitHub Actions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal company wiki&lt;/td&gt;
&lt;td&gt;60 to 300 seconds&lt;/td&gt;
&lt;td&gt;CMS document update hook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live ticketing or inventory data&lt;/td&gt;
&lt;td&gt;10 to 30 seconds&lt;/td&gt;
&lt;td&gt;Automated tag purge on ticket update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strictly confidential HR or legal records&lt;/td&gt;
&lt;td&gt;Pass-through (no cache)&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start with short TTL values (60 to 120 seconds). Monitor your cache hit rate in the dashboard, and only lengthen the TTL once automated invalidation hooks are tested and verified.&lt;/p&gt;

&lt;h2&gt;
  
  
  VPC and private data requirements
&lt;/h2&gt;

&lt;p&gt;Some enterprise customers cannot send proprietary internal documents through a multi-tenant public edge.&lt;/p&gt;

&lt;p&gt;For these environments, &lt;a href="https://getapexcache.com/docs/enterprise" rel="noopener noreferrer"&gt;ApexCache BYOC&lt;/a&gt; deploys the gateway directly inside your private AWS or GCP VPC. The data plane runs on your own compute instances, meaning cached document chunks never leave your security perimeter. The hosted control plane only manages cache policy definitions and API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick smoke test on staging
&lt;/h2&gt;

&lt;p&gt;To verify edge caching on your retrieval service:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Expose your retrieval logic behind a &lt;code&gt;GET /v1/retrieve&lt;/code&gt; route.&lt;/li&gt;
&lt;li&gt;In the ApexCache dashboard, connect your staging hostname and define a caching rule for &lt;code&gt;/v1/retrieve*&lt;/code&gt; with a TTL of 120 seconds.&lt;/li&gt;
&lt;li&gt;Send two identical curl requests:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="s2"&gt;"https://staging-api.example.com/v1/retrieve?query=billing+policy&amp;amp;top_k=5"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; x-apexcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that the second request returns &lt;code&gt;X-ApexCache-Status: HIT&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do next on your stack
&lt;/h2&gt;

&lt;p&gt;If your vector database costs grow faster than your active user count:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://getapexcache.com" rel="noopener noreferrer"&gt;Open ApexCache&lt;/a&gt;&lt;/strong&gt; and check the reverse proxy caching architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://app.getapexcache.com" rel="noopener noreferrer"&gt;Start free&lt;/a&gt;&lt;/strong&gt; on a staging domain and apply one policy rule to your retrieval endpoint.&lt;/li&gt;
&lt;li&gt;Compare vector database query volume before and after enabling edge caching.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Docs: &lt;a href="https://getapexcache.com/docs" rel="noopener noreferrer"&gt;getapexcache.com/docs&lt;/a&gt; · Contact: &lt;a href="https://getapexcache.com/contact" rel="noopener noreferrer"&gt;getapexcache.com/contact&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I work on ApexCache. Measure the repeat query percentage in your application logs before relying on any retrieval caching benchmarks.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>vector</category>
      <category>performance</category>
    </item>
    <item>
      <title>AI SaaS: Cut the Bill Before You Buy More GPUs</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Fri, 25 Sep 2026 11:04:24 +0000</pubDate>
      <link>https://dev.to/alok1663/ai-saas-cut-the-bill-before-you-buy-more-gpus-1bn2</link>
      <guid>https://dev.to/alok1663/ai-saas-cut-the-bill-before-you-buy-more-gpus-1bn2</guid>
      <description>&lt;p&gt;The board deck said to scale inference. The Grafana dashboard showed a different bottleneck: Postgres CPU sat at 78%, and &lt;code&gt;GET /v1/models&lt;/code&gt; handled 40,000 requests per minute, all returning identical JSON.&lt;/p&gt;

&lt;p&gt;This pattern shows up repeatedly at AI startups that already solved streaming completions. The expensive GPU fleet gets all the attention, but boring HTTP traffic often breaks the infrastructure first. Model lists, pricing sheets, feature flags, public prompt templates, and dashboard summaries hammer the primary database on every page load and client poll.&lt;/p&gt;

&lt;p&gt;Here is how to offload that read traffic at the edge without touching your streaming chat pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden traffic split in AI products
&lt;/h2&gt;

&lt;p&gt;When we examined the telemetry from three production teams (a B2B coding copilot, an automated legal assistant, and a customer support agent platform), the request distribution looked almost identical:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traffic type&lt;/th&gt;
&lt;th&gt;Share of requests&lt;/th&gt;
&lt;th&gt;Cost profile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Streaming completions (&lt;code&gt;POST&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;8% to 15%&lt;/td&gt;
&lt;td&gt;High per request (GPU time, token counts)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedding generation jobs&lt;/td&gt;
&lt;td&gt;5% to 15%&lt;/td&gt;
&lt;td&gt;Moderate (vector API batches)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Read APIs&lt;/strong&gt; (models, configs, pricing, prompt libraries)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;70% to 85%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Small per request, massive in aggregate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A single request to &lt;code&gt;GET /v1/models&lt;/code&gt; takes negligible CPU. But 40,000 requests per minute against an ORM query that joins model variants, token pricing tiers, context windows, and provider availability will saturate a database connection pool.&lt;/p&gt;

&lt;p&gt;When your web frontend, mobile client, and VS Code extension poll that endpoint on every reload, you pay a heavy database tax for static data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;/v1/models&lt;/code&gt; hits the database so hard
&lt;/h2&gt;

&lt;p&gt;In most codebases, &lt;code&gt;GET /v1/models&lt;/code&gt; is not a flat JSON file. It is an ORM call that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# FastAPI + SQLAlchemy example
&lt;/span&gt;&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/models&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_models&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;select&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;joinedload&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;pricing_tiers&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;joinedload&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;provider_status&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;joinedload&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;supported_parameters&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="nf"&gt;where&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;is_active&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by&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;display_order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;scalars&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;all&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;data&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="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&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;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every incoming client request does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Acquires a connection from your database connection pool (e.g. PgBouncer or SQLAlchemy pool).&lt;/li&gt;
&lt;li&gt;Runs a multi-table SQL query with multiple &lt;code&gt;LEFT OUTER JOIN&lt;/code&gt; clauses.&lt;/li&gt;
&lt;li&gt;Serializes several hundred rows into JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Under burst traffic, connection pools fill up. New chat sessions fail to acquire database connections to save conversation state, leading to 500 errors during peak user activity. The GPU cluster is completely idle, but the application fails because the catalog API exhausted the database pool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to cache and what to leave alone
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://getapexcache.com" rel="noopener noreferrer"&gt;ApexCache&lt;/a&gt; only caches HTTP &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD&lt;/code&gt; methods. Every other method passes directly through to your origin servers.&lt;/p&gt;

&lt;p&gt;This aligns cleanly with standard AI API design:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache these endpoints
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/models&lt;/code&gt;: Cache for 60 to 300 seconds. Add a surrogate tag (&lt;code&gt;Cache-Tag: models&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/pricing&lt;/code&gt;: Cache for 300 to 1800 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/prompts/public&lt;/code&gt;: Cache for 60 to 300 seconds with query string preservation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/docs&lt;/code&gt; or &lt;code&gt;GET /openapi.json&lt;/code&gt;: Cache for 3600 seconds.&lt;/li&gt;
&lt;li&gt;Static eval leaderboards and benchmark results: Cache for 600 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Never cache these endpoints
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /v1/chat/completions&lt;/code&gt;: Streaming tokens and user prompts must never touch an edge cache.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /v1/embeddings&lt;/code&gt;: Unique text embeddings belong on the origin.&lt;/li&gt;
&lt;li&gt;Tool execution webhooks: Dynamic and stateful.&lt;/li&gt;
&lt;li&gt;User account settings, billing invoices, and private conversation history: Authenticated per-tenant routes must bypass caching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You are not caching the AI engine. You are caching the menu that every user reads before placing an order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tag-based invalidation at the origin
&lt;/h2&gt;

&lt;p&gt;To keep data fresh without waiting for long TTL timeouts, return a &lt;code&gt;Cache-Tag&lt;/code&gt; header from your origin server. When your marketing or engineering team updates a model parameter or pricing tier, invalidate that specific tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Express / Node.js origin response&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/v1/models&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchModelsFromPostgres&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Instruct edge proxy to cache and tag this response&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public, s-maxage=300&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Tag&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;models,catalog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;models&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 deploy a new model variant or adjust rate limits, trigger a tag purge through the ApexCache API:&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;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.getapexcache.com/api/v1/cache/invalidate"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$APEXCACHE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"tags":["models"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every edge node drops the cached &lt;code&gt;/v1/models&lt;/code&gt; payload in under 10 milliseconds. The next request fetches fresh data from your origin and repopulates edge memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers: delaying a database upgrade
&lt;/h2&gt;

&lt;p&gt;Consider an AI SaaS application handling 30 million read API requests each month:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Origin baseline:&lt;/strong&gt; Node.js API to AWS RDS Postgres (db.r6g.xlarge, ~$520/month plus read replica). Average latency is 45ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge cache hit rate:&lt;/strong&gt; 88% on catalog, pricing, and public prompt routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origin reads dropped:&lt;/strong&gt; From 30 million down to 3.6 million per month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average latency:&lt;/strong&gt; Drops from 45ms down to 4ms for cached requests served from memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By reducing read QPS against the primary database by 88%, the team avoided adding a second RDS read replica ($260/month) and delayed upgrading the primary instance to &lt;code&gt;db.r6g.2xlarge&lt;/code&gt; ($1,040/month).&lt;/p&gt;

&lt;p&gt;The infrastructure savings came from eliminating redundant database queries, not from tweaking LLM prompt tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to configure your routing
&lt;/h2&gt;

&lt;p&gt;You do not need to create a separate domain for caching. You can point your existing API domain (&lt;code&gt;api.yourproduct.com&lt;/code&gt;) or specific path prefixes to ApexCache:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add your domain in the ApexCache dashboard.&lt;/li&gt;
&lt;li&gt;Add the CNAME record (&lt;code&gt;cname.getapexcache.com&lt;/code&gt;) and the verification TXT record in your DNS provider.&lt;/li&gt;
&lt;li&gt;Configure a path rule for &lt;code&gt;/v1/models*&lt;/code&gt; with a TTL of 120 seconds.&lt;/li&gt;
&lt;li&gt;Verify the setup using &lt;code&gt;curl&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; &lt;span class="s2"&gt;"https://api.yourproduct.com/v1/models"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; x-apexcache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first request, the response header reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;X-ApexCache-Status: MISS
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the second request, the response returns directly from memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;X-ApexCache-Status: HIT
Age: 4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security boundaries and gotchas
&lt;/h2&gt;

&lt;p&gt;Keep these three boundaries in mind when introducing edge caching to an AI API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check Authorization headers:&lt;/strong&gt; If your &lt;code&gt;/v1/models&lt;/code&gt; endpoint returns custom permission flags depending on user API keys, do not cache it globally. Only cache public, unauthenticated routes, or configure your policy to include the tenant identifier in the cache key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strip internal debug headers:&lt;/strong&gt; Ensure your origin server does not leak database connection pool diagnostics or internal trace identifiers in cached responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep streaming paths isolated:&lt;/strong&gt; Verify that your route rules explicitly target your read endpoints (&lt;code&gt;/v1/models&lt;/code&gt;, &lt;code&gt;/v1/pricing&lt;/code&gt;) rather than broad wildcards (&lt;code&gt;/*&lt;/code&gt;), so that streaming completions (&lt;code&gt;/v1/chat/completions&lt;/code&gt;) never encounter cache inspection overhead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Edge caching does not reduce your OpenAI or Anthropic invoice. It fixes the infrastructure tax around your product so your database stays online when traffic spikes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do next on your stack
&lt;/h2&gt;

&lt;p&gt;If your database CPU spikes while your GPU servers remain underutilized:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://getapexcache.com" rel="noopener noreferrer"&gt;Open ApexCache&lt;/a&gt;&lt;/strong&gt; and inspect the GET-only reverse proxy architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://app.getapexcache.com" rel="noopener noreferrer"&gt;Start free&lt;/a&gt;&lt;/strong&gt; by connecting a staging hostname with one policy rule on &lt;code&gt;/v1/models*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run a baseline load test using &lt;code&gt;k6&lt;/code&gt; or &lt;code&gt;hey&lt;/code&gt; and monitor your database CPU before and after caching.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Docs: &lt;a href="https://getapexcache.com/docs" rel="noopener noreferrer"&gt;getapexcache.com/docs&lt;/a&gt; · Contact: &lt;a href="https://getapexcache.com/contact" rel="noopener noreferrer"&gt;getapexcache.com/contact&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I work on ApexCache. Numbers in this article are based on production benchmarks. Run your own load test before sizing production infrastructure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>startup</category>
      <category>performance</category>
      <category>caching</category>
    </item>
    <item>
      <title>How DEV.to Can Cut TTFB by 98% and Scale Throughput with Edge Caching</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Thu, 03 Sep 2026 09:55:20 +0000</pubDate>
      <link>https://dev.to/alok1663/how-devto-can-cut-ttfb-by-98-and-scale-throughput-with-edge-caching-335n</link>
      <guid>https://dev.to/alok1663/how-devto-can-cut-ttfb-by-98-and-scale-throughput-with-edge-caching-335n</guid>
      <description>&lt;p&gt;dev.to is one of the largest developer communities on the web, serving millions of technical articles, discussions, and feeds each month. The platform runs on &lt;a href="https://github.com/forem/forem" rel="noopener noreferrer"&gt;Forem&lt;/a&gt;, an open-source software stack built on Ruby on Rails, PostgreSQL, Redis, and Sidekiq.&lt;/p&gt;

&lt;p&gt;Forem's Rails architecture provides productive developer tooling and clean domain abstractions. However, serving dynamic server-rendered HTML through Puma workers introduces measurable latency, especially under high concurrency or traffic surges.&lt;/p&gt;

&lt;p&gt;We ran a performance and infrastructure audit against production dev.to endpoints using an automated benchmarking suite. Here is a breakdown of origin bottlenecks, empirical benchmark results, and how an in-memory edge proxy like ApexCache drops time to first byte (TTFB) from 640ms down to 11ms while offloading 100% of read traffic from origin Rails workers.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Rails request lifecycle on Forem
&lt;/h2&gt;

&lt;p&gt;When a user requests a public page like &lt;code&gt;https://dev.to/videos&lt;/code&gt; or &lt;code&gt;https://dev.to/challenges&lt;/code&gt;, the request travels through several layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request ──► Cloudflare / CDN ──► Origin Load Balancer ──► Puma Worker (Ruby)
                                                                 ├── Controller action &amp;amp; params validation
                                                                 ├── PostgreSQL query (articles, user relations)
                                                                 ├── Fragment cache checks (Redis)
                                                                 └── ERB / ViewComponent HTML rendering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with Redis fragment caching and database indexing, every request hitting the Rails origin must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Allocate an execution thread inside a Puma worker process.&lt;/li&gt;
&lt;li&gt;Initialize request context, parse cookies, and evaluate session state.&lt;/li&gt;
&lt;li&gt;Fetch missing cache fragments or execute database queries via ActiveRecord.&lt;/li&gt;
&lt;li&gt;Render string templates and construct the complete HTTP payload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Under light traffic, a modern Rails server responds in 300ms to 600ms. But because Ruby is interpreted and execution threads hold memory, Puma worker pools quickly saturate when hundreds of concurrent requests arrive simultaneously.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Benchmark methodology: direct origin vs edge acceleration
&lt;/h2&gt;

&lt;p&gt;We audited dev.to across 8 representative public routes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt; (Homepage feed)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/challenges&lt;/code&gt; (Community coding challenges)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/++&lt;/code&gt; (Community member index)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/videos&lt;/code&gt; (Video content feed)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/deved&lt;/code&gt; (Developer Education listings)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/help&lt;/code&gt; (Help &amp;amp; FAQ center)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/advertise&lt;/code&gt; (Advertising information)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/organizations&lt;/code&gt; (Developer organization index)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test ran in two phases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1 (Direct Origin Baseline):&lt;/strong&gt; Paced baseline queries (concurrency 2, 100ms pacing delay) directly to &lt;code&gt;https://www.dev.to&lt;/code&gt; to measure native origin response times without triggering rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2 &amp;amp; 3 (In-Memory Edge Acceleration):&lt;/strong&gt; The same endpoints routed through an ApexCache edge proxy instance, warmed with a single pass, then stress-tested across 25,000 requests at a concurrency of 40 worker threads.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Empirical benchmark results
&lt;/h2&gt;

&lt;p&gt;The benchmark measured TTFB, end-to-end latency, throughput capacity, and tail latencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telemetry comparison matrix
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evaluation parameter&lt;/th&gt;
&lt;th&gt;Before (Direct origin)&lt;/th&gt;
&lt;th&gt;After (ApexCache edge)&lt;/th&gt;
&lt;th&gt;Delta / Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sampled request load&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;40 requests&lt;/td&gt;
&lt;td&gt;25,000 requests&lt;/td&gt;
&lt;td&gt;625x load scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Worker concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 workers&lt;/td&gt;
&lt;td&gt;40 workers&lt;/td&gt;
&lt;td&gt;High-concurrency burst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTFB (Mean)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;639.9 ms&lt;/td&gt;
&lt;td&gt;11.0 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;58.3x faster (-98.3%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTFB (Median P50)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;420.0 ms&lt;/td&gt;
&lt;td&gt;10.5 ms&lt;/td&gt;
&lt;td&gt;-409.5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTFB (P95 Tail)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.85 s&lt;/td&gt;
&lt;td&gt;18.1 ms&lt;/td&gt;
&lt;td&gt;-1.83 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTFB (P99 Tail)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.29 s&lt;/td&gt;
&lt;td&gt;23.1 ms&lt;/td&gt;
&lt;td&gt;-2.27 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E Latency (Mean)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;660.0 ms&lt;/td&gt;
&lt;td&gt;15.6 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42.3x faster (-97.6%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E Latency (Median P50)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;434.6 ms&lt;/td&gt;
&lt;td&gt;14.7 ms&lt;/td&gt;
&lt;td&gt;-419.9 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E Latency (P95 Tail)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.85 s&lt;/td&gt;
&lt;td&gt;27.1 ms&lt;/td&gt;
&lt;td&gt;-1.82 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E Latency (P99 Tail)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.51 s&lt;/td&gt;
&lt;td&gt;34.2 ms&lt;/td&gt;
&lt;td&gt;-2.48 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Origin offload ratio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.0% (All hit origin)&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;td&gt;100% backend offload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput capacity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.4 req/s&lt;/td&gt;
&lt;td&gt;2,556.1 req/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,086x capacity&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Per-endpoint latency breakdown
&lt;/h3&gt;

&lt;p&gt;Origin response times varied substantially by page complexity:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint path&lt;/th&gt;
&lt;th&gt;Origin TTFB&lt;/th&gt;
&lt;th&gt;Edge TTFB&lt;/th&gt;
&lt;th&gt;Origin E2E&lt;/th&gt;
&lt;th&gt;Edge E2E&lt;/th&gt;
&lt;th&gt;Mean payload&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/&lt;/code&gt; (Homepage)&lt;/td&gt;
&lt;td&gt;1,090.0 ms&lt;/td&gt;
&lt;td&gt;14.3 ms&lt;/td&gt;
&lt;td&gt;1,200.0 ms&lt;/td&gt;
&lt;td&gt;23.7 ms&lt;/td&gt;
&lt;td&gt;253.7 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/challenges&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;689.1 ms&lt;/td&gt;
&lt;td&gt;11.7 ms&lt;/td&gt;
&lt;td&gt;703.8 ms&lt;/td&gt;
&lt;td&gt;18.7 ms&lt;/td&gt;
&lt;td&gt;171.5 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/++&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;767.2 ms&lt;/td&gt;
&lt;td&gt;12.9 ms&lt;/td&gt;
&lt;td&gt;780.7 ms&lt;/td&gt;
&lt;td&gt;18.6 ms&lt;/td&gt;
&lt;td&gt;141.1 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/videos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;921.2 ms&lt;/td&gt;
&lt;td&gt;10.3 ms&lt;/td&gt;
&lt;td&gt;924.7 ms&lt;/td&gt;
&lt;td&gt;14.1 ms&lt;/td&gt;
&lt;td&gt;64.8 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/organizations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;538.9 ms&lt;/td&gt;
&lt;td&gt;9.9 ms&lt;/td&gt;
&lt;td&gt;547.4 ms&lt;/td&gt;
&lt;td&gt;13.0 ms&lt;/td&gt;
&lt;td&gt;56.9 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/help&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;463.1 ms&lt;/td&gt;
&lt;td&gt;9.4 ms&lt;/td&gt;
&lt;td&gt;467.4 ms&lt;/td&gt;
&lt;td&gt;12.5 ms&lt;/td&gt;
&lt;td&gt;48.8 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/advertise&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;337.8 ms&lt;/td&gt;
&lt;td&gt;9.5 ms&lt;/td&gt;
&lt;td&gt;339.3 ms&lt;/td&gt;
&lt;td&gt;12.0 ms&lt;/td&gt;
&lt;td&gt;42.3 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/deved&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;310.7 ms&lt;/td&gt;
&lt;td&gt;9.6 ms&lt;/td&gt;
&lt;td&gt;321.5 ms&lt;/td&gt;
&lt;td&gt;12.3 ms&lt;/td&gt;
&lt;td&gt;43.2 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the homepage (&lt;code&gt;/&lt;/code&gt;), origin TTFB was 1.09 seconds. Edge acceleration delivered the same fully-rendered HTML document in 14.3ms.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Why tail latencies matter for dev.to
&lt;/h2&gt;

&lt;p&gt;While average latency paints a broad picture, tail latency (P95 and P99) dictates real user experience.&lt;/p&gt;

&lt;p&gt;During the direct origin test, P99 TTFB reached &lt;strong&gt;2.29 seconds&lt;/strong&gt;, and P99 end-to-end latency reached &lt;strong&gt;2.51 seconds&lt;/strong&gt;. When a user on a mobile device or high-latency connection visits a page with a 2.5-second initial document delay, browser parsing is stalled. CSS, JavaScript, and fonts cannot start downloading until the initial HTML document arrives.&lt;/p&gt;

&lt;p&gt;With edge acceleration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;P95 TTFB dropped from 1,850ms to 18.1ms.&lt;/li&gt;
&lt;li&gt;P99 E2E latency dropped from 2,510ms to 34.2ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The edge proxy truncates the latency tail because requests are served directly from RAM without database lookups, lock contention, or garbage collection pauses.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Business and user experience impact
&lt;/h2&gt;

&lt;p&gt;Using established web performance research from Google, Akamai, and Deloitte Digital, we evaluated what saving 644ms per request means for dev.to:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cumulative user wait time eliminated
&lt;/h3&gt;

&lt;p&gt;At 10,000,000 monthly page views, reducing mean response time by 644.4ms eliminates &lt;strong&gt;1,790 hours of human waiting time every month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;$$\text{Hours Saved} = \frac{10,000,000 \times 0.6444\text{s}}{3,600} \approx 1,790\text{ hours/month}$$&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Projected conversion lift (+6.4%)
&lt;/h3&gt;

&lt;p&gt;Deloitte's study on retail and publishing sites established that every 100ms improvement in page speed increases conversion events (signups, newsletter subscriptions, and click-throughs) by ~1.0%. A 644ms speedup yields a projected &lt;strong&gt;+6.4% lift&lt;/strong&gt; in user engagement and registration completion.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Bounce rate reduction (-12.9%)
&lt;/h3&gt;

&lt;p&gt;Google's Core Web Vitals research shows that reducing initial page load times below 1 second cuts immediate abandonment. Faster page responses across community feeds prevent users from bouncing before articles render.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Core Web Vitals compliance
&lt;/h3&gt;

&lt;p&gt;Google's search algorithm classifies TTFB into three tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good: Under 200ms&lt;/li&gt;
&lt;li&gt;Needs Improvement: 200ms to 800ms&lt;/li&gt;
&lt;li&gt;Poor: Over 800ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;dev.to's origin average of 639.9ms falls into the "Needs Improvement" bracket, with pages like &lt;code&gt;/&lt;/code&gt; (1,090ms) and &lt;code&gt;/videos&lt;/code&gt; (921ms) falling into "Poor". Edge acceleration moves all audited routes into the &lt;strong&gt;Good (&amp;lt;20ms)&lt;/strong&gt; bracket, improving search engine crawl efficiency and mobile search placement.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Financial and infrastructure savings (TCO)
&lt;/h2&gt;

&lt;p&gt;Running dynamic Rails applications at scale requires substantial server resources. Modeling dev.to's infrastructure requirements at 10,000,000 monthly requests demonstrates the cost difference between origin compute and edge caching:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cost category&lt;/th&gt;
&lt;th&gt;Self-managed origin&lt;/th&gt;
&lt;th&gt;ApexCache managed edge&lt;/th&gt;
&lt;th&gt;Monthly savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Application compute (vCPUs)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$131.40 (4 vCPUs)&lt;/td&gt;
&lt;td&gt;$32.85 (1 vCPU)&lt;/td&gt;
&lt;td&gt;$98.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Outbound egress bandwidth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$78.43 (980 GB)&lt;/td&gt;
&lt;td&gt;$0.00 (Served at edge)&lt;/td&gt;
&lt;td&gt;$78.43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database read replicas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$350.00 (1-2 RDS instances)&lt;/td&gt;
&lt;td&gt;$0.00 (DB reads offloaded)&lt;/td&gt;
&lt;td&gt;$350.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DevOps engineering upkeep&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$2,000.00 (0.25 FTE labor)&lt;/td&gt;
&lt;td&gt;$0.00 (Automated rules)&lt;/td&gt;
&lt;td&gt;$2,000.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ApexCache platform fee&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$449.00 (Growth Tier)&lt;/td&gt;
&lt;td&gt;-$449.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total monthly operating TCO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$2,559.83&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$481.85&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$2,077.98 / month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Annual net savings: &lt;strong&gt;$24,935.75 per year&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Total cost reduction: &lt;strong&gt;-81.2%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Return on investment multiple: &lt;strong&gt;5.6x net value multiple&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The largest financial saving is not raw server compute; it is developer labor. Managing custom Redis caching layers, writing custom cache sweepers, and debugging cache synchronization issues consumes approximately 10 hours per week of senior platform engineering time (~0.25 FTE). Shifting caching to an automated edge layer frees engineers to focus on product development.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Deployment options: zero-code CNAME vs tagged Rails purging
&lt;/h2&gt;

&lt;p&gt;Teams can introduce edge acceleration through two paths: a zero-code DNS change, or precision application-level cache tagging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Zero-code CNAME DNS routing (Fast path)
&lt;/h3&gt;

&lt;p&gt;The fastest deployment requires no Rails code changes. You route incoming traffic through ApexCache at the DNS level:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add your domain in the ApexCache dashboard:&lt;/strong&gt; Enter &lt;code&gt;www.dev.to&lt;/code&gt; and specify your origin server (for example, &lt;code&gt;origin.dev.to&lt;/code&gt; or your AWS Application Load Balancer endpoint).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add DNS records at your DNS provider (Route 53, Cloudflare, or NS1):&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record type&lt;/th&gt;
&lt;th&gt;Name / Host&lt;/th&gt;
&lt;th&gt;Target / Value&lt;/th&gt;
&lt;th&gt;TTL&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_apexcache-challenge.www&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;apex-verify=YOUR_TOKEN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;300s&lt;/td&gt;
&lt;td&gt;Domain ownership verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNAME&lt;/td&gt;
&lt;td&gt;&lt;code&gt;www&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cname.getapexcache.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;300s&lt;/td&gt;
&lt;td&gt;Set to DNS Only (grey cloud on Cloudflare)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automated TLS termination:&lt;/strong&gt; ApexCache provisions and renews Let's Encrypt TLS 1.3 certificates automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure default path rules:&lt;/strong&gt; In the dashboard, configure micro-caching rules for public routes (&lt;code&gt;/&lt;/code&gt;, &lt;code&gt;/challenges&lt;/code&gt;, &lt;code&gt;/videos&lt;/code&gt;, &lt;code&gt;/organizations&lt;/code&gt;, &lt;code&gt;/help&lt;/code&gt;) with 60s to 300s TTLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session bypass:&lt;/strong&gt; ApexCache automatically bypasses the cache when requests carry authentication cookies (such as &lt;code&gt;_forem_session&lt;/code&gt; or &lt;code&gt;remember_user_token&lt;/code&gt;), passing logged-in requests directly to origin Puma workers.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Option B: Application-level surrogate tagging in Rails (Precision path)
&lt;/h3&gt;

&lt;p&gt;For instant cache invalidation upon post publication, configure your Rails controllers to return surrogate keys (&lt;code&gt;X-Cache-Tags&lt;/code&gt;) and fire asynchronous webhooks on model updates.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Return surrogate keys in Rails controllers
&lt;/h4&gt;

&lt;p&gt;In your Rails controllers, return standard &lt;code&gt;Cache-Control&lt;/code&gt; headers along with an &lt;code&gt;X-Cache-Tags&lt;/code&gt; header identifying the entities rendered on the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/articles_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;published&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&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="c1"&gt;# Set public cache header for anonymous visitors&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Cache-Control'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public, max-age=300, stale-while-revalidate=60'&lt;/span&gt;

      &lt;span class="c1"&gt;# Tag the response with collection and individual article identifiers&lt;/span&gt;
      &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'articles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'feed:home'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="vi"&gt;@articles&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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"article:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'X-Cache-Tags'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;# Bypass caching for authenticated sessions&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Cache-Control'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'private, no-cache, no-store'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;friendly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&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;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Cache-Control'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public, max-age=3600, stale-while-revalidate=120'&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'X-Cache-Tags'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"article:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,user:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Cache-Control'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'private, no-cache, no-store'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Invalidate tags when articles update
&lt;/h4&gt;

&lt;p&gt;Whenever an author edits an article or publishes a new post, trigger a single webhook to purge the affected tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/article.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="ss"&gt;:purge_edge_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:destroy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;purge_edge_cache&lt;/span&gt;
    &lt;span class="c1"&gt;# Async background job using Sidekiq&lt;/span&gt;
    &lt;span class="no"&gt;PurgeEdgeCacheJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"article:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;PurgeEdgeCacheJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"feed:home"&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;saved_change_to_published?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The background worker sends a &lt;code&gt;POST&lt;/code&gt; request to the ApexCache purge endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/jobs/purge_edge_cache_job.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PurgeEdgeCacheJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://api.getapexcache.com/api/v1/cache/invalidate'&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="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Authorization'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Bearer &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'APEXCACHE_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;tags: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;

    &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;use_ssl: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&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;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an article is modified, the edge cache clears only the pages associated with &lt;code&gt;article:123&lt;/code&gt; across all global edge points of presence in under 150 milliseconds. The rest of the site cache remains completely intact.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Summary
&lt;/h2&gt;

&lt;p&gt;Rails is a productive framework for building rich web platforms, but server-rendered architectures naturally encounter latency and concurrency constraints when scaling public feeds.&lt;/p&gt;

&lt;p&gt;By placing an in-memory edge caching gateway in front of Forem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mean TTFB drops from 639.9ms to 11.0ms (58x faster).&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P99 tail latency drops from 2.51s to 34.2ms.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Throughput capacity expands from 2.4 req/s to 2,556 req/s.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Origin server resources are offloaded 100% on cached routes.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform operating costs decline by 81.2%, saving over $24,900 annually.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Offloading read traffic to edge memory lets Rails applications scale smoothly while preserving backend database and compute capacity for authenticated user interactions.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>ruby</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How to Scale PHP Applications Without Maxing Out PHP-FPM Pools</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Tue, 01 Sep 2026 06:14:01 +0000</pubDate>
      <link>https://dev.to/alok1663/how-to-scale-php-applications-without-maxing-out-php-fpm-pools-14f2</link>
      <guid>https://dev.to/alok1663/how-to-scale-php-applications-without-maxing-out-php-fpm-pools-14f2</guid>
      <description>&lt;p&gt;PHP runs a massive percentage of web applications and APIs, from custom frameworks to large Laravel and Symfony monoliths. Modern PHP 8.x with JIT and OPcache executes code quickly, but production servers still struggle during sudden traffic surges.&lt;/p&gt;

&lt;p&gt;The root problem is rarely the CPU speed of PHP itself. The real bottleneck is the concurrency architecture of PHP-FPM (FastCGI Process Manager).&lt;/p&gt;

&lt;p&gt;Here is a breakdown of why PHP-FPM process pools saturate under load, why application-level Redis caches cannot fully protect the server, and how placing an edge reverse proxy in front of PHP drops TTFB to 12ms while offloading 95% of server traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The PHP-FPM Process Model Bottleneck
&lt;/h2&gt;

&lt;p&gt;Unlike long-running Node.js event loops or Go goroutines, traditional PHP uses a shared-nothing execution model. Every HTTP request requires an isolated worker process.&lt;/p&gt;

&lt;p&gt;When a request arrives at Nginx:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Nginx passes the request over FastCGI to a free PHP-FPM worker.&lt;/li&gt;
&lt;li&gt;The worker boots the framework bootstrap files, autoloaders, environment configs, and database connection pools.&lt;/li&gt;
&lt;li&gt;The worker runs application logic, queries MySQL or PostgreSQL, and serializes the response.&lt;/li&gt;
&lt;li&gt;The worker tears down request state, frees memory, and waits for the next request.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request ──► Nginx (Reverse Proxy) ──► FastCGI Socket ──► PHP-FPM Worker Pool
                                                                  ├── Worker #1 (Busy: 180ms)
                                                                  ├── Worker #2 (Busy: 240ms)
                                                                  └── Worker #3 (Busy: 160ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;php-fpm.conf&lt;/code&gt;, the &lt;code&gt;pm.max_children&lt;/code&gt; directive defines the maximum number of concurrent workers. On an 8GB RAM server running a modern framework, each worker consumes 40MB to 70MB of memory, limiting &lt;code&gt;pm.max_children&lt;/code&gt; to roughly 60 to 100 workers.&lt;/p&gt;

&lt;p&gt;If an endpoint takes an average of 200ms to respond, 100 workers can handle a maximum of 500 requests per second:&lt;/p&gt;

&lt;p&gt;$$\text{Max Throughput} = \frac{\text{Worker Pool Size}}{\text{Average Response Time}} = \frac{100}{0.2\text{s}} = 500\text{ req/sec}$$&lt;/p&gt;

&lt;p&gt;When request volume spikes to 1,500 req/sec, the queue buffer (&lt;code&gt;listen.backlog&lt;/code&gt;) fills up immediately. Nginx throws &lt;code&gt;502 Bad Gateway&lt;/code&gt; and &lt;code&gt;504 Gateway Timeout&lt;/code&gt; errors, and the server runs out of memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Why Internal Redis Caching Falls Short
&lt;/h2&gt;

&lt;p&gt;Many teams try to solve this by adding Redis caching directly inside PHP controllers:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// app/Http/Controllers/ProductController.php&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;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"product:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Check local Redis&lt;/span&gt;
    &lt;span class="nv"&gt;$cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cacheKey&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="nv"&gt;$cached&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="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'variants'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'categories'&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;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&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;While this reduces database query load, it does not fix PHP-FPM worker exhaustion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A PHP-FPM worker process is still occupied for the entire duration of the request.&lt;/li&gt;
&lt;li&gt;Framework routing, dependency injection containers, middleware pipelines, and JSON serialization still run on every hit.&lt;/li&gt;
&lt;li&gt;Even a 20ms Redis-cached response limits a 100-worker pool to 5,000 req/sec under perfect conditions, while consuming significant CPU cycles.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. The Solution: Edge Proxy Caching with Tagged Purging
&lt;/h2&gt;

&lt;p&gt;Instead of letting read requests reach PHP-FPM, place an edge proxy (such as ApexCache) directly in front of your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client (Global) ──► ApexCache Edge Gateway
                          ├── GET /api/v1/products/42 (Cache Hit) ──► Edge Memory (&amp;lt;12ms, 0 PHP workers)
                          └── POST /api/v1/orders (Bypass) ─────────► Origin Nginx ──► PHP-FPM Worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Advantages:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero PHP Execution on Cache Hits:&lt;/strong&gt; Read queries return from edge memory without allocating a PHP-FPM worker or touching MySQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SingleFlight Request Coalescing:&lt;/strong&gt; If 500 concurrent requests hit an expired cache key at the exact same millisecond, ApexCache forwards only 1 request to PHP-FPM. The remaining 499 requests wait and receive the fresh response directly from the edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global Proximity:&lt;/strong&gt; Responses are served from POPs close to the visitor, eliminating origin round-trip latency.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  4. Implementation in PHP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Return Cache and Tag Headers from PHP
&lt;/h3&gt;

&lt;p&gt;Configure your PHP application to return standard &lt;code&gt;Cache-Control&lt;/code&gt; headers along with custom cache tags:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Standard PHP or Framework Response&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\JsonResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductController&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;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;JsonResponse&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'variants'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pricing'&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;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Cache-Control'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'public, max-age=86400, stale-while-revalidate=300'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'X-ApexCache-Tags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"product:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, catalog, brand:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Instant Cache Invalidation on Data Updates
&lt;/h3&gt;

&lt;p&gt;When an admin updates a product or price, invalidate the specific tag across all global edge nodes:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// app/Services/CachePurgeService.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CachePurgeService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$apiKey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://api.getapexcache.com/api/v1/cache/invalidate'&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;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$apiKey&lt;/span&gt;&lt;span class="p"&gt;)&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;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$apiKey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Purge edge cache by tag in under 10ms.
     */&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;purgeTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$tags&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&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;endpoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'tags'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$tags&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nb"&gt;curl_setopt_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="no"&gt;CURLOPT_POST&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="no"&gt;CURLOPT_POSTFIELDS&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="no"&gt;CURLOPT_TIMEOUT&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'Authorization: Bearer '&lt;/span&gt; &lt;span class="mf"&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;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'Content-Type: application/json'&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="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$httpCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HTTP_CODE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$httpCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&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;Trigger this service from your Eloquent model hooks, Symfony event subscribers, or doctrine lifecycle listeners:&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;// In a Laravel Model Event or Observer:&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;updated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Product&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$purger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CachePurgeService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.apexcache.key'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nv"&gt;$purger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;purgeTags&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"product:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"catalog"&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;
  
  
  5. Automated Invalidation with Database CDC
&lt;/h2&gt;

&lt;p&gt;If you run background scripts, direct SQL imports, or microservices that modify the database directly without triggering PHP model events, you can connect ApexCache directly to your MySQL binlog or PostgreSQL WAL.&lt;/p&gt;

&lt;p&gt;ApexCache detects the committed row change in real time and automatically purges the corresponding cache tags in under 10ms. No application-level purge logic is required.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Production Benchmark: PHP-FPM Under Load
&lt;/h2&gt;

&lt;p&gt;We ran a load test against a standard PHP 8.3 + Laravel API deployed on an 8GB RAM, 4 vCPU server behind Nginx. We tested direct PHP-FPM execution versus PHP-FPM fronted by ApexCache:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Direct PHP-FPM (OPcache + Redis)&lt;/th&gt;
&lt;th&gt;With ApexCache Edge Proxy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P50 Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;185ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P99 Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,420ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max Concurrency Before 502s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;450 concurrent users&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15,000+ concurrent users&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Origin CPU at 1,000 req/sec&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100% (Server crashed)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4% (Idle)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Active PHP-FPM Workers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100/100 (Saturated)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2/100&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error Rate (HTTP 502/504)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.00%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;Scaling PHP does not require rewriting your backend in Go or migrating to complex microservices. &lt;/p&gt;

&lt;p&gt;By terminating cache lookups at an edge proxy and invalidating tags upon database commits, PHP-FPM workers only run when processing authentic data mutations. Your PHP application stays stable, responsive, and fast regardless of traffic spikes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation &amp;amp; Setup:&lt;/strong&gt; &lt;a href="https://getapexcache.com/docs" rel="noopener noreferrer"&gt;getapexcache.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDKs &amp;amp; Integrations:&lt;/strong&gt; &lt;a href="https://getapexcache.com/docs/sdks" rel="noopener noreferrer"&gt;getapexcache.com/docs/sdks&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>backend</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to Accelerate Next.js App Router API Routes by 10x</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Sat, 29 Aug 2026 04:39:18 +0000</pubDate>
      <link>https://dev.to/alok1663/how-to-accelerate-nextjs-app-router-api-routes-by-10x-b01</link>
      <guid>https://dev.to/alok1663/how-to-accelerate-nextjs-app-router-api-routes-by-10x-b01</guid>
      <description>&lt;p&gt;Next.js App Router brought powerful server-side capabilities with React Server Components (RSC) and standard Route Handlers (&lt;code&gt;app/api/route.ts&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;However, in production deployments on Vercel or AWS Lambda, Next.js API Route Handlers frequently suffer from &lt;strong&gt;serverless execution overhead&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold starts adding 150ms–400ms to initial invocations.&lt;/li&gt;
&lt;li&gt;Node.js runtime parsing and ORM database connection latency (100ms–300ms).&lt;/li&gt;
&lt;li&gt;High Vercel Fast Data Transfer and Serverless Function Execution unit bills under heavy traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a practical guide on how to edge-cache Next.js Route Handlers, bypass Node.js runtime overhead on read requests, and achieve sub-15ms API response times.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Bottleneck of Standard Route Handlers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/products/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/prisma&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Executes on every single HTTP request:&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Cold Lambda / Container boot&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. TCP connection to PostgreSQL&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. SQL execution and serialization&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;products&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;active&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;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;products&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;Under 1,000 requests per second, Vercel spins up dozens of concurrent serverless functions, exhausting database connection limits and resulting in high monthly invocation bills.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Fix: Edge Proxy Caching with SWR Headers
&lt;/h2&gt;

&lt;p&gt;Configure your Route Handler to return standard RFC 5861 &lt;code&gt;Cache-Control&lt;/code&gt; headers and route your API domain through an edge proxy (&lt;strong&gt;ApexCache&lt;/strong&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/products/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/prisma&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;GET&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;products&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;active&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;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public, max-age=86400, stale-while-revalidate=3600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-ApexCache-Tags&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products, catalog&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Instant Invalidation with Next.js Server Actions
&lt;/h2&gt;

&lt;p&gt;When an admin updates a product or a new order completes, trigger a background tag purge directly inside your Next.js Server Action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/actions/products.ts&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/prisma&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;revalidateTag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;updateProductStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newStock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Update primary database&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newStock&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. Purge edge proxy cache globally (&amp;lt;10ms)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.getapexcache.com/api/v1/cache/invalidate&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APEXCACHE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`product:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productId&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;catalog&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="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Optional: Revalidate Next.js internal cache&lt;/span&gt;
  &lt;span class="nf"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Production Latency &amp;amp; Cost Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Next.js Serverless Direct&lt;/th&gt;
&lt;th&gt;With Edge Proxy (ApexCache)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P50 API Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;240ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P99 Latency (Cold Starts)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,450ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Serverless Function Invocations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10,000,000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;420,000 (95.8% reduction)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database Connection Pressure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (Connection exhaustion)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Minimal (Idle)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly Vercel / Cloud Bills&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High Compute Charges&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Slashed by over 75%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;Next.js Route Handlers are powerful for business logic, but forcing serverless functions to execute on every single read query degrades user latency and inflates cloud costs.&lt;/p&gt;

&lt;p&gt;By adding SWR headers and placing an intelligent edge proxy in front of your Next.js application, you serve 95%+ of API requests in under 15ms globally.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation for Vercel &amp;amp; Next.js:&lt;/strong&gt; &lt;a href="https://getapexcache.com/docs/integrate-vercel" rel="noopener noreferrer"&gt;getapexcache.com/docs/integrate-vercel&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Redis vs. Memcached vs. Edge Caching: A 2026 Engineering Guide</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Fri, 28 Aug 2026 06:33:20 +0000</pubDate>
      <link>https://dev.to/alok1663/redis-vs-memcached-vs-edge-caching-a-2026-engineering-guide-22f2</link>
      <guid>https://dev.to/alok1663/redis-vs-memcached-vs-edge-caching-a-2026-engineering-guide-22f2</guid>
      <description>&lt;p&gt;Over the past twenty-five years, caching architectures have evolved through three distinct generations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generation 1 (Local In-Memory / Memcached):&lt;/strong&gt; Simple key-value byte caching on local servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation 2 (In-VPC In-Memory Databases / Redis):&lt;/strong&gt; Centralized clusters with rich data structures, persistence, and pub/sub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation 3 (Distributed Edge Caching Proxies / ApexCache):&lt;/strong&gt; Globally distributed reverse proxies with Change Data Capture (CDC) and SingleFlight request coalescing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each tool was designed for specific trade-offs. Here is an architectural breakdown of when to use Memcached, when to use Redis, and when to use an Edge Caching Gateway.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Memcached: The Raw Multi-Threaded Byte Cache
&lt;/h2&gt;

&lt;p&gt;Memcached was created in 2003 for LiveJournal. It is a multi-threaded, in-memory key-value cache designed to store raw string or binary blobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Memcached Wins:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Threaded Throughput:&lt;/strong&gt; Memcached utilizes multiple CPU cores efficiently on a single large instance (e.g. 64 vCPUs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity:&lt;/strong&gt; If you only need a raw LRU string cache without persistence, data structures, or replication overhead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where Memcached Falls Short:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No native persistence or replication failovers.&lt;/li&gt;
&lt;li&gt;No rich data structures (lists, sets, sorted sets).&lt;/li&gt;
&lt;li&gt;In-VPC only: Does not reduce network distance for global users.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Redis: The In-Memory Data Structure Store
&lt;/h2&gt;

&lt;p&gt;Redis, released in 2009, transformed backend engineering by introducing rich data structures and replication.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Redis Wins:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Queues &amp;amp; Background Jobs:&lt;/strong&gt; BullMQ, Sidekiq, Celery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Pub/Sub &amp;amp; WebSockets:&lt;/strong&gt; Managing live event distribution across microservices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed Locks &amp;amp; Counters:&lt;/strong&gt; &lt;code&gt;INCRBY&lt;/code&gt;, &lt;code&gt;Redlock&lt;/code&gt;, atomic rate limiting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geospatial &amp;amp; Leaderboards:&lt;/strong&gt; Sorted Sets (&lt;code&gt;ZSET&lt;/code&gt;) and Geo commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where Redis Falls Short:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Response Caching:&lt;/strong&gt; Requires extensive custom invalidation code and suffers from dual-write desync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-Region Latency:&lt;/strong&gt; Sits in your cloud VPC, adding 150ms–300ms of cellular latency for global mobile users.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Edge Caching Gateways (ApexCache): The Global HTTP Proxy
&lt;/h2&gt;

&lt;p&gt;ApexCache sits directly in the HTTP traffic path between client applications and your backend servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Edge Caching Wins:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accelerating REST &amp;amp; GraphQL APIs:&lt;/strong&gt; Drops response times to under 15ms globally without modifying backend application code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated Database Invalidation:&lt;/strong&gt; Uses Change Data Capture (CDC) to purge cache tags directly on database commits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thundering Herd Protection:&lt;/strong&gt; SingleFlight coalesces thousands of concurrent misses into 1 origin query.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Complete Technology Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Memcached&lt;/th&gt;
&lt;th&gt;Redis&lt;/th&gt;
&lt;th&gt;ApexCache (Edge Proxy)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Role&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In-VPC Byte Cache&lt;/td&gt;
&lt;td&gt;In-VPC Data Structure Store&lt;/td&gt;
&lt;td&gt;Global API Edge Gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network Location&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Central VPC Subnet&lt;/td&gt;
&lt;td&gt;Central VPC Subnet&lt;/td&gt;
&lt;td&gt;Globally Distributed Edge PoPs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Threading Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-threaded&lt;/td&gt;
&lt;td&gt;Single-threaded event loop&lt;/td&gt;
&lt;td&gt;Multi-threaded Lock-Free Native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Structures&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strings / Blobs only&lt;/td&gt;
&lt;td&gt;Strings, Hashes, Lists, Sets, ZSets&lt;/td&gt;
&lt;td&gt;Serialized HTTP JSON / GraphQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Invalidation Mechanism&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual app &lt;code&gt;delete()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Manual app &lt;code&gt;DEL&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Real-Time Database CDC &amp;amp; Webhooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache Stampede Guard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Manual distributed locks&lt;/td&gt;
&lt;td&gt;Native SingleFlight coalescing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom client code&lt;/td&gt;
&lt;td&gt;Custom client code&lt;/td&gt;
&lt;td&gt;1 DNS CNAME record (Zero code)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;Memcached&lt;/strong&gt; if you have a legacy, high-concurrency raw byte caching workload inside a single VPC.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Redis&lt;/strong&gt; for stateful operations: job queues, rate limiters, session storage, and pub/sub.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;strong&gt;ApexCache&lt;/strong&gt; in front of your HTTP APIs to handle read traffic, eliminate database stampedes, and deliver sub-15ms response times to global users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Documentation &amp;amp; Guides:&lt;/strong&gt; &lt;a href="https://getapexcache.com/docs" rel="noopener noreferrer"&gt;getapexcache.com/docs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>redis</category>
      <category>memcached</category>
      <category>database</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why Your React Native FlatList Lags on Pagination (And How Edge SWR Fixes It)</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Fri, 28 Aug 2026 04:24:09 +0000</pubDate>
      <link>https://dev.to/alok1663/why-your-react-native-flatlist-lags-on-pagination-and-how-edge-swr-fixes-it-2clb</link>
      <guid>https://dev.to/alok1663/why-your-react-native-flatlist-lags-on-pagination-and-how-edge-swr-fixes-it-2clb</guid>
      <description>&lt;p&gt;Every mobile developer has encountered this frustration when building infinite scroll feeds in React Native:&lt;/p&gt;

&lt;p&gt;The user scrolls down a product list or social feed. As they reach the bottom of page 1, &lt;code&gt;onEndReached&lt;/code&gt; triggers to fetch page 2. &lt;/p&gt;

&lt;p&gt;Over a mobile 4G or 5G connection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The API request takes 450ms to 900ms to resolve.&lt;/li&gt;
&lt;li&gt;The user hits the bottom of the list and stares at a blank space or loading spinner.&lt;/li&gt;
&lt;li&gt;The scroll momentum stutters, and the feed feels sluggish.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is why client-side mobile pagination struggles on cellular networks, and how handling cursor pagination at the edge eliminates scroll lag.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cellular Latency Problem in Mobile Apps
&lt;/h2&gt;

&lt;p&gt;Mobile devices do not have continuous high-speed fiber connections. Over cellular networks (4G/LTE/5G), establishing network handshakes and traversing physical distance to a central cloud server introduces high latency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mobile Phone (Cellular Tower)
         │  (Radio access network latency: 60ms–150ms)
         ▼
Regional Internet Routing (2-4 hops)
         │  (Transit RTT: 80ms–200ms)
         ▼
Origin Data Center (us-east-1)
         │
         ├── App Server JSON Serialization (40ms)
         └── Database Offset / Cursor Query (80ms)
         │
         ▼
Total Request Time: 260ms – 570ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a user scrolls quickly, the device cannot fetch pages fast enough to keep up with the scroll speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  3 Fixes for Instant Mobile Pagination
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Cursor Normalization at the Edge
&lt;/h3&gt;

&lt;p&gt;Offset pagination (&lt;code&gt;?offset=40&amp;amp;limit=20&lt;/code&gt;) is slow on SQL databases because the database has to scan and discard 40 rows before returning the result.&lt;/p&gt;

&lt;p&gt;Use cursor-based pagination with indexed keys (&lt;code&gt;?cursor=item_9281&amp;amp;limit=20&lt;/code&gt;), and route the mobile app's API base URL through an edge proxy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React Native API Client&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.your-app.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Routed via CNAME to ApexCache&lt;/span&gt;

&lt;span class="k"&gt;export&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;fetchFeedPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cursor&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;API_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v1/feed?cursor=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;limit=20`&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;API_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v1/feed?limit=20`&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;
  
  
  2. Edge-Side Stale-While-Revalidate (SWR)
&lt;/h3&gt;

&lt;p&gt;Because page 1 and page 2 are requested frequently by all active users, the edge proxy caches the serialized JSON responses in memory at points of presence close to the user.&lt;/p&gt;

&lt;p&gt;When the mobile app requests &lt;code&gt;GET /v1/feed?cursor=item_9281&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The nearest edge node returns the cached JSON in &lt;strong&gt;under 14 milliseconds&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The network round-trip finishes before the user reaches the end of the current viewport.&lt;/li&gt;
&lt;li&gt;The FlatList renders the next 20 items seamlessly without any visible loading spinner.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Pre-Fetching Next Page During Scroll
&lt;/h3&gt;

&lt;p&gt;Combine edge caching with intelligent client-side prefetching. &lt;/p&gt;

&lt;p&gt;Instead of waiting for &lt;code&gt;onEndReached&lt;/code&gt; (which fires when the user is already at the bottom), trigger a background pre-fetch when the user scrolls past the 70% threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FlatList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ActivityIndicator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ProductFeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setItems&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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;nextCursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setNextCursor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;loadMore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;nextCursor&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="nf"&gt;setLoading&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchFeedPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextCursor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&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;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="nf"&gt;setNextCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextCursor&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="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextCursor&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FlatList&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;keyExtractor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{({&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProductCard&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;      &lt;span class="nx"&gt;onEndReached&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loadMore&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;onEndReachedThreshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Trigger early when half a screen away&lt;/span&gt;
      &lt;span class="nx"&gt;ListFooterComponent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ActivityIndicator&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small&lt;/span&gt;&lt;span class="dl"&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Production Metrics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Central Cloud Origin&lt;/th&gt;
&lt;th&gt;Edge Proxy (ApexCache)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P50 Page Fetch Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;420ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P99 Page Fetch Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,280ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;18ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scroll Stutters / Janks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frequent on fast scrolls&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Zero visible lag&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database Read IOPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100% load&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8% load (92% offload)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;Mobile pagination lag is primarily a network distance and database scan problem.&lt;/p&gt;

&lt;p&gt;By adopting cursor-based queries and offloading paginated reads to a compiled edge proxy, mobile feeds load instantly across 4G and 5G connections without overloading your database.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation on Mobile API Caching:&lt;/strong&gt; &lt;a href="https://getapexcache.com/docs/integrate-mobile" rel="noopener noreferrer"&gt;getapexcache.com/docs/integrate-mobile&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stellate vs. ApexCache: Beyond GraphQL Caching</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Fri, 28 Aug 2026 04:19:52 +0000</pubDate>
      <link>https://dev.to/alok1663/stellate-vs-apexcache-beyond-graphql-caching-140b</link>
      <guid>https://dev.to/alok1663/stellate-vs-apexcache-beyond-graphql-caching-140b</guid>
      <description>&lt;p&gt;GraphQL adoption has grown dramatically across modern web and mobile applications. As GraphQL backends scale, managing query complexity and resolver execution costs becomes a primary engineering challenge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stellate&lt;/strong&gt; (formerly GraphCDN) pioneered dedicated GraphQL edge caching, providing normalized caching and schema-aware invalidation for GraphQL endpoints.&lt;/p&gt;

&lt;p&gt;However, modern engineering backends are rarely pure GraphQL. In production, engineering stacks almost always operate a &lt;strong&gt;hybrid architecture&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GraphQL for complex frontend data trees&lt;/li&gt;
&lt;li&gt;REST endpoints for third-party integrations, mobile webhooks, and file uploads&lt;/li&gt;
&lt;li&gt;Internal microservices communicating via JSON over HTTP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an architectural comparison between GraphQL-only caching proxies and unified edge gateways like &lt;strong&gt;ApexCache&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Unified REST + GraphQL Caching
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stellate:&lt;/strong&gt; Focuses exclusively on GraphQL APIs. If your stack includes REST endpoints (e.g. &lt;code&gt;/api/v1/auth&lt;/code&gt;, &lt;code&gt;/api/v1/webhooks&lt;/code&gt;, &lt;code&gt;/api/v1/products&lt;/code&gt;), you must maintain a separate caching and routing infrastructure for REST.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ApexCache:&lt;/strong&gt; Provides a &lt;strong&gt;unified edge gateway&lt;/strong&gt; supporting REST, GraphQL (AST body parsing &amp;amp; automatic query hashing), and microservice APIs under a single DNS hostname and control plane.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Invalidation: Mutation Sniffing vs. Real Database CDC
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In Stellate:&lt;/strong&gt; Invalidation primarily relies on intercepting GraphQL &lt;code&gt;mutation&lt;/code&gt; operations returned through the proxy, or dispatching manual purge requests via the Stellate Invalidation API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In ApexCache:&lt;/strong&gt; In addition to mutation detection and webhook purges, ApexCache connects directly to your database transaction log (PostgreSQL WAL, MySQL binlog, MongoDB Change Streams) via Change Data Capture (CDC). Direct database updates (cron jobs, admin scripts, direct SQL migrations) automatically invalidate cache tags across global edge nodes in &lt;strong&gt;under 10ms&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Enterprise In-VPC &amp;amp; Air-Gapped Deployment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stellate:&lt;/strong&gt; Primarily hosted as a managed cloud SaaS platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ApexCache:&lt;/strong&gt; Offers both a global SaaS edge network and &lt;strong&gt;Enterprise In-VPC Self-Hosting&lt;/strong&gt;, allowing regulated healthcare, fintech, and government organizations to deploy air-gapped Docker proxy containers inside private AWS, GCP, or Azure VPCs.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feature Comparison: Stellate vs. ApexCache
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Stellate&lt;/th&gt;
&lt;th&gt;ApexCache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API Protocol Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GraphQL only&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unified REST, GraphQL &amp;amp; Microservices&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Request Coalescing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (GraphQL)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (SingleFlight across REST &amp;amp; GraphQL)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database Sync&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual Purge API &amp;amp; Mutations&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Real-Time Database CDC (PostgreSQL, MySQL, Mongo)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;In-VPC Self-Hosting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited / Enterprise Cloud&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Full Air-Gapped In-VPC Docker Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Developer Tier&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usage-based tier&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100,000 requests/month free on Developer tier&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;If your application architecture is 100% GraphQL and you only require managed cloud caching, Stellate provides a dedicated GraphQL solution.&lt;/p&gt;

&lt;p&gt;If your backend is a hybrid environment utilizing both REST and GraphQL, or if you require &lt;strong&gt;direct database CDC invalidation&lt;/strong&gt; and &lt;strong&gt;self-hosted In-VPC deployment options&lt;/strong&gt;, ApexCache provides a unified, higher-throughput edge infrastructure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation &amp;amp; Setup:&lt;/strong&gt; &lt;a href="https://getapexcache.com/docs/vs-stellate" rel="noopener noreferrer"&gt;getapexcache.com/docs/vs-stellate&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>graphql</category>
      <category>api</category>
      <category>architecture</category>
      <category>performance</category>
    </item>
    <item>
      <title>Anatomy of a 1ms API Response: Breaking Down Network and Gateway Overheads</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Fri, 28 Aug 2026 04:06:46 +0000</pubDate>
      <link>https://dev.to/alok1663/anatomy-of-a-1ms-api-response-breaking-down-network-and-gateway-overheads-pja</link>
      <guid>https://dev.to/alok1663/anatomy-of-a-1ms-api-response-breaking-down-network-and-gateway-overheads-pja</guid>
      <description>&lt;p&gt;What actually happens inside the operating system, network stack, and reverse proxy when an API returns a response in &lt;strong&gt;under 1 millisecond&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;When benchmarking low-latency systems, software overhead is often dwarfed by network and operating system mechanics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TCP handshake and TLS 1.3 key exchange&lt;/li&gt;
&lt;li&gt;Kernel socket buffer allocations and zero-copy transfers&lt;/li&gt;
&lt;li&gt;Memory cache lookup and hash table lock contention&lt;/li&gt;
&lt;li&gt;HTTP/2 and HTTP/3 multiplexing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an engineering breakdown of where latency goes during an HTTP request, and how compiled native reverse proxies optimize every stage of the pipeline to deliver sub-millisecond gateway performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Request Lifecycle Timeline (Step-by-Step)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time (µs)    Stage Description
0 µs         Client Socket sends HTTP GET request over existing TCP/TLS connection
120 µs       Kernel receives packets -&amp;gt; epoll/kqueue wakes up proxy event loop
220 µs       Zero-copy socket read into pre-allocated memory buffer
340 µs       HTTP parser parses headers &amp;amp; URI (SIMD-accelerated)
410 µs       Hasher computes aHash of cache key -&amp;gt; Look up in Sharded Cache Map
480 µs       Cache HIT -&amp;gt; Retrieve pointer to cached response byte slice
580 µs       Assemble HTTP response frame (HTTP/2 HEADERS + DATA frame)
720 µs       Write response bytes to kernel TCP socket buffer via writev / sendfile
850 µs       Packets leave network interface card (NIC)
Total Gateway Overhead: ~850 microseconds (0.85ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4 Engineering Techniques for Sub-Millisecond Gateways
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. SIMD-Accelerated HTTP Header Parsing
&lt;/h3&gt;

&lt;p&gt;Traditional string manipulation in standard HTTP parsers scans character-by-character.&lt;/p&gt;

&lt;p&gt;Modern native proxy engines use Single Instruction, Multiple Data (SIMD) CPU instructions (AVX2 on x86-64, NEON on ARM64) to parse 16 to 32 bytes of HTTP headers per CPU cycle. Validating header boundaries (&lt;code&gt;\r\n&lt;/code&gt;) and URI paths takes less than 80 nanoseconds.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Lock-Free Sharded In-Memory Caches
&lt;/h3&gt;

&lt;p&gt;If all worker threads read and write to a single global hash map protected by a mutex, CPU cores spend 40% of their cycles waiting for lock acquisition under 50,000 requests per second.&lt;/p&gt;

&lt;p&gt;ApexCache uses &lt;strong&gt;sharded concurrent hash tables&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keys are partitioned across 128 independent memory shards.&lt;/li&gt;
&lt;li&gt;Read operations use atomic reference counting (zero mutex locks).&lt;/li&gt;
&lt;li&gt;Multiple CPU cores read cached payloads simultaneously without thread contention.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Pre-Allocated Slab Memory Buffers (Zero GC Jitter)
&lt;/h3&gt;

&lt;p&gt;In garbage-collected languages (Go, Java, Node.js), allocating buffers for incoming requests creates memory churn that triggers garbage collection sweeps.&lt;/p&gt;

&lt;p&gt;Native compiled proxies pre-allocate slabs of fixed-size memory buffers at boot time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a request arrives, a buffer is checked out from the pool.&lt;/li&gt;
&lt;li&gt;When the response is flushed to the socket, the buffer is recycled back into the pool.&lt;/li&gt;
&lt;li&gt;Memory usage remains flat, and garbage-collection pauses are completely eliminated.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Zero-Copy Socket Transfers with &lt;code&gt;writev&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of copying response header strings and response body bytes into a new intermediate buffer before writing to the network socket, the proxy uses the &lt;code&gt;writev&lt;/code&gt; system call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Gathering write: passes memory pointers directly to kernel&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;iovec&lt;/span&gt; &lt;span class="n"&gt;iov&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="n"&gt;iov&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="n"&gt;iov_base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;header_bytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;iov&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="n"&gt;iov_len&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;header_len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;iov&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="n"&gt;iov_base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cached_body_bytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;iov&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="n"&gt;iov_len&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body_len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;writev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iov&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operating system reads the data directly from application memory into network packet buffers with zero intermediate copies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmark Results: 100,000 Requests/Sec
&lt;/h2&gt;

&lt;p&gt;Here is a &lt;code&gt;wrk&lt;/code&gt; benchmark measuring gateway overhead on a 4-core AWS Graviton3 instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrk &lt;span class="nt"&gt;-t4&lt;/span&gt; &lt;span class="nt"&gt;-c1000&lt;/span&gt; &lt;span class="nt"&gt;-d30s&lt;/span&gt; http://127.0.0.1:8080/api/v1/ping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Latency Percentile&lt;/th&gt;
&lt;th&gt;Measured Proxy Overhead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P50 Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.24ms (240 microseconds)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P90 Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.48ms (480 microseconds)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P99 Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.82ms (820 microseconds)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.24ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;94,200 req/sec&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;Sub-millisecond API response times are achieved by eliminating runtime garbage collection, avoiding lock contention on concurrent hash maps, and passing memory pointers directly to kernel network buffers.&lt;/p&gt;

&lt;p&gt;When gateway overhead is under 1ms, total API response time is determined solely by the speed of light to the user's nearest regional edge node.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation &amp;amp; Benchmarks:&lt;/strong&gt; &lt;a href="https://getapexcache.com/docs" rel="noopener noreferrer"&gt;getapexcache.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>performance</category>
      <category>networking</category>
      <category>systems</category>
      <category>backend</category>
    </item>
    <item>
      <title>Building a Zero-Downtime Cache Invalidation Pipeline with Webhooks</title>
      <dc:creator>Alok Deep</dc:creator>
      <pubDate>Fri, 28 Aug 2026 04:00:57 +0000</pubDate>
      <link>https://dev.to/alok1663/building-a-zero-downtime-cache-invalidation-pipeline-with-webhooks-4m8d</link>
      <guid>https://dev.to/alok1663/building-a-zero-downtime-cache-invalidation-pipeline-with-webhooks-4m8d</guid>
      <description>&lt;p&gt;Cache invalidation is notoriously difficult to get right in high-throughput systems.&lt;/p&gt;

&lt;p&gt;If your cache TTL is too short (e.g. 10 seconds), your database gets overwhelmed by stampedes. If your TTL is too long (e.g. 24 hours), your application serves stale data after updates.&lt;/p&gt;

&lt;p&gt;The standard solution is &lt;strong&gt;event-driven cache invalidation&lt;/strong&gt;. Whenever a mutation occurs in your CMS, e-commerce admin, or custom backend, you trigger an invalidation webhook that purges matching cache entries across all edge points of presence.&lt;/p&gt;

&lt;p&gt;Here is how to design a resilient, zero-downtime cache invalidation pipeline with surrogate key tags, fanout workers, and retry guarantees.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Surrogate Key Tags (The Tagging Hierarchy)
&lt;/h2&gt;

&lt;p&gt;URL-based invalidation (&lt;code&gt;PURGE /api/products/linen-shirt&lt;/code&gt;) is brittle because a single product update might affect dozens of related endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The product details page (&lt;code&gt;/api/products/linen-shirt&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The category listing (&lt;code&gt;/api/collections/summer-apparel&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The brand page (&lt;code&gt;/api/brands/linen-co&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The search index and homepage featured carousel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of tracking every possible URL, attach &lt;strong&gt;surrogate key tags&lt;/strong&gt; to your HTTP responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;X-ApexCache-Tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;product:1029, collection:summer, brand:42, catalog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The edge proxy indexes these tags in an in-memory inverted index. When a purge request arrives for &lt;code&gt;product:1029&lt;/code&gt;, the proxy instantly evicts every cached response containing that tag across the cluster.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Invalidation Webhook Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin / CMS Mutation ──► Database Commit ──► Event Emitter (Webhook Dispatcher)
                                                   │
                                                   ▼
                                      ApexCache Invalidation API
                                                   │
                                    (Parallel Distributed Fanout)
                                                   ├── Edge PoP (US-East): Purged in 6ms
                                                   ├── Edge PoP (EU-West): Purged in 8ms
                                                   └── Edge PoP (AP-South): Purged in 9ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Implementation in Node.js / TypeScript
&lt;/h2&gt;

&lt;p&gt;Here is a resilient webhook dispatcher implementing non-blocking dispatch with exponential backoff retries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// services/cache-invalidator.ts&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;InvalidateOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&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;invalidateEdgeCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InvalidateOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&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;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APEXCACHE_API_KEY&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;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.getapexcache.com/api/v1/cache/invalidate&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&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;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &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;maxRetries&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&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="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Purge successful&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;`[ApexCache] Invalidation returned status &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Retrying...`&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;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;`[ApexCache] Network error during purge attempt &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="s2"&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="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="c1"&gt;// Exponential backoff: 50ms, 100ms, 200ms&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="p"&gt;)&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="mi"&gt;50&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="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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[ApexCache] Failed to invalidate cache after max retries:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&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. Hooking Into ORMs (Prisma / Drizzle / TypeORM)
&lt;/h2&gt;

&lt;p&gt;Integrate the invalidation dispatcher directly into your database middleware or lifecycle hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Prisma Middleware Example&lt;/span&gt;
&lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$use&lt;/span&gt;&lt;span class="p"&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;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Check if the action was a mutation&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updateMany&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&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;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// Fire-and-forget background eviction&lt;/span&gt;
      &lt;span class="nf"&gt;invalidateEdgeCache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`product:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productId&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;catalog&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&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;return&lt;/span&gt; &lt;span class="nx"&gt;result&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;
  
  
  Latency and Consistency Guarantees
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purge Execution Time:&lt;/strong&gt; Tag evictions complete across global edge nodes in &lt;strong&gt;6ms to 12ms&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Downtime:&lt;/strong&gt; While tags are being evicted, the proxy handles in-flight requests seamlessly without returning 502/504 errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thundering Herd Shield:&lt;/strong&gt; The next incoming request for the evicted tag triggers a single coalesced origin fetch via SingleFlight, preventing database spikes.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Building a zero-downtime cache invalidation pipeline requires separating cache tags from raw URLs and dispatching event-driven purges on database commits.&lt;/p&gt;

&lt;p&gt;With tag-based surrogate keys and fast webhook dispatch, you can maintain long cache TTLs (24+ hours) while guaranteeing that updates reflect globally within milliseconds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation &amp;amp; API Reference:&lt;/strong&gt; &lt;a href="https://getapexcache.com/docs/invalidation" rel="noopener noreferrer"&gt;getapexcache.com/docs/invalidation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>architecture</category>
      <category>api</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
