مسئله: کاربر باز میکند و فید دنبالشدههایش را میبیند — فوری. مسئله واقعی: فید هر کاربر «کوئری» باشد یا «از پیش ساخته»؟ این همان جدال Fanout است.
۱-۲) نیازمندی و تخمین
- FR: پست عکس/ویدیو، فالو، فید معکوسزمانی/رتبهبندیشده، لایک/کامنت.
- NFR: باز شدن فید <200ms؛ تحمل کهنگی چند ثانیهای؛ Read-Heavy شدید (~100:1).
- فرض: 500M DAU، روزی 100M پست جدید، هر کاربر ۲۰۰ فالو.
۳) طراحی — دو استراتژی Fanout
| Fanout-on-Write (Push) | Fanout-on-Read (Pull) | |
|---|---|---|
| ایده | هنگام پست، ID اش به فیدِ کششده همه فالوورها push میشود | هنگام بازکردن فید، پستهای همه دنبالشدهها کوئری و ادغام میشود |
| خواندن | فوری — فقط خواندن یک لیست آماده | کند — صدها کوئری و merge |
| نوشتن | سنگین: سلبریتی با 100M فالوور = 100M نوشتن! | ارزان — یک insert |
| مناسب | کاربران عادی | سلبریتیها |
راهحل واقعی هیبرید است: برای اکثریت، push به فیدهای Redis (لیست ~800 آیتمی از post_id ها)؛ برای سلبریتیها (فالوور > آستانه) push نکن — هنگام خواندن، پستهای تازه سلبریتیهای دنبالشده pull و merge میشوند. بهترینِ هر دو دنیا.
DIAGRAMمسیر پست جدید (کاربر عادی)
Post svc
→
Kafka
→
Fanout workers
→
Redis فید فالوورها
۴) عمق
- فید فقط ID نگه میدارد؛ محتوا (کپشن، URL عکس) با mget از کش پستها hydrate میشود — تکرارِ داده کمینه.
- رتبهبندی: کاندیدها از فید + امتیازدهی ML در لحظه خواندن؛ جدایی «تولید کاندید» از «رتبه».
- مدیا: آپلود مستقیم به Object Storage با URL امضاشده؛ نمایش از CDN با چند سایز از-پیش-ساخته.
- شمارنده لایک: Eventual — تجمیع در Redis و flush دورهای؛ هرگز UPDATE per-like روی DB اصلی.
به زبان ساده
فید اینستاگرام برای اکثریت از قبل ساخته میشود (fan-out هنگام نوشتن) و برای سلبریتیها هنگام خواندن — چون یک پست سلبریتی به میلیونها صف تزریق، همه را خفه میکند.
مثال واقعی
مثل روزنامه: برای مشترک عادی روزنامه چاپ و پخش میشود؛ اما اخبار فردی مثل رئیسجمهور فقط وقتی کسی پرسید از آرشیو خوانده میشود.