#Design a Recommendation Engine (Netflix / YouTube / Amazon Scale)

#1. Problem Statement & Clarifications

A Recommendation Engine is the system responsible for predicting and surfacing items (movies, products, songs, posts) that a user is most likely to engage with. It powers the "Recommended For You", "Because You Watched...", and "Customers Also Bought" sections seen across Netflix, YouTube, Amazon, and Spotify.

#Functional Requirements

  1. Personalized Recommendations β€” Generate ranked item lists tailored to each user based on their history, preferences, and behavior signals
  2. Real-Time Event Ingestion β€” Capture user interactions (views, clicks, purchases, ratings, dwell time) in real-time to update recommendation context
  3. Multiple Recommendation Types β€” Support different strategies: collaborative filtering (users like you), content-based (items like this), trending/popular, and "because you interacted with X"
  4. Candidate Generation + Ranking β€” Two-stage pipeline: generate hundreds of candidates, then rank them with a fine-grained ML model
  5. Explanation β€” Provide reasoning for recommendations ("Because you watched X", "Trending in your area")

#Non-Functional Requirements

Requirement Target
Read Latency P99 < 100ms for fetching personalized recommendations
Freshness New interactions reflected in recommendations within 5–30 minutes
Availability 99.99% β€” recommendations drive 35%+ of engagement
Scale 500M users, 100M items, 50B interaction events/day
Throughput 200K recommendation requests/sec at peak

#Out of Scope

#Assumptions


#2. Back-of-Envelope Estimation

#Traffic Estimates

DAU:                    200M users
Rec requests/day:       2B (avg 10 page loads with recs per user)
Rec QPS (avg):          2B / 86400 β‰ˆ 23K QPS
Rec QPS (peak 3x):     ~70K QPS
Event ingestion:        50B events/day β‰ˆ 580K events/sec
Event ingestion (peak): ~1.5M events/sec

#Storage Estimates

User profiles:          500M users Γ— 2KB (demographics + prefs) = 1TB
User embeddings:        500M Γ— 512B (256-dim float32) = 256GB
Item embeddings:        100M Γ— 512B = 51GB
Interaction history:    50B events/day Γ— 200B avg = 10TB/day (hot: 30 days = 300TB)
Feature store:          500M users Γ— 1KB real-time features = 500GB
Item metadata:          100M Γ— 5KB = 500GB
Total hot storage:      ~302TB (dominated by interaction logs)

#Bandwidth

Rec response:           ~5KB (50 items with metadata)
Peak rec bandwidth:     70K Γ— 5KB = 350 MB/s
Event ingestion BW:     1.5M Γ— 200B = 300 MB/s

#Cache Estimates

Top 5% items (popular):   5M items Γ— 5KB = 25GB (item metadata cache)
Pre-computed recs:         50M active users Γ— 2KB = 100GB (Redis cluster)
Cache hit ratio target:   90%+ for pre-computed, 95%+ for item metadata

#3. API Design

#Get Recommendations API

GET /api/v1/recommendations
  ?user_id=U123
  &context=homepage|post_play|product_page
  &item_id=I456              // optional: "related to this item"
  &rec_type=personalized|similar|trending|because_you_watched
  &limit=50
  &page_token=abc123

Response 200:
{
  "recommendations": [
    {
      "item_id": "I789",
      "score": 0.94,
      "title": "Stranger Things S4",
      "thumbnail_url": "https://cdn.example.com/...",
      "explanation": "Because you watched Dark",
      "rec_type": "because_you_watched",
      "source_item_id": "I456"
    }
  ],
  "request_id": "req_abc",          // for A/B tracking
  "model_version": "v3.2.1",
  "page_token": "def456"
}

#Record Event API (Client β†’ Event Collector)

POST /api/v1/events
{
  "user_id": "U123",
  "event_type": "VIEW|CLICK|PURCHASE|RATE|SKIP|DWELL",
  "item_id": "I789",
  "timestamp": 1716982400,
  "context": {
    "page": "homepage",
    "position": 3,
    "rec_request_id": "req_abc",
    "device": "mobile_ios",
    "session_id": "sess_xyz"
  },
  "metadata": {
    "dwell_time_sec": 45,
    "rating": 4.5,
    "watch_pct": 0.72
  }
}

Response 202: { "status": "accepted" }

#Get Similar Items API

GET /api/v1/items/{item_id}/similar?limit=20

