โ† 2026-05-15 ๐Ÿ“‚ All Days 2026-05-17 โ†’

Review Day 45 โ€” Week in Review

Today's primary content: Saturday Deep Dive โ€” Design a Recommendation System (sent above).

Mini Quiz: Days 41-44

Q1: [System Design] What are the two main components of a distributed task queue like Celery, and what role does each play?

A1: A message broker (e.g., Redis or RabbitMQ) holds the task queue and delivers tasks to workers; worker processes pull tasks, execute them, and report results. The broker decouples task producers from consumers, enabling horizontal scaling of workers independently.

Q2: [Algorithms] In Floyd's Cycle Detection (used in Linked List Cycle), why does the fast pointer always catch the slow pointer inside the cycle?

A2: Once both pointers are inside the cycle, the fast pointer gains 1 step per iteration on the slow pointer. The gap closes by 1 each step, so it's guaranteed to reach 0 (meet) within cycle_length iterations โ€” never "jumping over" the slow pointer.

Q3: [AI/ML] What is quantization in LLMs, and what is the practical tradeoff between INT8 and FP16 quantization?

A3: Quantization reduces weight precision (e.g., FP32 โ†’ INT8) to shrink model size and speed up inference. INT8 uses 4ร— less memory than FP32 with ~1-2% accuracy loss; FP16 uses 2ร— less with negligible loss. INT4 (via GPTQ/AWQ) allows running 70B models on consumer GPUs but with more noticeable quality degradation on complex tasks.

๐Ÿ”ฌ Saturday Deep Dive: Design a Recommendation System (15 min read)

๐Ÿ“Š NeetCode: 38/150 ยท SysDesign: 37/40 ยท Behavioral: 37/40 ยท Frontend: 37/50 ยท AI: 18/30

๐Ÿ”ฅ 44-day streak!


Overview / ๆฆ‚่ฟฐ

ๆŽจ่็ณป็ปŸๆ˜ฏ็Žฐไปฃไบ’่”็ฝ‘ไบงๅ“็š„ๆ ธๅฟƒๅผ•ๆ“Žใ€‚Netflix ็š„"็Œœไฝ ๅ–œๆฌข"ใ€YouTube ็š„้ฆ–้กต่ง†้ข‘ใ€Amazon ็š„"่ดญไนฐไบ†่ฟ™ไปถๅ•†ๅ“็š„็”จๆˆท่ฟ˜่ดญไนฐไบ†โ€ฆโ€ฆ"โ€”โ€”่ฟ™ไบ›่ƒŒๅŽ้ƒฝๆ˜ฏๆŽจ่็ณป็ปŸๅœจๅฎžๆ—ถ่ฟ่ฝฌใ€‚ๅฏนไบŽๅŽ็ซฏๅทฅ็จ‹ๅธˆๆฅ่ฏด๏ผŒๆŽจ่็ณป็ปŸๆถต็›–ไบ†็ณป็ปŸ่ฎพ่ฎกไธญๆœ€้šพ็š„ๅ‡ ไธช็ปดๅบฆ๏ผšๆตท้‡ๆ•ฐๆฎๅค„็†ใ€ๅฎžๆ—ถไธŽ็ฆป็บฟ็š„ๆƒ่กกใ€ๅคš้˜ถๆฎต่ฟ‡ๆปคๆผๆ–—ใ€ไปฅๅŠ ML ไธŽๅทฅ็จ‹็š„ๆทฑๅบฆ็ป“ๅˆใ€‚

A recommendation system is the core engine of modern internet products. Netflix's "Top Picks," YouTube's homepage, Amazon's "Customers also bought" โ€” they all run on recommendation systems. For backend engineers, this topic spans the hardest dimensions of system design: massive data pipelines, real-time vs. offline tradeoffs, multi-stage filtering funnels, and the deep integration of ML and engineering.

Why it matters in interviews: This question tests whether you can break down a fuzzy product requirement into a concrete, scalable architecture. It's one of the canonical "design a ML-backed system" questions at Google, Meta, Netflix, and Amazon.


