What Reel You Will Get Next
β’ 9 min read
We watched what instagram.com sends while you scroll Reels. Then we liked one panda video. The next batch was full of animals, and pandas kept landing in the top slot.
How does a system learn that in about 80 seconds? Let’s design it from scratch, using three things:
- What we observed in the browser (our experiments, desktop web, one account)
- Monolith: ByteDance’s paper on real-time training
- Wukong: Meta’s paper on scaling ranking models
Ground rule. Instagram’s ranking runs on Meta’s servers, so we can’t see it. Every claim here is labelled observed (we saw it in network traffic), Meta says (public docs) or paper (Monolith or Wukong). Monolith is ByteDance’s system, not Instagram’s, and Meta has never said Reels runs Wukong. We use both as reference designs.
1. What the client actually does (observed)
| What | What we saw |
|---|---|
| Feed delivery | One GraphQL call returns ~14 reels. No request per swipe |
| Preloading | Videos for upcoming reels start downloading before you reach them |
| Event log | logging_client_events, batched every ~10 s, ~240 events in 4 min |
| Per-reel signal | video_exited carries the exact second you left, the reel length and the loop count |
| The join key | Each reel arrives with a tracking_token. Every event about that reel sends it back (73/73 matched) |
| Next-page request | Sends only the IDs of seen_reels. Watch time travels separately in the event log |
| Third parties | None. Everything went to Instagram/Meta domains |
So the whole system is a loop:
2. Requirements
- Functional: given a user, return the next page of ~14 reels, ordered by how likely they are to engage.
- Fresh: a new interest should show up within a page or two, not tomorrow.
- Scale: billions of reels and users. New IDs appear and old ones die every minute.
- Latency: a page must be ready before you hit the end of the current one. Preloading hides the rest.
3. High-level design: the funnel (Meta says)
Meta’s system card for Reels Chaining lists four stages: gather inventory, use signals to pick about 100, predict, then rank by score. Every reel we captured carried inventory_source: "recommended_clips_chaining_model", which matches that name.
Why a funnel? Running the big model on billions of reels for every request would be far too slow. Each stage is cheaper than the next and hands over a shorter list.
The hard parts aren’t the boxes. They’re two properties of the data, both named in the Monolith paper.
4. Problem 1: the features are sparse IDs
Features are what the model looks at for one (you, reel) pair: your user ID, the reel ID, the creator ID, the audio ID, your city, the hour. Almost all of them are IDs. Each ID becomes a learned vector (an embedding) stored in a giant table.
The usual shortcut is to hash IDs into a fixed-size table. Then two reels can land in the same slot and share one vector, and popular IDs make those collisions hurt more.
Monolith’s fix (paper):
- A collisionless hash table (cuckoo hashing). Every ID gets its own vector, and lookups are O(1).
- Frequency filter: an ID only gets a slot after it appears a few times. Rare IDs would be undertrained anyway.
- Expiry: IDs unused for a set period are evicted, so dead reels and inactive users stop eating memory.
5. Problem 2: interests drift by the minute (concept drift)
The pattern in the data keeps changing. Cricket during the IPL. Diwali reels in October. Pandas, right after you like one. A model trained yesterday is already stale.
Monolith’s answer (paper): train online, sync often.
flowchart LR
U[User actions<br/>watch, like, share] --> LK[(Log Kafka)]
MS[Model server] -->|features used<br/>at serve time| FK[(Feature Kafka)]
LK --> J[Online joiner<br/>by request key]
FK --> J
J --> TK[(Training examples)]
TK --> TW[Training workers]
TW --> TPS[Training PS]
TPS -->|sparse: every minute<br/>dense: daily| SPS[Serving PS]
SPS --> MS
TK --> H[(HDFS: batch training)]
- Joiner. Features are logged when a reel is served. Actions arrive later, sometimes days later. Both carry a request key, so they can be paired. Recent ones wait in memory, older ones on disk. Our
tracking_token(73/73 matched) is exactly the kind of key such a joiner needs. That’s an inference: we can’t see Meta’s joiner. - Negative sampling. Skips vastly outnumber likes, so negatives are sampled down. A log-odds correction at serving time undoes the bias.
- Cheap sync. In a short window only a few IDs get touched, so only those embedding rows are pushed, every minute. The paper’s example: 100,000 IDs Γ 1024 dims β 400 MB per minute. Dense weights move slowly, so they sync daily.
- Reliability trade. Snapshots happen once a day. Losing up to a day of updates hurt surprisingly little.
- Payoff (paper). A 30-minute sync beat 1-hour and 5-hour syncs. In a live A/B test on an ads model, online training beat batch training on AUC every day for a week.
Does Instagram behave like this? Our panda run (observed)
Table view
| Page | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Giant pandas | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 2 | 1 | 1 | 1 |
| All animals | 0 | 0 | 0 | 0 | 2 | 9 | 2 | 5 | 5 | 4 | 2 |
| Reels in page | 14 | 13 | 14 | 14 | 14 | 15 | 14 | 13 | 14 | 14 | 13 |
- 0 pandas in 55 reels before. 6 in 83 after, three times in slot #0.
- The very next page (~80 s after the first like) had 9/15 animal reels: a broad “cute animals” burst first, then pandas specifically.
- Pandas came back with Korean and Chinese captions and no shared keyword. That fits embedding similarity better than keyword matching.
- ~14 hours later, with no more panda engagement, pandas had nearly vanished (1/41, last slot).
This is what a real-time system looks like from the outside. We can’t tell which mechanism did it: online training, a real-time retrieval source or short-term user features. Meta’s Explore post mentions real-time sources and hourly retraining. It’s one account with no control run, so treat it as a strong hint, not proof.
6. The ranker: Wukong (paper, Meta)
Now the expensive stage: scoring ~100 reels. The model’s real job is feature interactions. “Likes pandas” alone is weak. “Likes pandas Γ liked something 2 minutes ago Γ reel is from a new creator” is strong.
Older recommenders grew mostly by making embedding tables bigger. That adds memory but little compute, and quality plateaus. Wukong grows the dense interaction part instead, the way LLMs scale.
- FM block: a factorization machine takes dot products between embeddings, then an MLP. Interacting two order-k inputs gives order 2k.
- Linear compress block: recombines embeddings without raising the order, so lower orders survive.
- Both outputs are concatenated, with a residual connection and LayerNorm:
X(i+1) = LN(concat(FMB(Xi), LCB(Xi)) + Xi). - The result (paper): on Meta’s internal dataset (146B examples, 720 features, embedding tables fixed at 627B parameters), quality kept improving across two orders of magnitude of compute, past 100 GFLOP per example. That’s roughly 0.1% better LogLoss per 4Γ compute. The best baseline needed 40Γ more compute to match it.
How this maps to Reels: the system card says Reels predicts things like reshares, watching to the end, likes and audio-page visits, then combines them into one score. A multi-task ranker like Wukong is a natural fit for that stage. That’s our design choice, not a confirmed fact.
7. The full design
flowchart TB
C[Client<br/>~14 reels/page + preload] -->|seen_reels IDs| API[Feed API]
C -->|events every ~10 s<br/>+ tracking_token| LOG[(Event stream)]
API --> R[Retrieval<br/>embeddings: billions to thousands]
R --> P[Light ranker + integrity<br/>to ~100]
P --> W[Heavy ranker<br/>Wukong-style multi-task]
W --> RR[Re-rank<br/>diversity, policy, exploration]
RR -->|page + tracking_token per reel| C
LOG --> J[Online joiner]
API -.->|serve-time features| J
J --> T[Online trainer<br/>Monolith-style]
T -->|sparse rows every minute| W
T -->|sparse rows every minute| R
Takeaways
- Batch the feed, stream the signals. The client pulls ~14 reels at a time but reports behaviour every ~10 s. The next page is the earliest point ranking can react.
- A join key makes learning possible. Every served item carries a token, and every event echoes it back.
- Freshness is a sync problem. Push only the embedding rows that changed, every minute. Ship the dense weights daily.
- Scale the interactions, not just the tables. Stacked FM layers give the ranker a scaling law.
Sources
- Observed: our captures of instagram.com Reels, Oct 20β21 2026 (desktop web, one account, secret values masked).
- Meta Transparency Center, Instagram Reels Chaining AI system.
- Meta AI, The AI behind unconnected content recommendations on Facebook and Instagram (2023).
- Meta Engineering, Scaling the Instagram Explore recommendations system (2023). This one is about Explore, not Reels.
- Liu et al., Monolith: Real Time Recommendation System With Collisionless Embedding Table, ByteDance, 2022.
- Zhang et al., Wukong: Towards a Scaling Law for Large-Scale Recommendation, Meta, ICML 2024.
#Blog #System-Design #Recommendation-Systems #Instagram #Notes