Response 200:
{
  "source_item": "I456",
  "similar_items": [
    { "item_id": "I789", "similarity_score": 0.91, "shared_attributes": ["sci-fi", "thriller"] }
  ]
}

#Internal: Fetch User Features (Feature Store)

GET /internal/features/user/{user_id}

Response 200:
{
  "user_id": "U123",
  "embedding": [0.12, -0.34, ...],         // 256-dim vector
  "real_time_features": {
    "last_5_items": ["I1","I2","I3","I4","I5"],
    "session_genre_dist": {"action":0.4, "comedy":0.3, "drama":0.3},
    "hours_since_last_visit": 2.5
  },
  "static_features": {
    "country": "US",
    "age_bucket": "25-34",
    "subscription_tier": "premium"
  }
}

#4. Data Model

#User Profile Store (PostgreSQL β€” sharded by user_id)

CREATE TABLE users (
  user_id       BIGINT PRIMARY KEY,
  country       VARCHAR(5),
  language      VARCHAR(10),
  age_bucket    VARCHAR(10),
  signup_date   TIMESTAMP,
  sub_tier      ENUM('free','basic','premium'),
  updated_at    TIMESTAMP
);

CREATE TABLE user_preferences (
  user_id       BIGINT REFERENCES users,
  category      VARCHAR(50),        -- e.g., "genre", "brand"
  value         VARCHAR(100),       -- e.g., "sci-fi", "Nike"
  weight        FLOAT,              -- learned or explicit preference
  PRIMARY KEY (user_id, category, value)
);

#Item Metadata Store (PostgreSQL)

CREATE TABLE items (
  item_id       BIGINT PRIMARY KEY,
  title         VARCHAR(500),
  description   TEXT,
  category_path VARCHAR(500),       -- "Movies > Sci-Fi > Thriller"
  attributes    JSONB,              -- {"genre":["sci-fi"],"director":"Nolan","year":2024}
  popularity    FLOAT,              -- normalized 0-1
  created_at    TIMESTAMP,
  updated_at    TIMESTAMP,
  status        ENUM('ACTIVE','INACTIVE')
);

#Embedding Store (Vector DB β€” Milvus / Pinecone / pgvector)

Collection: user_embeddings
  - user_id:    BIGINT (primary key)
  - vector:     FLOAT[256]          // 256-dim dense embedding
  - version:    INT                 // model version that produced this
  - updated_at: TIMESTAMP

Collection: item_embeddings
  - item_id:    BIGINT (primary key)
  - vector:     FLOAT[256]
  - version:    INT
  - updated_at: TIMESTAMP

Index: IVF_FLAT or HNSW for ANN (Approximate Nearest Neighbor) search

#Interaction Event Store (Kafka β†’ Cassandra / ClickHouse)

Partition Key: user_id
Sort Key:      timestamp (descending)

{
  user_id:      "U123",
  item_id:      "I789",
  event_type:   "VIEW",
  timestamp:    1716982400,
  context:      {"page":"homepage","position":3},
  metadata:     {"dwell_time_sec":45,"watch_pct":0.72}
}

#Real-Time Feature Store (Redis Cluster)

Key:   user:features:{user_id}
Value: {
  "last_n_items":       ["I1","I2","I3","I4","I5"],
  "session_clicks":     12,
  "genre_dist_24h":     {"action":0.4,"comedy":0.3,"drama":0.3},
  "avg_dwell_time_7d":  35.2,
  "last_active_ts":     1716982400
}
TTL: 7 days (refreshed on every interaction)

#Access Patterns

Query Store Index/Key
Get user embedding Vector DB PK: user_id
ANN search (find similar items) Vector DB (Milvus) HNSW index on item_embeddings
Get user's recent interactions Cassandra PK: user_id, SK: timestamp DESC
Get real-time user features Redis Key: user:features:{user_id}
Get item metadata PostgreSQL + Redis cache PK: item_id
Get trending items by category Redis sorted set Key: trending:{category}
Batch user embeddings for training HDFS / S3 Batch export from Vector DB

#5. High-Level Design (HLD) & Scale Evolution

#Stage 1: MVP / Startup Scale (100K Users, 10K Items)

#Stage 2: Growth Scale (10M Users, 1M Items)

#Stage 3: Netflix/YouTube Scale (500M Users, 100M Items)

This is the target architecture β€” a fully decoupled, two-stage ML pipeline with real-time and batch components.

