Design a Price Tracking & Alerting Service Like CamelCamelCamel
A price tracking service (e.g., CamelCamelCamel, Keepa, Honey) monitors millions of e-commerce products (Amazon, Walmart, Best Buy), records their historical price fluctuations over time, visualizes price charts, and automatically fires real-time alerts (email, push, SMS) when a product drops below a user's desired price threshold.
1. Understanding the Problem
Functional Requirements
- Track Product Prices: Continuously ingest current prices, stock status, and seller details for millions of products.
- Historical Price Charts: Provide complete historical price graphs (1 month, 1 year, all-time) with lowest, highest, and average price indicators.
- Set Price Drop Alerts: Users set target threshold alerts (e.g., "Notify me if Apple AirPods drop below $150").
- Trigger Notifications: When a price drop is detected, fire multi-channel notifications (Email, Mobile Push, Browser Extension, Webhook) within minutes.
- Product Search & Discovery: Search tracked products by title, ASIN/SKU, or product URL.
Non-Functional Requirements
- High Ingestion Efficiency: Track 50 Million active products without getting IP-banned by e-commerce platforms.
- Adaptive Crawl Scheduling: Products with volatile prices must be checked frequently (e.g. every 1 hour); static products checked less often (e.g. every 24 hours).
- Fast Price Chart Serving: Visual price history charts must load in
< 50ms. - Reliable Alert Delivery: Price drops are time-sensitive; alerts must be delivered in
< 5 minutesbefore items sell out.
Capacity Estimations & Sizing
- Total Tracked Products: 50 Million products.
- Price Checks per Day:
- Adaptive scheduling averages 4 checks per product per day: .
- Time-Series Storage Sizing (5 Years):
- Storing only price changes (run-length compression):
- On average, a product price changes twice per month = 24 changes/year.
- 50M products 24 changes 5 years = 6 Billion historical data points.
- Each point:
product_id(16 bytes) +timestamp(8 bytes) +price_cents(4 bytes) +in_stock(1 byte) 30 bytes. - 6 Billion 30 bytes 180 GB storage (stored in a specialized Time-Series Database like TimescaleDB or ClickHouse).
- Active User Alerts: 10 Million registered price drop alerts.
2. The Set Up
Defining the Core Entities
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRODUCT β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β product_id β UUID β PRIMARY KEY β
β store_name β VARCHAR(32) β AMAZON, WALMART, ... β
β external_sku β VARCHAR(64) β ASIN / SKU β
β title β VARCHAR(255) β NOT NULL β
β current_price β INT β In cents β
β all_time_low β INT β In cents β
β all_time_high β INT β In cents β
β poll_interval_minβ INT β Dynamic: 60 to 1440 β
β next_check_at β TIMESTAMP β Scheduler Index β
β last_checked_at β TIMESTAMP β NOT NULL β
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRICE_HISTORY β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β product_id β UUID β COMPOSITE PK, FK β
β timestamp β TIMESTAMP β COMPOSITE PK (TSDB) β
β price_cents β INT β Not null β
β is_in_stock β BOOLEAN β Availability status β
β seller_type β VARCHAR(16) β 1st-Party / 3rd-Partyβ
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRICE_ALERT β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β alert_id β UUID β PRIMARY KEY β
β user_id β UUID β INDEX, FK β
β product_id β UUID β COMPOSITE INDEX, FK β
β target_price β INT β Trigger threshold β
β channel β VARCHAR(16) β EMAIL / PUSH / SMS β
β is_triggered β BOOLEAN β DEFAULT FALSE β
β created_at β TIMESTAMP β NOT NULL β
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
3. High-Level Design
Core Data & Alerting Flow
1. Adaptive Crawl Schedulingβ
- An Adaptive Polling Scheduler queries products where
next_check_at <= NOW(). - Push scrape jobs into a Scraper Queue (Kafka / RabbitMQ).
- Distributed Headless Scraper Workers (Playwright / Puppeteer / Retail APIs):
- Rotate residential proxy IP addresses to bypass anti-bot detection (Cloudflare / PerimeterX).
- Parse product detail page HTML, extract current price and stock status.
- Returns result payload
{product_id, current_price, is_in_stock}to the Ingestion Service.
2. Price Change Detection & Time-Series Archivalβ
- The Ingestion Service compares the newly scraped price against
product.current_price:- Case A: Price Has NOT Changed: Updates
last_checked_atand recalibratesnext_check_at. Does not write redundant rows to the time-series store! - Case B: Price Changed:
- Inserts a new data point into the TimescaleDB / ClickHouse Time-Series Store.
- Updates
current_price,all_time_low, andall_time_highon thePRODUCTtable. - Emits a
PriceChangedEventto the Alert Evaluation Engine.
- Case A: Price Has NOT Changed: Updates
3. Alert Evaluation & Notification Dispatchβ
- The Alert Evaluation Engine consumes the
PriceChangedEvent:- Queries matching alerts in memory or database:
SELECT * FROM price_alert WHERE product_id = ? AND target_price >= :new_price AND is_triggered = FALSE.
- Queries matching alerts in memory or database:
- For each matched alert, pushes notification job to Kafka Notification Topic.
- Notification Worker Pool dispatches templated emails (SendGrid), push notifications (APNS/FCM), or Discord webhooks.
- Marks
is_triggered = TRUEto prevent spamming the user on subsequent minor price adjustments.
4. Potential Deep Dives & Bottlenecks
Deep Dive 1: Adaptive Crawl Scheduling (Minimizing Scrape Overhead)
If we scrape all 50 Million products every hour, we need 14,000 scrapes/sec, which triggers aggressive IP bans and costs millions in proxy fees. How do we schedule smartly?
- Volatile Products vs Static Products:
- 80% of products change price less than once a month (e.g. cables, screws, books).
- 5% of products change price multiple times a day (e.g. video game consoles, GPUs, holiday deals).
- Dynamic Polling Algorithm:
IF price_changed_today == true:poll_interval = max(60 mins, poll_interval / 2) -- Check more frequently!ELSE:poll_interval = min(1440 mins, poll_interval * 1.5) -- Back off to 24 hours!
- High-Demand Boost: If a product has 100+ active user alerts watching it, its maximum interval is capped at 60 minutes.
- Result: Reduces total scraping volume by 80% while improving price-drop detection latency on high-value products!
Deep Dive 2: Fast Time-Series Compression & Downsampling
How do we serve interactive price charts with 5 years of historical data in < 50ms on mobile screens?
- Run-Length Compression: Only record a row when the price physically changes. If a price stays $199.99 for 6 months, it occupies exactly one row, not 4,320 hourly rows!
- Rollup Materialization (LTTB Downsampling):
- A 4K monitor or mobile screen only has 1,000 horizontal pixels; returning 50,000 data points to the browser wastes bandwidth and freezes the DOM.
- The API uses the Largest Triangle Three Buckets (LTTB) downsampling algorithm to reduce 5,000 points down to 300 visually representative points in memory.
- Charts load in 0.02 seconds!
Deep Dive 3: Thundering Herd Alert Notifications
When Amazon drops the price of a PlayStation 5 by $100, 500,000 users may have active alerts set for that product simultaneously. How do we dispatch 500,000 emails without melting our notification infrastructure?
- Priority Tiering:
- Immediate Push / Webhook alerts dispatched first (cheap, lightweight).
- Email alerts batched and throttled through an asynchronous worker pool with token-bucket rate limiters per email provider domain (e.g. max 500 emails/sec to Gmail to prevent IP reputation blacklisting).
- Deduplication Guard:
- If a price bounces rapidly between $399 and $401 within 10 minutes, the alert engine uses an in-memory Redis cool-down lock (
SET alert:sent:usr_1:prod_2 1 EX 86400 NX) to ensure a user is alerted at most once per 24 hours for the same price drop.
- If a price bounces rapidly between $399 and $401 within 10 minutes, the alert engine uses an in-memory Redis cool-down lock (
5. Architectural Trade-Off Matrix
| Design Area | Option A | Option B | Selected Choice & Rationale |
|---|---|---|---|
| Crawl Strategy | Fixed 1-hour interval for all products | Adaptive Velocity-Based Scheduling | Adaptive Scheduling: Saves 80% of proxy bandwidth and crawler CPU costs while maintaining < 1 hour detection on volatile items. |
| History Storage | Standard MySQL Rows | Columnar Time-Series (TimescaleDB / ClickHouse) | TimescaleDB / ClickHouse: 10:1 data compression on price floats and sub-10ms historical range queries across billions of rows. |
| Alert Matching | Continuous DB Polling | Event-Driven Kafka Consumer | Event-Driven: Alert checks trigger only when a price physically changes, eliminating millions of wasted database queries. |
6. What is Expected at Each Level?
Mid-Level (L4 / IC4)
- Identifies the role of scrapers and asynchronous workers.
- Designs relational schemas for Products, Price History, and Alerts.
- Understands basic email/push notification integration.
- Proposes storing price history points sequentially.
Senior (L5 / IC5)
- Details the Adaptive Polling Algorithm to optimize scraper throughput and proxy costs.
- Implements run-length time-series storage and downsampling algorithms (LTTB) for fast chart rendering.
- Solves thundering herd notification bursts when viral products drop in price.
- Handles anti-scraping defenses (residential proxy rotation and browser fingerprinting).
Staff+ (L6 / Principal)
- Designs an affiliate monetization and checkout attribution tracking engine: Ensuring generated buy links include affiliate tags (
tag=camel...) with zero tampering. - Architects multi-retailer inventory mapping: Automatically matching the exact same physical product across Amazon, Walmart, and Target using UPC/EAN universal barcodes and text embeddings.
- Details operational scraper resilience: Circuit breakers that detect when an e-commerce platform silently changes its HTML DOM structure, preventing corrupted price updates from contaminating historical data.