Part 1: Theory / ็†่ฎบๅŸบ็ก€ (5 min)

ไธ‰็งๆ ธๅฟƒๆŽจ่็ฎ—ๆณ• / The Three Core Approaches

1. ๅๅŒ่ฟ‡ๆปค (Collaborative Filtering)

ๆ ธๅฟƒๆ€ๆƒณ๏ผš็”จๆˆท A ๅ’Œ็”จๆˆท B ็š„ๅކๅฒ่กŒไธบ็›ธไผผ๏ผŒๅˆ™ A ๅ–œๆฌข็š„ไธœ่ฅฟ B ๅฏ่ƒฝไนŸๅ–œๆฌขใ€‚

  • User-Based CF๏ผšๆ‰พๅˆฐไธŽไฝ ็›ธไผผ็š„็”จๆˆท็พคไฝ“๏ผŒๆŽจ่ไป–ไปฌๅ–œๆฌขไฝ†ไฝ ่ฟ˜ๆฒก็œ‹่ฟ‡็š„ๅ†…ๅฎนใ€‚
  • Item-Based CF๏ผšๆ‰พๅˆฐไธŽไฝ ไบ’ๅŠจ่ฟ‡็š„ๅ†…ๅฎน็›ธไผผ็š„ๅ…ถไป–ๅ†…ๅฎนใ€‚Netflix ๆ—ฉๆœŸ็”จ็š„ๅฐฑๆ˜ฏ่ฟ™ไธชใ€‚
  • Matrix Factorization๏ผˆ็Ÿฉ้˜ตๅˆ†่งฃ๏ผ‰๏ผšๅฐ†็”จๆˆท-็‰ฉๅ“ไบคไบ’็Ÿฉ้˜ตๅˆ†่งฃไธบไธคไธชไฝŽ็ปด็Ÿฉ้˜ต๏ผˆ็”จๆˆทๅตŒๅ…ฅ + ็‰ฉๅ“ๅตŒๅ…ฅ๏ผ‰ใ€‚่ฟ™ๆ˜ฏ Netflix Prize ็š„่Žทๅฅ–ๆ–นๆกˆ๏ผŒ็ŽฐไปฃๆŽจ่็š„ๅŸบ็ก€ใ€‚

Core idea: If User A and User B have similar historical behavior, what A likes, B will probably also like.

2. ๅŸบไบŽๅ†…ๅฎน็š„่ฟ‡ๆปค (Content-Based Filtering)

ๆ นๆฎ็‰ฉๅ“ๆœฌ่บซ็š„็‰นๅพ๏ผˆ็ฑปๅž‹ใ€ๆผ”ๅ‘˜ใ€ๅฏผๆผ”ใ€ๆ ‡็ญพ๏ผ‰ๆฅๆŽจ่็›ธไผผๅ†…ๅฎนใ€‚่งฃๅ†ณๅ†ทๅฏๅŠจ้—ฎ้ข˜๏ผˆๆ–ฐ็‰ฉๅ“ๆฒกๆœ‰ไบคไบ’ๆ•ฐๆฎ๏ผ‰็š„ๅ…ณ้”ฎๆ‰‹ๆฎตใ€‚

Recommends items with similar intrinsic features. Key for solving the cold start problem (new items have no interaction data).

3. ๆททๅˆๆ–นๆณ• (Hybrid)

็”Ÿไบง็ณป็ปŸๅ‡ ไนŽ้ƒฝๆ˜ฏๆททๅˆ็š„ใ€‚YouTube ็š„ DNN-based ๆŽจ่ใ€TikTok ็š„ๅŒๅก”ๆจกๅž‹๏ผŒๆœฌ่ดจไธŠ้ƒฝๆ˜ฏๅฐ†ๅคšไธชไฟกๅท่žๅˆๆˆไธ€ไธช็ปŸไธ€็š„ๆŽ’ๅบๆจกๅž‹ใ€‚

Production systems are almost always hybrid. The trick is knowing when to apply each.


