Vivek's Field Notes

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:

  1. What we observed in the browser (our experiments, desktop web, one account)
  2. Monolith: ByteDance’s paper on real-time training
  3. 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)

WhatWhat we saw
Feed deliveryOne GraphQL call returns ~14 reels. No request per swipe
PreloadingVideos for upcoming reels start downloading before you reach them
Event loglogging_client_events, batched every ~10 s, ~240 events in 4 min
Per-reel signalvideo_exited carries the exact second you left, the reel length and the loop count
The join keyEach reel arrives with a tracking_token. Every event about that reel sends it back (73/73 matched)
Next-page requestSends only the IDs of seen_reels. Watch time travels separately in the event log
Third partiesNone. Everything went to Instagram/Meta domains

So the whole system is a loop:

You scrollwatch, skip, loopEvent logevery ~10 sJoinerby tracking_tokenTraineronline updatesRankerscores candidatesnext page: ~14 reels, each stamped with a tracking_tokenblue = your signals going up Β· orange = the next batch coming down
The loop. "You scroll", "Event log" and the returning page are observed. Joiner, trainer and ranker live on Meta's servers: that part is our design, not something we saw.

2. Requirements

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.

Billions of reels (Meta AI blog)
Thousands of candidates (retrieval)
~100 (light model + integrity checks)
~14 per page (observed)
Cheap models shrink the list, so the expensive model only scores ~100 reels.

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):

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)]

Does Instagram behave like this? Our panda run (observed)

0510first like + share β†’0123Page 4: 1 giant panda of 14 reelsPage 4: 2 animal reels of 144Page 5: 9 animal reels of 15 (no giant panda)5Page 6: 1 giant panda of 14 reels, slot #0Page 6: 2 animal reels of 146Page 7: 2 giant pandas of 13 reels, slots #0 and #7Page 7: 5 animal reels of 137Page 8: 1 giant panda of 14 reels, slot #0Page 8: 5 animal reels of 148Page 9: 1 giant panda of 14 reelsPage 9: 4 animal reels of 149Page 10: 1 giant panda of 13 reelsPage 10: 2 animal reels of 1310feed page (~14 reels each)
Giant panda reelsAll animal reels (incl. pandas)
One account, desktop web, 13 minutes. Topics judged from captions, so counts are a lower bound. Hover a bar for details.
Table view
Page012345678910
Giant pandas00001012111
All animals00002925542
Reels in page1413141414151413141413

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.

Embeddingsuser, reel, creator, audio… (720 features)
Wukong layer 1FM block βˆ₯ linear block β†’ order 1–2
Wukong layer 2order 1–4
Wukong layer 3order 1–8
MLP headP(click), P(like / follow)
Each layer doubles the interaction order. Stack L layers and you capture up to 2L-way interactions.

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

  1. 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.
  2. A join key makes learning possible. Every served item carries a token, and every event echoes it back.
  3. Freshness is a sync problem. Push only the embedding rows that changed, every minute. Ship the dense weights daily.
  4. Scale the interactions, not just the tables. Stacked FM layers give the ranker a scaling law.

Sources

#Blog #System-Design #Recommendation-Systems #Instagram #Notes