<?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: Vivek Padia</title>
    <description>The latest articles on DEV Community by Vivek Padia (@vivekpadia70).</description>
    <link>https://dev.to/vivekpadia70</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%2F3637295%2Fc206109b-f8c4-4cf3-bfc2-70e25c840198.png</url>
      <title>DEV Community: Vivek Padia</title>
      <link>https://dev.to/vivekpadia70</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vivekpadia70"/>
    <language>en</language>
    <item>
      <title>I built a 3D game that shows the exact hop where your AWS design fails. Help me break it.</title>
      <dc:creator>Vivek Padia</dc:creator>
      <pubDate>Wed, 21 Oct 2026 10:07:17 +0000</pubDate>
      <link>https://dev.to/vivekpadia70/i-built-a-3d-game-that-shows-the-exact-hop-where-your-aws-design-fails-help-me-break-it-3np5</link>
      <guid>https://dev.to/vivekpadia70/i-built-a-3d-game-that-shows-the-exact-hop-where-your-aws-design-fails-help-me-break-it-3np5</guid>
      <description>&lt;p&gt;Every AWS practice exam I took had the same ending: "Incorrect."&lt;/p&gt;

&lt;p&gt;Sometimes there was an explanation. Usually it was a paragraph that told me what the right answer was, not why my design would actually fail. I'd memorise it, pass the question next time, and still not really understand route tables, security groups or NACLs.&lt;/p&gt;

&lt;p&gt;So I built the thing I wanted while studying: Tracesleuth, a 3D browser game where you learn AWS by watching your own designs fail.&lt;/p&gt;

&lt;p&gt;A packet dropped at the exact hop, with the reason&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw7m0720vanbuhpkb2h4z.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw7m0720vanbuhpkb2h4z.gif" alt="Demo GIF" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Each stage is an exam-style scenario: "Customers can't load the storefront", "The database must only be reachable from the web tier", "This pipeline costs too much".&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the design in 3D. The Region is the ground, Availability Zones are islands, the VPC is a glass enclosure, and subnets turn green when they become public.&lt;/li&gt;
&lt;li&gt;Press Send traffic. Packets travel hop by hop through your design.&lt;/li&gt;
&lt;li&gt;Watch it fail, precisely. When a packet is dropped, it stops at the exact hop that dropped it: "main-rtb has no route to 203.0.113.25". Then you get why it failed, the concept behind it, and an exam tip.&lt;/li&gt;
&lt;li&gt;Fix it and send again. Hints get more specific step by step, and a model solution unlocks once you've used them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI, data and architecture worlds use a "solution board" instead of packets. Each requirement is a row, you drop the AWS service that serves it into the slot, and a sample request runs along the row until the service that can't handle it rejects it, with the error the API would return.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the beta
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Six domains: networking, security, core architecture, data engineering, AI and machine learning, and operations.&lt;/li&gt;
&lt;li&gt;Mapped to the exams: SAA-C03, SAP-C02, SOA-C03, DVA-C02, DOP-C02, SCS-C03, DEA-C01, ANS-C01, AIF-C01, MLA-C02 and AIP-C01. Pick an exam and the map shows only the stages its guide covers.&lt;/li&gt;
&lt;li&gt;Free, with no sign-up: the first stage of every domain.&lt;/li&gt;
&lt;li&gt;Desktop browsers only for now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Play it: &lt;a href="https://beta.tracesleuth.com" rel="noopener noreferrer"&gt;beta.tracesleuth.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A few decisions that might interest you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No AI grading. A deterministic, unit-tested simulator decides pass or fail. The same design always gets the same verdict, and every failure maps to a hand-written explanation. I didn't want a learning tool that sometimes hallucinates why your security group is wrong.&lt;/li&gt;
&lt;li&gt;No AWS account and no bill. Everything is simulated in the browser, so you can break a NAT gateway as often as you like.&lt;/li&gt;
&lt;li&gt;Every check passes or fails for a stated reason. The explanation quotes your own design: which route table, which rule, which CIDR.&lt;/li&gt;
&lt;li&gt;Privacy. Analytics are cookieless and count stage starts and clears, never anything you type.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I need from you
&lt;/h2&gt;