ๅ…ณ้”ฎๅฟƒๆ™บๆจกๅž‹๏ผšๅ€™้€‰็”Ÿๆˆ โ†’ ็ฒ—ๆŽ’ โ†’ ็ฒพๆŽ’ / The Funnel Mental Model

ๅ…จ้‡็‰ฉๅ“ๆฑ  (ๆ•ฐๅไบฟ)
    โ†“  ๅ€™้€‰็”Ÿๆˆ (Candidate Generation)
ๅ€™้€‰้›†ๅˆ (~1000 items)
    โ†“  ็ฒ—ๆŽ’ (Pre-ranking)
็ฒ—ๆŽ’็ป“ๆžœ (~100 items)
    โ†“  ็ฒพๆŽ’ (Ranking)
็ฒพๆŽ’็ป“ๆžœ (~20 items)
    โ†“  ้‡ๆŽ’ / ไธšๅŠก้€ป่พ‘ (Re-ranking)
ๆœ€็ปˆๅฑ•็คบ (~10 items)

Every major recommendation system uses this funnel. The key insight: each stage trades recall for precision. At the top, you want to cast a wide net; at the bottom, you apply your most expensive, most accurate model.


Part 2: Step-by-Step Implementation / ไธ€ๆญฅไธ€ๆญฅๅฎž็Žฐ (8 min)

็ณป็ปŸๆžถๆž„ๅ…จๅ›พ / Full Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Data Layer โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ User Events โ”‚ โ”‚ Item Catalog โ”‚ โ”‚ User Profiles โ”‚ โ”‚ โ”‚ โ”‚ (Kafka) โ”‚ โ”‚ (Postgres) โ”‚ โ”‚ (Redis + DynamoDB)โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Stream โ”‚ โ”‚ Batch โ”‚ โ”‚ โ”‚ โ”‚ Processing โ”‚ โ”‚ Processing โ”‚ โ”‚ โ”‚ โ”‚ (Flink) โ”‚ โ”‚ (Spark) โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Feature Store โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Online Store โ”‚ โ”‚ Offline Store โ”‚ โ”‚ โ”‚ โ”‚ (Redis) โ”‚ โ”‚ (S3 + Hive/Delta Lake) โ”‚ โ”‚ โ”‚ โ”‚ <10ms reads โ”‚ โ”‚ batch feature computation โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Retrieval + Ranking Pipeline โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Candidate Generation โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ ANN Index โ”‚ โ”‚ Collab Filterโ”‚ โ”‚ Trending โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ (FAISS/ โ”‚ โ”‚ (item-item โ”‚ โ”‚ / Popular โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Pinecone) โ”‚ โ”‚ similarity) โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ merge & dedupe (~1000) โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Pre-Ranking (Light Model, e.g. LightGBM) โ”‚ โ”‚ โ”‚ โ”‚ Fast features only, prune to ~100 candidates โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Ranking (Heavy Model, e.g. Deep Neural Network) โ”‚ โ”‚ โ”‚ โ”‚ Full feature set, predict P(engagement | user, item) โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Re-Ranking โ”‚ โ”‚ โ”‚ โ”‚ Diversity injection, business rules, A/B slot โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ API Layer โ”‚ โ†’ Returns final ranked list to client โ”‚ (Go/Java) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ๆžถๆž„ๅ…ณ้”ฎ่ทฏๅพ„้ชŒ่ฏ / Architecture Cross-Check

Let's trace a real request through the system for user u123 visiting YouTube homepage:

  1. Request arrives โ†’ API Layer receives GET /feed?userId=u123
  2. Feature hydration โ†’ Redis lookup: user's watch history embeddings, session context (time of day, device)
  3. Candidate Generation (parallel):

- FAISS ANN search: find top-500 videos whose embeddings are closest to u123's user embedding

- Item-item CF: pull precomputed "similar to recently watched" list from Redis cache (~300 items)

- Trending pool: top 200 globally trending (pre-computed, refreshed every 5 min)