#Architecture Diagram

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚   CDN / Edge β”‚ ← Pre-computed recs for cold-start users
                    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚    API Gateway       β”‚
                β”‚  (Rate limit, Auth)  β”‚
                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β–Ό                β–Ό                  β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  Recommendation  β”‚ β”‚   Event      β”‚ β”‚  Similar Itemsβ”‚
 β”‚  Service         β”‚ β”‚   Collector  β”‚ β”‚  Service      β”‚
 β”‚                  β”‚ β”‚              β”‚ β”‚               β”‚
 β”‚ β€’ Orchestrates   β”‚ β”‚ β€’ Validates  β”‚ β”‚ β€’ Item-to-itemβ”‚
 β”‚   candidate gen  β”‚ β”‚ β€’ Publishes  β”‚ β”‚   ANN lookup  β”‚
 β”‚   + ranking      β”‚ β”‚   to Kafka   β”‚ β”‚               β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚                  β”‚                  β”‚
    β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”           β–Ό                  β–Ό
    β–Ό            β–Ό    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”β”‚  Kafka       β”‚   β”‚  Vector DB   β”‚
β”‚Candidatβ”‚ β”‚Ranking β”‚β”‚  Event Bus   β”‚   β”‚  (Milvus)    β”‚
β”‚  Gen   β”‚ β”‚Service β”‚β”‚              β”‚   β”‚  Item embeds β”‚
β”‚Service β”‚ β”‚        β”‚β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜       β”‚
    β”‚          β”‚      β”Œβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β–Ό          β–Ό      β–Ό     β–Ό              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚Vector  β”‚ β”‚Model β”‚ β”‚Feature     β”‚ β”‚Interaction  β”‚