&lt;p&gt;I'm a small team, and right now I can't tell whether this is genuinely useful or just pretty. &lt;strong&gt;Honest, even harsh, feedback is the most valuable thing you can give me.&lt;/strong&gt; Try one stage (about 10 to 15 minutes) and tell me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the failure explanation make it click? Or did you still not get why?&lt;/li&gt;
&lt;li&gt;Was the scenario realistic, compared with the exam or with real work?&lt;/li&gt;
&lt;li&gt;What was confusing, clunky or broken? Screenshots are gold.&lt;/li&gt;
&lt;li&gt;Would you use this alongside practice exams? If not, what's missing?&lt;/li&gt;
&lt;li&gt;Which certification should get the most stages first?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can reply in the comments, or use the Feedback button in the game. If you'd like to test the full game, join the waitlist on the site. I'll send invite codes to people who leave detailed feedback.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and good luck to everyone with an exam coming up. 🙏&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>showdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>Your best-selling SKU was out of stock for 13 hours and nobody noticed: real-time availability monitoring for D2C brands</title>
      <dc:creator>Vivek Padia</dc:creator>
      <pubDate>Thu, 06 Aug 2026 11:10:32 +0000</pubDate>
      <link>https://dev.to/vivekpadia70/your-best-selling-sku-was-out-of-stock-for-13-hours-and-nobody-noticed-real-time-availability-4g7m</link>
      <guid>https://dev.to/vivekpadia70/your-best-selling-sku-was-out-of-stock-for-13-hours-and-nobody-noticed-real-time-availability-4g7m</guid>
      <description>&lt;h2&gt;
  
  
  The Friday night nobody noticed
&lt;/h2&gt;

&lt;p&gt;It is 11 PM on a Friday. A dark store in one city runs out of your best-selling SKU. The listing does not throw an error or send anyone an email; it just greys out. Your performance ads keep serving, the campaign keeps spending, and every system involved behaves exactly as designed.&lt;/p&gt;

&lt;p&gt;Saturday, 12:10 PM: a customer DM lands on Instagram asking why they cannot order. Ops opens the app, finds the grey button, calls the distributor, and stock is back by early afternoon. Thirteen hours before anyone even knew, one SKU, one city.&lt;/p&gt;

&lt;p&gt;That evening slot normally moves about 8 units an hour at Rs. 450 a unit. 8 units x 13 hours x Rs. 450 is Rs. 46,800. Call it Rs. 47,000, gone silently, on one SKU in one city over one night.&lt;/p&gt;

&lt;p&gt;Nobody made a mistake. The warehouse did its job, the platform did its job, the ads did their job. There was simply nothing watching.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a stockout actually costs
&lt;/h2&gt;

&lt;p&gt;Rs. 47,000 is only the visible layer. A stockout charges you four times, and only the first charge shows up anywhere you currently look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct lost sales.&lt;/strong&gt; The base formula is hourly sales velocity x stockout hours x unit price, per SKU, per platform, per city. The load-bearing word is hourly. A hero SKU that sells 8 units an hour at 9 PM might sell 1 unit an hour at 4 AM, so pricing a stockout with a flat daily average understates the evening outage and overstates the overnight one. It also decides urgency: an alert that says "SKU-1042 out of stock" is easy to defer, while "SKU-1042 out of stock, Rs. 3,600 per hour at risk" is not.&lt;/p&gt;

&lt;p&gt;Priced honestly hour by hour, Friday night's Rs. 47,000 comes in lower, and an outage from 6 to 9 PM comes in higher, which is exactly why a flat number cannot be trusted in either direction. Hold onto that hourly figure: it is the exact number the monitoring system needs, per SKU and per hour of day, to price an open stockout while it is still open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wasted ad spend.&lt;/strong&gt; Campaigns do not pause themselves when a listing stops being buyable. Clicks keep landing on a grey button and you keep paying for them, and if your top three SKUs carry most of your ad budget, a weekend stockout on one of them burns real money for nothing. You are paying to advertise something nobody can buy. The fix is not someone pausing campaigns by hand at midnight; it is knowing the listing is dead within minutes, which makes this a monitoring problem before it is a marketing one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ranking decay.&lt;/strong&gt; This is the second-order cost, and it outlasts the stockout. Availability feeds every platform's ranking model, so a listing that goes dark overnight gets demoted, and restocking does not restore rank. Recovery takes days of renewed sales. A 13-hour stockout is not a 13-hour problem; it is closer to a week of depressed velocity on your best listing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Substitution and lifetime value.&lt;/strong&gt; On quick commerce, switching costs the customer one thumb-tap. Some of the buyers who hit the grey button buy a competitor instead, and a fraction of those do not come back. You lose not just tonight's order but a slice of every order that customer would have placed after it.&lt;/p&gt;