- Dedupe โ†’ ~1,000 candidates

  1. Pre-Ranking: LightGBM model scores all 1,000 with cheap features (category match, recency) โ†’ prune to top 100
  2. Ranking: Deep NN model (DLRM or two-tower) scores 100 candidates with full features โ†’ returns P(click), P(watch_30s)
  3. Re-Ranking: diversity injection (no 3 consecutive videos from same channel), filter watched videos, A/B experiment slot assignment
  4. Response: Return top 20 video IDs with predicted scores โ†’ client renders

Code path verification against diagram: Steps 3-4-5-6 map exactly to the four pipeline boxes. โœ…


ๆ ธๅฟƒไปฃ็ ๏ผšๅŒๅก”ๆจกๅž‹ๆœๅŠก็ซฏๆŽจ็† / Two-Tower Model: Serving Architecture

# two_tower_retrieval.py
# The Two-Tower (Dual Encoder) model architecture used by YouTube DNN, TikTok, etc.
# User tower and item tower are trained jointly but served independently.

import numpy as np
from dataclasses import dataclass
from typing import List
import faiss  # pip install faiss-cpu

@dataclass
class UserEmbedding:
    user_id: str
    vector: np.ndarray  # shape: (128,)
    updated_at: float   # unix timestamp

@dataclass 
class ItemEmbedding:
    item_id: str
    vector: np.ndarray  # shape: (128,)

class TwoTowerRetriever:
    """
    Manages ANN index for fast approximate nearest neighbor retrieval.
    
    In production: user embeddings updated every 5 min (stream processing),
    item embeddings updated every 6 hours (batch job).
    """
    
    def __init__(self, embedding_dim: int = 128):
        self.embedding_dim = embedding_dim
        
        # FAISS IVF (Inverted File) index โ€” production choice for >10M items
        # IVFFlat: split vectors into n_clusters, only search relevant clusters
        # Tradeoff: ~10% accuracy loss vs exhaustive search, 100x faster
        n_clusters = 1000  # rule of thumb: sqrt(n_items) for 10M items
        quantizer = faiss.IndexFlatIP(embedding_dim)  # Inner product = cosine similarity (on normalized vectors)
        self.index = faiss.IndexIVFFlat(quantizer, embedding_dim, n_clusters, faiss.METRIC_INNER_PRODUCT)
        
        self.item_ids: List[str] = []  # parallel array: index position โ†’ item_id
        self.is_trained = False
        
    def build_index(self, item_embeddings: List[ItemEmbedding]):
        """
        Offline batch job: (re)build the ANN index.
        Called every 6 hours, swapped atomically (blue-green pattern).
        """
        vectors = np.array([e.vector for e in item_embeddings], dtype='float32')
        
        # Normalize to unit vectors โ€” converts cosine similarity to inner product
        faiss.normalize_L2(vectors)
        
        # Train the IVF quantizer (learns cluster centroids via k-means)
        # Requires at least 30 * n_clusters training samples
        self.index.train(vectors)
        self.index.add(vectors)
        
        self.item_ids = [e.item_id for e in item_embeddings]
        self.is_trained = True
        
        # Production note: after building, serialize index to S3
        # faiss.write_index(self.index, '/tmp/item_index.faiss')
        
    def retrieve(self, user_embedding: UserEmbedding, top_k: int = 500) -> List[str]:
        """
        Online serving: retrieve top_k candidates for a given user.
        Target latency: <5ms for 10M items.
        """
        if not self.is_trained:
            raise RuntimeError("Index not built. Run build_index() first.")
        
        query = user_embedding.vector.reshape(1, -1).astype('float32')
        faiss.normalize_L2(query)  # same normalization as at index time
        
        # nprobe: how many clusters to search. Higher = more accurate, slower.
        # Production: nprobe=50 gives ~95% recall@500 with <5ms latency
        self.index.nprobe = 50
        
        distances, indices = self.index.search(query, top_k)
        
        # Convert flat indices back to item IDs; filter -1 (empty slots)
        return [
            self.item_ids[idx] 
            for idx in indices[0] 
            if idx != -1
        ]