β”‚  DB    β”‚ β”‚Serverβ”‚ β”‚Update Svc  β”‚ β”‚  Store      β”‚
β”‚(Milvus)β”‚ β”‚(TF   β”‚ β”‚(Flink/     β”‚ β”‚(Cassandra/  β”‚
β”‚User +  β”‚ β”‚Serve)β”‚ β”‚Spark Streamβ”‚ β”‚ ClickHouse) β”‚
β”‚Item    β”‚ β”‚      β”‚ β”‚            β”‚ β”‚             β”‚
β”‚embeds  β”‚ β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚
                   β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
                   β”‚  Redis        β”‚
                   β”‚  Feature Storeβ”‚
                   β”‚  + Rec Cache  β”‚
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚          Batch Pipeline (Every 4-6 hours)            β”‚
   β”‚  Spark/Flink β†’ Train models β†’ Update embeddings     β”‚
   β”‚  β†’ Push to Vector DB β†’ Warm Redis cache              β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

#Component Breakdown

Component Responsibility Tech Choice
API Gateway Rate limiting, auth, routing Kong / AWS API GW
Recommendation Service Orchestrates candidate gen β†’ ranking β†’ response Go / Java microservice
Candidate Generation Fast ANN retrieval of ~500 candidates Vector DB (Milvus) + HNSW
Ranking Service Re-rank candidates using fine-grained ML model TensorFlow Serving / Triton
Event Collector Validate & publish interaction events Go service β†’ Kafka
Feature Update Service Real-time feature aggregation from events Apache Flink / Spark Streaming
Feature Store Serve user/item features at low latency Redis cluster
Vector DB Store & search embeddings via ANN Milvus / Pinecone
Model Server Serve trained ranking models TF Serving / Triton Inference
Batch Pipeline Retrain models, recompute embeddings Spark + TensorFlow on GPU cluster

#Data Flow

Recommendation Read Path:

User β†’ API GW β†’ Recommendation Service
  β†’ 1. Fetch user features from Redis Feature Store
  β†’ 2. Fetch user embedding from Vector DB
  β†’ 3. Candidate Generation: ANN search in Vector DB β†’ 500 candidates
  β†’ 4. Fetch item features for candidates from Redis
  β†’ 5. Ranking Service: ML model scores each candidate
  β†’ 6. Post-processing: diversity filter, business rules, dedup
  β†’ 7. Return top 50 ranked items with explanations

Event Write Path:

User interaction β†’ Event Collector β†’ Kafka (events topic)
  β†’ Consumer 1: Feature Update Service β†’ update Redis features
  β†’ Consumer 2: Interaction Store β†’ persist to Cassandra
  β†’ Consumer 3: Near-real-time model update (optional)

Batch Path (Offline):

Interaction Store (Cassandra) β†’ Spark batch job
  β†’ Train collaborative filtering model (ALS / deep learning)
  β†’ Generate new user & item embeddings
  β†’ Push embeddings to Vector DB
  β†’ Pre-compute top-N recs for active users β†’ cache in Redis

#6. Deep Dive β€” Core Components

#Candidate Generation Service β€” Detailed Design

The two-stage architecture is critical: candidate generation narrows 100M items to ~500 cheaply, then ranking scores those 500 precisely.

Multiple Candidate Sources (Multi-Channel Retrieval):

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Candidate Generation                 β”‚
β”‚                                                   β”‚
β”‚  Source 1: User-to-Item ANN (collaborative)       β”‚
β”‚  β†’ User embedding β†’ ANN search β†’ 200 candidates  β”‚
β”‚                                                   β”‚
β”‚  Source 2: Item-to-Item ANN (content-based)       β”‚
β”‚  β†’ Last 5 viewed items β†’ ANN per item β†’ 150 each β”‚
β”‚  β†’ Deduplicate β†’ 200 candidates                  β”‚
β”‚                                                   β”‚
β”‚  Source 3: Trending / Popular                     β”‚
β”‚  β†’ Redis sorted set by category β†’ 50 candidates  β”‚
β”‚                                                   β”‚
β”‚  Source 4: Editorial / Business Rules             β”‚
β”‚  β†’ New releases, promoted content β†’ 50 candidates β”‚
β”‚                                                   β”‚
β”‚  Total: ~500 unique candidates (after dedup)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

ANN Search with HNSW:

#Ranking Service β€” Detailed Design

Feature Vector for Ranking:

For each (user, candidate_item) pair, construct:
{
  // User features (from Feature Store)
  user_embedding:        [256 floats],
  user_genre_dist:       [20 floats],
  user_avg_dwell:        float,
  user_activity_level:   float,

  // Item features
  item_embedding:        [256 floats],
  item_popularity:       float,
  item_freshness:        float,    // days since release
  item_category_onehot:  [50 floats],

  // Cross features
  user_item_dot_product: float,    // embedding similarity
  user_category_affinity: float,   // user pref Γ— item category match
  co_interaction_count:  int,      // how many similar users interacted

  // Context features
  time_of_day:           float,
  device_type:           onehot,
  page_context:          onehot
}

Ranking Model: Deep neural network (e.g., Wide & Deep, DCN, or two-tower with cross-attention) trained on click-through rate (CTR) and engagement signals. Served via TensorFlow Serving with GPU inference for batch scoring.

Post-Processing Pipeline:

Ranked candidates (500)
  β†’ Remove already-watched items (interaction store lookup)
  β†’ Diversity filter (max 3 items from same genre in top 20)
  β†’ Business rules (boost new releases, contractual obligations)
  β†’ Explanation generation (match rec_type to template)
  β†’ Return top 50

#Feature Update Service β€” Detailed Design

Real-Time Feature Computation (Apache Flink):

Kafka events stream β†’ Flink job:
  1. Sliding window: count clicks per genre in last 24h β†’ genre_dist
  2. Tumbling window: avg dwell time in last 7 days β†’ avg_dwell
  3. Session tracking: last N items interacted β†’ last_n_items
  4. Write updated features to Redis Feature Store

Why Flink over Spark Streaming? True event-time processing, lower latency (sub-second vs micro-batch), better exactly-once semantics with Kafka.

#Scaling Strategy

Component Strategy
Vector DB Sharded by embedding ID; replicas per shard for read throughput. Milvus supports horizontal scaling natively
Redis Feature Store Cluster mode, 600GB+ across 100+ nodes; separate clusters for features vs rec cache
Ranking Service GPU-backed pods; horizontal scale behind LB; batch scoring (500 items per request) amortizes GPU cost
Kafka Partition events topic by user_id for ordered processing; 500+ partitions
Interaction Store Cassandra with TTL (keep 90 days hot); older data archived to S3/HDFS for training
Candidate Generation Stateless service; scale pods independently; cache popular item neighborhoods

#Caching Strategy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Layer 1: Pre-computed Recommendations (Redis)         β”‚
β”‚ β€’ Top 50 recs per active user (TTL: 30min)           β”‚
β”‚ β€’ Refreshed on significant user activity             β”‚
β”‚ β€’ Serves 60% of requests without hitting ML pipeline β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 2: Item Metadata Cache (Redis)                  β”‚
β”‚ β€’ Item title, thumbnail, attributes (TTL: 1h)        β”‚
β”‚ β€’ 95% hit rate for popular items                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 3: ANN Result Cache (Local in-process)          β”‚
β”‚ β€’ Cache item-to-item neighbors (TTL: 4h)             β”‚
β”‚ β€’ Item embeddings change only on batch retrain       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 4: Trending / Popular Lists (Redis Sorted Sets) β”‚
β”‚ β€’ Pre-computed hourly; used for cold-start users     β”‚
β”‚ β€’ Category-level and global trending                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

#Consistency Models

Data Model Rationale
User embeddings Eventually consistent (4-6h) Updated on batch retrain; slight staleness is fine
Real-time features Eventually consistent (< 5s) Flink updates Redis within seconds of event
Item metadata Eventually consistent (< 30s) Stale title/image is briefly acceptable
Interaction events At-least-once delivery Kafka guarantees; dedup at consumer if needed
Pre-computed recs cache Eventually consistent (< 30min) Refreshed periodically; real-time path as fallback

#7. Low-Level Design β€” Core Functionality

#Key Algorithms

1. Two-Tower Embedding Model (Candidate Generation)

User Tower:                    Item Tower:
  user_id β†’ Embedding            item_id β†’ Embedding
  + user features                + item features
  β†’ Dense layers                 β†’ Dense layers
  β†’ 256-dim user vector          β†’ 256-dim item vector

Training: Maximize dot product for positive (user, item) pairs
          Minimize for negative samples (in-batch negatives)

Inference: Pre-compute all item vectors β†’ build ANN index
           At request time: compute user vector β†’ ANN search

2. Approximate Nearest Neighbor (HNSW)

HNSW Index Parameters:
  M = 16            (max connections per node per layer)
  ef_construction = 200  (search width during build)
  ef_search = 100        (search width during query)

Query: user_embedding β†’ search HNSW index β†’ top 200 items
Latency: < 5ms for 100M vectors at 95% recall
Memory: ~256B per vector Γ— 100M = 25.6GB + graph overhead β‰ˆ 40GB

3. Real-Time Feature Aggregation (Flink Pseudocode)

# Sliding window: genre distribution over last 24 hours
events_stream \
  .key_by("user_id") \
  .window(SlidingEventTimeWindows.of(hours(24), minutes(10))) \
  .aggregate(GenreDistributionAggregator()) \
  .add_sink(RedisSink("user:features:{user_id}", field="genre_dist_24h"))

# Per-event: update last-N items list
events_stream \
  .filter(lambda e: e.event_type in ["VIEW", "CLICK"]) \
  .key_by("user_id") \
  .process(LastNItemsProcessor(n=20)) \
  .add_sink(RedisSink("user:features:{user_id}", field="last_n_items"))

4. Diversity-Aware Re-ranking (MMR β€” Maximal Marginal Relevance)

def mmr_rerank(candidates, lambda_param=0.7, top_k=50):
    """Balance relevance and diversity using MMR."""
    selected = []
    remaining = list(candidates)

    while len(selected) < top_k and remaining:
        best_score = -inf
        best_item = None

        for item in remaining:
            relevance = item.ranking_score
            max_sim = max(cosine_sim(item.embedding, s.embedding)
                         for s in selected) if selected else 0
            mmr = lambda_param * relevance - (1 - lambda_param) * max_sim

            if mmr > best_score:
                best_score = mmr
                best_item = item

        selected.append(best_item)
        remaining.remove(best_item)

    return selected

#Design Patterns Used

Pattern Where Why
CQRS Event ingestion (write) vs Recommendation serving (read) Vastly different read/write patterns and scale
Two-Stage Pipeline Candidate Gen β†’ Ranking Can't run expensive ML model on 100M items; narrow first, rank second
Event Sourcing Interaction events via Kafka Immutable event log enables replay for model retraining
Feature Store Pattern Redis-backed real-time features Decouple feature computation from model serving
Circuit Breaker Rec Service β†’ Ranking Service Fallback to cached/popular recs if ML model is slow
Bulkhead Separate thread pools for each candidate source One slow source doesn't block others

#8. Failure Handling & Edge Cases

#What Happens When X Fails?

Failure Impact Mitigation
Vector DB down Can't generate ANN candidates Fallback to pre-computed recs from Redis cache; serve trending
Ranking Service (GPU) down Can't score candidates Return candidates sorted by embedding similarity (dot product); skip ML ranking
Redis Feature Store down Missing real-time features Use default/average features; model trained to handle missing features gracefully
Kafka lag Features become stale Alert on lag > 60s; recs still work but less personalized
Interaction Store down Can't filter already-seen items Serve recs anyway; minor UX degradation (user sees repeat recs)
Model Server OOM Ranking fails for batch Reduce batch size dynamically; circuit breaker activates fallback

#Cold Start Problem

This is the most critical edge case for recommendation engines:

NEW USER (no interaction history):
  1. Onboarding survey β†’ explicit preferences β†’ content-based recs
  2. Demographic-based recs (age, country, device)
  3. Global trending / popular items
  4. Explore/Exploit: show diverse items, learn quickly from clicks
  5. After 10-20 interactions β†’ switch to collaborative filtering

NEW ITEM (no user interactions):
  1. Content-based features (genre, description, creator) β†’ place in embedding space
  2. Boost factor for new items (exploration bonus in ranking score)
  3. Show to users who liked similar-attribute items
  4. After 100-500 interactions β†’ collaborative signals kick in

#Edge Cases


#9. Monitoring & Observability

#Key Metrics

Metric Target Alert Threshold
Rec API P99 latency < 100ms > 250ms
Candidate gen P99 < 20ms > 50ms
Ranking P99 < 60ms > 150ms
Event ingestion lag (Kafka) < 5s > 60s
Pre-computed rec cache hit ratio > 60% < 40%
CTR on recommendations > 5% < 2% (model degradation)
Rec diversity score (ILS) > 0.4 < 0.2 (filter bubble)
Cold-start user coverage > 95% < 85%
Model serving error rate < 0.1% > 0.5%
Embedding freshness < 6h > 12h

#Alerting Strategy

#SLAs / SLOs

Recommendation API: 99.95% availability, P99 < 100ms
Event Ingestion:    99.99% availability, P99 < 50ms
Feature Freshness:  99% of events reflected in features within 30s
Model Freshness:    Embeddings updated every 6h with < 1h variance

#10. Trade-off Summary

Decision Chose Over Because
Candidate generation ANN (HNSW) on Vector DB Brute-force scoring all items Can't run ML model on 100M items in real-time; ANN narrows to 500 in < 10ms
Embedding index HNSW IVF_FLAT, LSH Better recall (95%+) at comparable latency; worth the extra memory
Feature store Redis (real-time) + HDFS (batch) Single unified store Real-time features need sub-ms reads; batch features need petabyte-scale storage
Event processing Apache Flink Spark Streaming True event-time semantics, sub-second latency vs micro-batch lag
Interaction store Cassandra PostgreSQL, ClickHouse High write throughput for 580K events/sec; time-series access pattern fits wide-column model
Ranking model serving TensorFlow Serving (GPU) CPU-only inference GPU batch scoring amortizes cost; 500 candidates scored in < 30ms on GPU vs 200ms on CPU
Rec staleness tolerance Eventually consistent (5-30 min) Real-time model updates Online model updates are complex and risky; batch retrain + real-time features is a good middle ground
Diversity approach MMR re-ranking post-hoc Diversity-aware training MMR is interpretable, tunable, and doesn't require model retraining to adjust

#11. Extensions & Follow-ups

#What Would You Add With More Time?

  1. Multi-Objective Ranking β€” Optimize for engagement, revenue, and user satisfaction simultaneously using Pareto-optimal ranking
  2. Contextual Bandits β€” Online learning for explore/exploit; adapt recs in real-time without full model retrain
  3. Cross-Domain Recommendations β€” "You watched X on Netflix, you might like Y on the e-commerce store" using shared user embeddings
  4. Graph Neural Networks β€” Model user-item-user interactions as a graph for richer collaborative signals (e.g., PinSage)
  5. Federated Learning β€” Train personalization models on-device for privacy-sensitive use cases
  6. Explanation Generation β€” Use LLMs to generate natural language explanations beyond template-based "Because you watched X"

#How Would This Change at 100x Scale?


#12. Cross-References

Topic Connection
E-Commerce (#11) Recommendation engine powers "related products" and "customers also bought" sections
Search Autocomplete (#14) Shares user behavior signals; search queries inform recommendation features
Notification System (#13) Delivers recommendation-based push notifications ("New release you might like")
Distributed Cache (#15) Redis cluster design underpins feature store and rec cache

#Building Blocks Used

#Concepts Used