&lt;p&gt;The composite model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;monthly stockout cost =
    direct     sum per incident: velocity[hour of day] x price x hours out
  + ad waste   spend rate on the affected listings x hours out
  + decay      velocity shortfall after restock x recovery days x price
  + churn      blocked buyers x switch rate x never-return rate x future margin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worked through for a hypothetical brand doing Rs. 2 Cr a month in GMV across 40 SKUs, 3 platforms and 6 cities, with assumptions stated per row:&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;Working assumption&lt;/th&gt;
&lt;th&gt;Monthly cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direct lost sales&lt;/td&gt;
&lt;td&gt;~25 incidents x 9 hrs average x ~Rs. 800/hr at-risk velocity&lt;/td&gt;
&lt;td&gt;Rs. 1.8 lakh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wasted ad spend&lt;/td&gt;
&lt;td&gt;Rs. 20 lakh monthly spend, 2-3% served against unbuyable listings&lt;/td&gt;
&lt;td&gt;Rs. 0.4-0.6 lakh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ranking decay&lt;/td&gt;
&lt;td&gt;3-4 days of ~30% depressed velocity after each incident&lt;/td&gt;
&lt;td&gt;Rs. 1.5-3.5 lakh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Substitution and churn&lt;/td&gt;
&lt;td&gt;~20% of blocked buyers switch, a third of those stay gone&lt;/td&gt;
&lt;td&gt;Rs. 0.4-1.5 lakh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~Rs. 4-7.5 lakh&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The at-risk velocity in that first row is roughly 20x the average listing-hour. Rs. 2 Cr a month spread over 720 listing states is about Rs. 38 per listing-hour, and the gap is deliberate, because stockouts concentrate on hero SKUs at peak hours. A stockout is what happens when velocity outruns replenishment.&lt;/p&gt;

&lt;p&gt;Run the model and a brand doing Rs. 2 Cr a month is carrying Rs. 4-7.5 lakh of stockout cost, roughly 2 to 4 percent of GMV once lost sales, wasted spend and forgone future margin are added up. Your numbers will differ; the components will not. And none of this appears on any report as a line called "stockout cost". It is smeared across sales, marketing and retention metrics, which is exactly how it survives quarter after quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a person checking apps cannot fix this
&lt;/h2&gt;

&lt;p&gt;The instinctive fix is a WhatsApp group and a checking rota, and it fails on arithmetic before it fails on discipline. The brand above has 40 SKUs x 3 platforms x 6 cities: 720 listing states, each able to flip any hour. Checking each twice a day is 1,440 checks and still leaves 12-hour blind windows, and the Friday-night stockout burned straight through one of them.&lt;/p&gt;

&lt;p&gt;Seller dashboards do not close the gap either. They lag by hours, they are one-platform silos with no combined view, and most report what you shipped to the warehouse rather than what a buyer in a given pincode actually sees. A listing can be in stock in the panel and unbuyable on the app at the same moment: suppressed from search, buy box lost, pincode unserviceable. If your check reads the panel, that failure is invisible by construction.&lt;/p&gt;

&lt;p&gt;And even a perfectly executed rota records nothing. There is no history, no answer to how many hours the hero SKU was dark last month, no rupee attribution, no way to tell whether this quarter is better or worse than the last one. A recurring operational loss with no KPI attached to it never gets budget, so it never gets fixed.&lt;/p&gt;