class RankingModel:
    """
    Simplified ranking model interface.
    In production: DLRM (Deep Learning Recommendation Model) or 
    a gradient boosted tree (XGBoost/LightGBM) for pre-ranking.
    """
    
    def score(self, user_features: dict, item_features: dict) -> float:
        """
        Returns P(engagement | user, item).
        
        Features include:
        - User: embedding, historical CTR, session context, demographic
        - Item: embedding, global CTR, freshness, category, creator follower count
        - Cross features: user ร— item category match, historical interactions
        """
        # Placeholder โ€” real model would call a TF Serving / TorchServe endpoint
        # or run a local ONNX model for sub-millisecond latency
        raise NotImplementedError("Load your model weights here")


class RecommendationPipeline:
    """
    Orchestrates the full retrieval + ranking funnel.
    """
    
    def __init__(self, retriever: TwoTowerRetriever, ranker: RankingModel):
        self.retriever = retriever
        self.ranker = ranker
        
    def get_recommendations(
        self, 
        user_embedding: UserEmbedding,
        user_features: dict,
        item_features_store: dict,  # item_id โ†’ features
        n_final: int = 20
    ) -> List[str]:
        
        # Stage 1: Candidate Generation (~1000 items)
        candidates = self.retriever.retrieve(user_embedding, top_k=500)
        # (In practice, merge with CF-based and trending pools here)
        
        # Stage 2: Ranking (score all candidates)
        scored = []
        for item_id in candidates:
            if item_id not in item_features_store:
                continue  # item not in catalog (e.g., deleted)
            score = self.ranker.score(user_features, item_features_store[item_id])
            scored.append((item_id, score))
        
        # Sort by score descending
        scored.sort(key=lambda x: x[1], reverse=True)
        
        # Stage 3: Re-ranking (diversity injection)
        final = self._inject_diversity(scored, n_final)
        return final
    
    def _inject_diversity(self, scored: List[tuple], n: int) -> List[str]:
        """
        Ensure we don't show all items from the same creator/category.
        Simple approach: cap same-creator items to 2 in the final slate.
        """
        # In production: use MMR (Maximal Marginal Relevance) or DPP (Determinantal Point Process)
        result = []
        seen_count = {}  # creator_id โ†’ count
        
        for item_id, score in scored:
            if len(result) >= n:
                break
            # placeholder diversity check
            result.append(item_id)
            
        return result

็‰นๅพๅทฅ็จ‹๏ผšๅฎžๆ—ถ vs ็ฆป็บฟ / Feature Engineering: Real-Time vs Offline

# feature_store_client.py
# Production systems use a feature store (Feast, Tecton, or custom) 
# to serve features with consistent definitions between training and serving.

import redis
import json
from typing import Dict, Any, Optional

class FeatureStoreClient:
    """
    Unified interface for online (Redis) and offline (S3/Hive) features.
    
    Key insight: Training/serving skew is the #1 cause of recommendation 
    quality degradation. A feature store ensures the same feature computation 
    logic is used at both training time and serving time.
    """
    
    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client
        
    def get_user_features(self, user_id: str) -> Optional[Dict[str, Any]]:
        """
        Fetch real-time user features from Redis.
        
        What's stored:
        - user_embedding: 128-dim float vector (updated every 5 min by Flink)
        - recent_categories: top-3 categories clicked in last hour (stream)
        - session_count_today: integer counter (atomic increment)
        - historical_ctr: click-through rate across all sessions (batch, updated hourly)
        """
        raw = self.redis.get(f"user_features:{user_id}")
        if raw is None:
            # Cold start: new user, return default features
            return self._default_user_features()
        return json.loads(raw)
    
    def get_item_features(self, item_id: str) -> Optional[Dict[str, Any]]:
        """
        Fetch item features.
        
        What's stored:
        - item_embedding: 128-dim float vector (updated every 6h by batch job)
        - global_ctr_7d: click-through rate over last 7 days (batch)
        - freshness_score: decay function of item age (recomputed every 15 min)
        - creator_id: string (static)
        - category: enum (static)
        """
        raw = self.redis.get(f"item_features:{item_id}")
        if raw is None:
            return None  # item not in catalog
        return json.loads(raw)
    
    def _default_user_features(self) -> Dict[str, Any]:
        """
        For new users: use global popular items (popularity-based fallback).
        This is the standard cold-start solution.
        """
        return {
            "user_embedding": [0.0] * 128,  # zero vector โ†’ will match popular items
            "recent_categories": [],
            "session_count_today": 0,
            "historical_ctr": 0.05  # global average CTR
        }
    
    def update_user_embedding(self, user_id: str, new_embedding: list, ttl_seconds: int = 86400):
        """
        Called by Flink stream job after processing user events.
        TTL ensures stale embeddings don't persist indefinitely.
        """
        current = self.get_user_features(user_id) or {}
        current["user_embedding"] = new_embedding
        self.redis.setex(
            f"user_features:{user_id}",
            ttl_seconds,
            json.dumps(current)
        )