&lt;p&gt;That failure yields three requirements: detection in minutes, on the system's schedule rather than a person's; every alert priced in rupees at risk, so the expensive stockout outranks the trivial one automatically; and stockout history stored as a first-class business KPI you can trend. The rest of this post is the architecture those three requirements force.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3p18z6dfj9s0kj26ptb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3p18z6dfj9s0kj26ptb.png" alt="StockWatch architecture: sources into connectors, Kafka backbone, detection with revenue enrichment, alerts and dashboard, Airflow reconciliation looping back into the connectors" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read it left to right: sources, one normalizing ingest layer, an event backbone, detection that prices what it detects, then alerts and a dashboard, with Airflow keeping the fast loop honest. Every AWS service named is the boring managed option: MSK, ECS Fargate, RDS, SQS, SES. The interesting decisions are in the flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connectors.&lt;/strong&gt; Every platform exposes inventory truth differently, so the system speaks three connector shapes: a marketplace seller API, a storefront API with webhooks, and warehouse CSV drops on S3. The marketplace connector is the one worth studying, because it carries the awkward parts of real seller APIs like Amazon's SP-API.&lt;/p&gt;

&lt;p&gt;Tokens expire, so a token manager refreshes proactively at 90 percent of lifetime, single-flight, so concurrent requests do not stampede the auth endpoint. Rate limits belong to the server, so the client reads the rate-limit header on every response and retunes its token bucket instead of hardcoding published numbers. And it runs two channels at once: webhook notifications for speed, polling for truth, because notifications get dropped in transit and a missed "went out of stock" is precisely the failure this system exists to catch.&lt;/p&gt;

&lt;p&gt;Two scope notes, because honesty is cheaper than being caught. In the repo the marketplace connector runs against a simulator implementing the same contract: token endpoint, rate-limit headers, injected 429s, added latency, dropped push notifications. That last fault is what argues for polling, and the connector stays production-shaped without a live seller account.&lt;/p&gt;

&lt;p&gt;The connector shape the repo does not ship is the buyer-side poller for quick-commerce apps with no seller API, the platform type in the opening scene. That is respectful-scraping territory, with its own rate, serviceability and anti-bot constraints, and deserves its own writeup. Its production shape is pincode-sampled storefront checks emitting the same normalized reading, so nothing downstream changes.&lt;/p&gt;

&lt;p&gt;Whatever the source, everything normalizes into one reading: SKU, location, channel, available, on hand, reserved, buyable, observed-at, source. Downstream code is platform-agnostic by construction. Buyable is deliberately separate from available, because seller-reported stock and buyer-visible availability are different facts, and a listing with units on hand that will not sell (suppressed, buy box lost, unserviceable) trips its own rule no stock count could see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The event backbone.&lt;/strong&gt; Readings become events on Kafka topics keyed by SKU, published through a transactional outbox: the state change and the outgoing event commit in one database transaction, and a relay publishes pending rows, so a crash causes a re-publish, never a lost event. Four independent consumer groups hang off the backbone: rules evaluation and an event ledger on the stock-changed topic, sales-velocity aggregation on orders, and the notification bridge on alert lifecycle events.&lt;/p&gt;

&lt;p&gt;Do you even need Kafka? At small scale, no. Fifty SKUs on 2 platforms polled every 5 minutes is under 30,000 events a day, and a cron writing to Postgres absorbs that without noticing. Even 500 SKUs across 5 platforms at 10-minute checks is about 360,000 events a day, around 4 per second.&lt;/p&gt;

&lt;p&gt;Event volume never justifies Kafka at this scale. Consumers, replay and burst shape do. Past 2 or 3 independent consumers of the same stream, or the first time you need to replay history after a bug, a work queue starts fighting the problem, and a flash sale arrives as a spike that the daily average hides. This repo clears it on replay rather than consumer count, with only two groups on the stock-changed stream. Below that line, SQS or a polled Postgres table is simpler, and simpler is a feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcgeu91dt94lcuglejsyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcgeu91dt94lcuglejsyb.png" alt="Kafka UI during the demo scenario: order and stock-change events flowing through the SKU-keyed topics" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Detection that knows what money looks like.&lt;/strong&gt; The rules evaluator alerts on transitions, not levels. "Went from in stock to out of stock" fires once; "is currently out of stock" would re-announce the same incident every sweep, dozens of times a day. Hysteresis handles the boundary: a low-stock alert resolves only after stock climbs back above threshold plus a margin, so a SKU oscillating around the line does not flap the channel. Each alert is enriched at fire time with hour-of-day velocity: a trailing 14-day mean per hour bucket, times unit price. The rupees-per-hour figure rides inside the payload.&lt;/p&gt;