Part 3: Edge Cases & Gotchas / ่พน็•Œๆƒ…ๅ†ต (2 min)

1. ๅ†ทๅฏๅŠจ้—ฎ้ข˜ (Cold Start Problem)

  • ๆ–ฐ็”จๆˆท๏ผšๆฒกๆœ‰ๅކๅฒๆ•ฐๆฎใ€‚่งฃๆณ•๏ผšๅฑ•็คบๅ…จๅฑ€็ƒญ้—จๅ†…ๅฎน + ๅผ•ๅฏผ็”จๆˆทๅฃฐๆ˜Žๅๅฅฝ๏ผˆonboarding๏ผ‰+ ๅˆฉ็”จ demographic ็‰นๅพใ€‚
  • ๆ–ฐ็‰ฉๅ“๏ผšๆฒกๆœ‰ไบคไบ’ๆ•ฐๆฎใ€‚่งฃๆณ•๏ผšๅŸบไบŽๅ†…ๅฎน็‰นๅพ๏ผˆๆ ‡็ญพใ€ๅˆ†็ฑปใ€ๆ–‡ๆœฌๅตŒๅ…ฅ๏ผ‰ๅปบ็ซ‹ item embedding๏ผ›ๅˆๅง‹ๆ—ถ็ป™ไบˆๆ›ๅ…‰ไฟ้šœ๏ผˆexploration๏ผ‰ใ€‚

2. ่ฟ‡ๆปคๆฐ”ๆณก (Filter Bubble)

ๅฆ‚ๆžœๅชๆŽจ่็”จๆˆทๅทฒ็ปๅ–œๆฌข็š„๏ผŒไผš่ถŠๆฅ่ถŠ็ช„ใ€‚่งฃๆณ•๏ผšๅœจ re-ranking ้˜ถๆฎตไธปๅŠจๆณจๅ…ฅๅคšๆ ทๆ€ง๏ผˆMMRใ€้šๆœบๆŽข็ดขๆฏ”ไพ‹๏ผ‰ใ€‚

3. ไฝ็ฝฎๅๅทฎ (Position Bias)

็”จๆˆทๆ›ดๅฏ่ƒฝ็‚นๅ‡ปๆŽ’ๅ้ ๅ‰็š„ๅ†…ๅฎน๏ผŒไธไปฃ่กจๅฎƒ็œŸ็š„ๆ›ดๅฅฝใ€‚ๅœจ่ฎญ็ปƒๆ—ถ้œ€่ฆๅฏน position ๅšๅŽปๅๅค„็†๏ผˆposition debiasing๏ผ‰ใ€‚

4. ๆ—ถๆ•ˆๆ€ง vs ็›ธๅ…ณๆ€ง (Freshness vs Relevance)

่€ๅ†…ๅฎน็š„ CTR ๆ•ฐๆฎๆ›ดไธฐๅฏŒไฝ†ๅฏ่ƒฝ่ฟ‡ๆ—ถ๏ผ›ๆ–ฐๅ†…ๅฎนๆฒกๆœ‰ CTR ไฝ†ๅฏ่ƒฝๆ›ดๅŠๆ—ถใ€‚่งฃๆณ•๏ผšfreshness score๏ผˆๆ—ถ้—ด่กฐๅ‡ๅ‡ฝๆ•ฐ๏ผ‰+ ๆ–ฐๅ†…ๅฎนๆ›ๅ…‰ไฟ้šœใ€‚

5. ่ฎญ็ปƒ-ๆœๅŠกๅๅทฎ (Training/Serving Skew)

่ฎญ็ปƒ็”จๅކๅฒ batch ๆ•ฐๆฎ๏ผŒๆœๅŠกๆ—ถ็”จๅฎžๆ—ถๆ•ฐๆฎโ€”โ€”ๅฆ‚ๆžœ็‰นๅพ่ฎก็ฎ—้€ป่พ‘ไธไธ€่‡ด๏ผŒๆจกๅž‹ไผš้™็บงใ€‚Feature Store ๆ˜ฏๆ ‡ๅ‡†่งฃๆณ•ใ€‚

6. ๆ•ฐๆฎ้ฃž่ฝฎ (Data Flywheel)

ๆ›ดๅฅฝ็š„ๆŽจ่ โ†’ ๆ›ดๅคš็”จๆˆท โ†’ ๆ›ดๅคšๆ•ฐๆฎ โ†’ ๆ›ดๅฅฝ็š„ๆจกๅž‹ใ€‚ไฝ†ไนŸๆ„ๅ‘ณ็€้ฉฌๅคชๆ•ˆๅบ”๏ผšๅคด้ƒจๅ†…ๅฎน่ขซๆŽจ่่ถŠๅคš๏ผŒๅฐพ้ƒจๅ†…ๅฎน่ถŠๆฅ่ถŠ้šพ่ขซๅ‘็Žฐใ€‚

Part 4: Real-World Application / ๅฎž้™…ๅบ”็”จ (2 min)

YouTube: Deep Neural Network for Recommendations (2016)

Google ๅœจ 2016 ๅนดๅ‘่กจ็š„่ฎบๆ–‡ๆ่ฟฐไบ†ไธค้˜ถๆฎตๆžถๆž„๏ผšๅ€™้€‰็”Ÿๆˆ็ฝ‘็ปœ๏ผˆsoftmax over item embedding space๏ผ‰+ ๆŽ’ๅบ็ฝ‘็ปœ๏ผˆDNN predicting watch time๏ผ‰ใ€‚่ฟ™ๆ˜ฏไธš็•Œ็ฌฌไธ€ไธชๅฐ†ๆทฑๅบฆๅญฆไน ่ง„ๆจกๅŒ–ๅบ”็”จไบŽๆŽจ่็š„้‡Œ็จ‹็ข‘ใ€‚

Paper: https://research.google.com/pubs/pub45530.html

TikTok: Dual Encoder + Realtime Feedback Loop

TikTok ็š„ๆ ธๅฟƒ็ซžไบ‰ๅŠ›ๅœจไบŽ่ถ…ๅฟซ็š„ๅ้ฆˆๅพช็Žฏ๏ผš็”จๆˆทๆฏๅฎŒๆˆไธ€ไธช่ง†้ข‘๏ผŒไฟกๅท๏ผˆwatch time, replay, share๏ผ‰ๅœจ็ง’็บงๅ†…ๆ›ดๆ–ฐ็”จๆˆท embedding๏ผŒไธ‹ไธ€ๆกๆŽจ่็ซ‹ๅณๅๆ˜ ใ€‚่ฟ™ๅฐฑๆ˜ฏ"่ถŠๅˆท่ถŠๅ‡†"็š„ๅทฅ็จ‹ๆœฌ่ดจใ€‚

Netflix: Offline Evaluation with A/B Testing