&lt;p&gt;Measured locally against the docker compose stack, the full path lands in 4 to 5 seconds: storefront webhook to ingestion to outbox to Kafka to rules to a triggered alert.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fukohsxhdnx2tk4ggmf9z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fukohsxhdnx2tk4ggmf9z.png" alt="SKU detail mid-flash-sale: availability falling to zero with alert markers on the history chart" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Airflow, the slow loop.&lt;/strong&gt; Streaming answers what changed in the last minute; Airflow answers whether our picture of the world is still correct. A nightly DAG drives every connector through a cursor-free full snapshot, diffs it against the live model, and emits corrections back through the normal ingestion path, so every consumer sees them as ordinary facts. An hourly DAG refreshes the velocity model; a manual DAG replays topic windows for backfills. The nightly diff catches what a stream of increments cannot: the dropped webhook, the source that fell behind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serving.&lt;/strong&gt; No dashboard read walks the order stream: sales velocity is pre-aggregated into an hour-bucket table the stream maintains, and every rupee figure is integrated over those buckets. History is the honest exception: the availability views scan the append-only snapshot series at request time within a 30-day lookback, and event feeds read the ledger directly.&lt;/p&gt;

&lt;p&gt;Those derived read models can be rebuilt by clearing the consumer's dedupe rows and replaying Kafka. They are cache-shaped state, not a second source of truth; the canonical inventory model is written by the ingestion path that publishes the events. The heatmap ranks rows by rupees at risk, and the alert inbox lists open alerts first by severity, then by rupees at risk, so the SKU actually bleeding money is never buried under slow-movers.&lt;/p&gt;

&lt;p&gt;Both captures below are from the seeded demo mid-flash-sale, which lifts that hour's velocity bucket; at rest these SKUs read Rs. 3,000 to 9,000 an hour.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftv67xjb5ncfueuu956ym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftv67xjb5ncfueuu956ym.png" alt="Alert inbox: every open alert priced in rupees per hour at risk" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft5f3v8f9ilyfms2blmtj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft5f3v8f9ilyfms2blmtj.png" alt="Stockout heatmap: SKU x channel x location grid, ranked by revenue at risk" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How this usually gets built wrong
&lt;/h2&gt;

&lt;p&gt;The same five patterns appear in most home-built availability monitors. Each one looks reasonable on day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one-table poller.&lt;/strong&gt; Poll everything into a single readings table and aggregate at read time. Week one it is fine; at a few million rows the dashboard is scanning history on every page load, queries time out, and someone bolts on a cache that hides the problem until it is worse. Pre-aggregating at write time is the fix, and it usually arrives as a crisis rebuild instead of a design decision. You can spot the shape of it from outside: a dashboard that was fast in the demo and unusable by month three.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alerting on checks instead of transitions.&lt;/strong&gt; Every 10-minute sweep re-announces everything currently out of stock. That is 40 messages before breakfast, all of them repeats. Humans respond by muting the channel, and from that moment the monitoring is dead while running perfectly. Nobody announces the mute; you discover it during the next stockout. Alert volume should be proportional to state changes, which are rare, not to checks, which are constant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Polling without manners.&lt;/strong&gt; No backoff, no jitter, a fixed IP and an aggressive interval. It works for two weeks, then the platform rate-bans the scraper and monitoring goes dark, and "no alerts" quietly starts meaning "no coverage" instead of "no problems". Being a polite client (adaptive rates, exponential backoff, jittered retries) is not etiquette; it is what keeps the data flowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treating every SKU the same.&lt;/strong&gt; The hero SKU's stockout arrives as alert 7 of 23, below six slow-movers nobody will restock this quarter. Ranked by rupees at risk it would be alert 1. Severity should come from money, not from rule type, and that ranking needs the velocity model, which is why pricing and alerting have to be one system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing loss from daily averages.&lt;/strong&gt; A flat daily-average velocity understates peak-hour stockouts at exactly the moment they matter most, by your peak-to-average ratio: the 8-units-at-9-PM against 1-unit-at-4-AM profile from earlier averages out well below its own peak. The 9 PM outage gets priced like the 4 AM one, and the alert that most needed urgency reads as routine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production concerns and the AWS bill
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Rate limits shape the design, not throughput.&lt;/strong&gt; At this event volume, compute is never the constraint; the platform's request budget is. Batching SKUs per request, adapting the client's token bucket to what the server currently allows, and backing off with jitter on 429s decide your real detection interval. The connector treats the platform's budget as the platform's property and adjusts to it at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At-least-once, made safe.&lt;/strong&gt; The outbox relay guarantees delivery by allowing re-publishes, so every consumer is idempotent: processed event ids are recorded and a replayed event is a no-op. Notification delivery dedupes the same way, so a retried delivery is a no-op once the send is recorded. The exactly-once machinery Kafka offers is deliberately not used here: the idempotency discipline is simpler to reason about and, more importantly, simpler to test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring the monitor.&lt;/strong&gt; A dead poller and a fully stocked catalog both produce no events at all. Left alone, "everything is in stock" and "the monitor has been down since Tuesday" are indistinguishable, so connector health is a monitored fact. Consecutive sync failures escalate a connector's status and fire an alert of its own, which covers the loud failure. The nightly reconciliation covers the quiet one: a location whose last observation has gone stale while other locations keep reporting gets flagged, catching the poller that stopped without ever raising an error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bill.&lt;/strong&gt; Dev-sized on AWS in us-east-1, from the CloudFormation templates in the repo. One scope note: Airflow runs local-only in this repo, so the slow loop is not in this bill; budget MWAA or self-hosting separately if you want it in the cloud.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Monthly estimate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MSK, 2 x kafka.t3.small&lt;/td&gt;
&lt;td&gt;~$71&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT gateways, one per AZ&lt;/td&gt;
&lt;td&gt;~$66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fargate, 5 services&lt;/td&gt;
&lt;td&gt;~$48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALB&lt;/td&gt;
&lt;td&gt;~$20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RDS db.t4g.micro&lt;/td&gt;
&lt;td&gt;~$14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch, SQS, S3, misc&lt;/td&gt;
&lt;td&gt;~$5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$224/month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;MSK dominates, and it is the honest price of managed Kafka: below the consumer-count threshold from earlier, replace it with SQS or Postgres and cut the bill by a third. The NAT line is a choice, not a given: two gateways buy availability-zone redundancy, one saves $33 a month, and for a dev environment one is the right trade. For evaluation, docker compose on a laptop is free.&lt;/p&gt;

&lt;p&gt;At 10x the volume the first change would be the snapshot history table: range-partition it by month, or move cold history to S3 with Athena over it, because the append-only time series outgrows everything else first. The second would be sizing Kafka partition counts up front, since repartitioning a live SKU-keyed topic is the kind of migration you plan around. Everything else scales on knobs the design already has: consumers by partition, workers by queue depth, API by CPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three lessons and a repo
&lt;/h2&gt;

&lt;p&gt;Alert on transitions, not states: the information is in the moment something changes, and everything else is repetition that trains people to stop reading. A rupee figure in the alert gets triaged where a severity label gets muted. And reconcile what you stream, because incremental readings drift, and a nightly full-snapshot diff is cheap insurance against the misses you cannot see from inside the stream.&lt;/p&gt;

&lt;p&gt;Everything above is running code: &lt;a href="https://github.com/vivekpadia70/stockwatch" rel="noopener noreferrer"&gt;github.com/vivekpadia70/stockwatch&lt;/a&gt;. Bring it up with docker compose, seed the demo catalog, trigger the flash-sale scenario, and watch a priced alert fire and auto-resolve. More than 500 tests, the CloudFormation stacks and the cost breakdown are in the repo.&lt;/p&gt;

&lt;p&gt;I take on select data engineering engagements - reach out on &lt;a href="https://www.linkedin.com/in/vivekpadia70/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>kafka</category>
      <category>aws</category>
      <category>python</category>
    </item>
  </channel>
</rss>