Netflix ็š„ๅทฅ็จ‹ๅšๅฎข่ฏฆ็ป†ๆ่ฟฐไบ†ๅฆ‚ไฝ•็”จ็ฆป็บฟๆŒ‡ๆ ‡๏ผˆnDCG, recall@k๏ผ‰ๅšๅฟซ้€Ÿๅฎž้ชŒ็ญ›้€‰๏ผŒๅ†็”จๅœจ็บฟ A/B test ้ชŒ่ฏ็œŸๅฎžไธšๅŠกๆŒ‡ๆ ‡๏ผˆretention, watch time๏ผ‰ใ€‚็ปๅคงๅคšๆ•ฐ็ฆป็บฟ่กจ็Žฐๅฅฝ็š„ๆจกๅž‹ไธŠ็บฟๅŽๆ•ˆๆžœไธ€่ˆฌโ€”โ€”่ฟ™ๆ้†’ๆˆ‘ไปฌ offline metrics โ‰  online metricsใ€‚

Feature Engineering at Scale: Uber Michelangelo

Uber ็š„ Michelangelo ๅนณๅฐๆ˜ฏไธš็•Œๅ…ฌๅผ€็š„ๆœ€ๅฎŒๆ•ด็š„ ML ๅนณๅฐๆกˆไพ‹๏ผŒๅ…ถ Feature Store ่งฃๅ†ณไบ†"ๅŒไธ€็‰นๅพๅœจ่ฎญ็ปƒๅ’ŒๆŽจ็†ๆ—ถ่ฆไฟๆŒๅฎŒๅ…จไธ€่‡ด"็š„ๅทฅ็จ‹้šพ้ข˜ใ€‚ๅ€ผๅพ—ๆทฑ่ฏปใ€‚

Blog: https://www.uber.com/blog/michelangelo-machine-learning-platform/

Part 5: Interview Simulation / ้ข่ฏ•ๆจกๆ‹Ÿ (3 min)

Q1: How would you handle the cold start problem for new users?

A: Two-pronged approach. Short-term: show globally popular items, use any available signals (device type, geo, time of day). Long-term: onboarding flow where user explicitly declares interests; these explicit signals are extremely high-value. Once the user has 5-10 interactions, switch to personalized recommendations.

Q2: How do you ensure the recommendation system doesn't create a filter bubble?

A: Enforce diversity constraints at the re-ranking stage. Two techniques: (1) Determinantal Point Process (DPP) โ€” mathematically maximizes diversity while preserving relevance; (2) ฮต-greedy exploration โ€” dedicate 10-15% of recommendation slots to "exploration" (novel categories, emerging creators). Also track diversity metrics (intra-list diversity, catalog coverage) in your A/B test dashboards.

Q3: The ranking model starts degrading after a few months. What do you do?

A: Likely causes: (1) distribution shift โ€” user behavior changed, model trained on stale data; (2) feature drift โ€” upstream feature pipeline changed but model wasn't retrained. Solution: implement continuous training (retrain model weekly/daily on fresh data). Monitor feature distributions with alerts on statistical drift (KL divergence). Use a Champion/Challenger framework to always have a fresh challenger model ready.

Q4: How would you design the system to handle 10M concurrent users at peak?

A: The key is pre-computation. You can't run full retrieval + ranking in real-time for everyone. Strategies: (1) Pre-compute recommendations for your top 5% most active users every 5 minutes (they drive 80% of traffic); (2) Cache retrieval candidates โ€” user embeddings change slowly, so cache the ANN search results for 5-10 minutes; (3) Stateless ranking servers behind a load balancer โ€” ranking is CPU/GPU bound and scales horizontally; (4) Feature hydration via Redis with local read replicas near each ranking server.

Q5: How do you measure if your recommendation system is actually good?

A: Never rely on a single metric. Offline: precision@k, recall@k, nDCG (normalized discounted cumulative gain), AUC on held-out test set. Online: CTR, watch time per session, D7/D30 retention. Business: revenue per user, subscriber growth. Crucially, watch for Goodhart's Law โ€” optimizing CTR alone leads to clickbait. Use a balanced scorecard. Also track long-term metrics like catalog coverage and diversity to avoid the filter bubble trap.


Next Saturday Deep Dive: Coming up in the Trees / DP territory โ€” Merge K Sorted Lists or advanced tree traversal patterns.