- AtlasMoth Newsletter
- Posts
- Netflix's Search Design Theory
Netflix's Search Design Theory
Founder-led designs

Receive Honest News Today
Join over 4 million Americans who start their day with 1440 – your daily digest for unbiased, fact-centric news. From politics to sports, we cover it all by analyzing over 100 sources. Our concise, 5-minute read lands in your inbox each morning at no cost. Experience news without the noise; let 1440 help you make up your own mind. Sign up now and invite your friends and family to be part of the informed.
Hey, it’s Kushagra. Let’s geek out for a sec!
You remember how Netflix made search work in their giant content graph? They’ve been leveling up.
Now they’ve rolled out Graph Search to the entire engineering team—100+ apps, 50 indexes, and counting.
But here’s the twist:
They just built Reverse Search.
Instead of searching for something, you start with it and find all the queries matching it.
Wild, right?
Let’s unpack how Studio Engineering and Content Engineering pulled this off.
Initial Idea?
A post-production coordinator at Netflix tracks a bunch of movies at different stages, like pre-production, shooting, and post.
She works with teams like Legal, Creative, and Title Launch, keeping tabs on how her movies are doing.
She’s not following specific movies, but subscribing to queries that give her dynamic lists of movies.
But there’s a problem: when a movie changes, the team don’t know who to alert, because they don't know which movies each person cares about.
So, they could just save the searches and keep querying for them all the time, but since they’re in a big federated graph, this would put a ton of traffic on every service we connect to.
They'd have to pick: fast updates or less strain on our graph?
If they could answer, “Would this movie show up in this query?”, they could trigger precise re-queries based on changes, without stressing the whole system.
Graph Search runs on Elasticsearch, which has what they need:
Percolator fields that can index queries
Percolate queries to see if a query matches a specific document

So, think of it like this —
Most search stuff works like this: you type “Spanish flicks shot in Mexico City” and boom, it pulls up stuff like Roma or Familia. Cool, right?
But percolate flips the script. Now you toss in one doc — say, Roma — and it tells you all the saved searches it fits, like “Spanish flicks” or “dramas with scripts”.
We call this SavedSearches. It’s like a smart, saved filter that stays tied to an index. Set it once, and it keeps track for you.
That filter, made in Graph Search DSL, gets flipped to an Elasticsearch query and dropped in a percolate field.
Wanna know why they made our DSL and didn’t just roll with the usual Elasticsearch lingo?
Check the “Query Language” part in How Netflix Content Eng makes a fed graph searchable (Part 2). It spills the tea.
They call this match-finding trick ReverseSearch. It’s the chillest part of the whole thing.
They added a new plug (a “resolver”) to DGS for Graph Search.
You feed it an index and a doc, and it spits out all the saved searches that vibe with that doc — thanks to a slick percolate query.
"""
Query for retrieving all the registered saved searches, in a given index,
based on a provided document. The document in this case is an ElasticSearch
document that is generated based on the configuration of the index.
"""
reverseSearch(
after: String,
document: JSON!,
first: Int!,
index: SearchIndex!): SavedSearchConnection
When you save a SavedSearch, it kicks off a new move (we call it a “mutation”) in Graph Search DGS.
That move drops the search as a query in a percolate field, so it stays there for good.
"""
Mutation for registering and updating a saved search. They need to be updated
any time a user adjusts their search criteria.
"""
upsertSavedSearch(input: UpsertSavedSearchInput!): UpsertSavedSearchPayload
Adding percolate fields low-key changed how we set up our index flows in Graph Search.
Before, each Graph Search index had just one flow — now it’s two:
• One to stash docs
• One to stash saved search stuff in a Percolate index.
We split them up so we could tweak the speed for each kind on its own. Smart, right?
Now, Elasticsearch is kinda picky.
The percolate index needs to look like the docs it runs with — same shape, same fields. So both need the same map (aka the blueprint of what goes where).
To keep it chill, they used index templates.
They tell new indexes what map to use.
With index patterns, they make sure both the doc index and the percolate index share the same map.
Process?
It’s not as easy as just grabbing the input from the GraphQL query, flip it to an ES query, and stashing it.
Why? One word: versions. Yeah… they came in, caused some mess, and made the whole thing less chill.
So here’s how we set up the Percolate index flow to deal with that chaos.

A Data Movement and Processing Platform @ Netflix to learn more about Data Mesh.
When you tweak a SavedSearch, they stash it in CockroachDB. That DB then shoots out a CDC event (it’s like: “yo, stuff changed!”).
All SavedSearches live in one big table, so first they gotta filter the ones tied to the index they care ‘bout. They use a filter step for that.
Now here’s the catch — what they save in the DB is in our Graph Search lingo (DSL), not the same as the one used by Elasticsearch. So they can’t just toss it in the percolator index straight up.
What do they do? They hit the Graph Search DGS with a mutation. It flips their DSL to an ES query.
Then, boom — they index that ES query in the right percolate index, as a percolate field.
If it works, great. If not? They chuck it in the Dead Letter Queue (DLQ) — our “oops” pile — so we can fix stuff like “yo, that field’s gone now”.
Now for why we go through all this mess: versioning.
Say users start tagging flicks that got pets or wild stuff in ‘em.
Folks wanna see “all movies with pets”, right? But — oops — their index doesn’t even know that field yet.
Since their old map doesn’t have that “has_animals” field, they can’t filter on it.
At Atlasmoth Designs, we turn bold ideas into unforgettable brand identities—crafted to inspire, built to grow your success.
So what’s the move?
New index version. That way, new fields show up, and folks can vibe with the fresh filters.
When they tweak an index and need a new map, like when they add that "has pets" tag, Graph Search makes a new ver of the ES index, plus a new pipe to fill it up.
This fresh pipe reads from a log-packed Kafka feed in Data Mesh. Why’s that clutch? 'Cause they can re-fill the whole thing fast — no need to bug the data folks to send old stuff again.
Both the old and new pipes run at the same time for a bit. Once the new one’s catch up, Graph Search swaps over to it, smooth, with some index alias trickery.
Now, if they make a new doc index, they also need a new percolate index for the saved queries — 'cause both gotta speak the same map.
That new percolate index? Yeah, it needs to be backfilled too when they switch vers.
That’s why their pipe’s built this way — they can hit the same log-packed Kafka feed to re-fill all those SavedSearches when they spin up a fresh percolate pipe.

Version translation
Hot Take
This could also be lit for live UIs.
Like, what if — instead of just pulling search once — you got live hits as things change?
Smooth. Fast. Real-time vibes.
Rounded icons. Glass UI. iOS 19 is shaping how tech feels, not just looks.📱
This track gave me a serious boost—check out ‘Oppenheimer’s Gambit’ by Thunder Porpoise🎵
How Founders Can Level Up Their Design🚀
“Design is not just what it looks like and feels like. Design is how it works.”
Thank you for fluttering by and reading our newsletter! If you found it as captivating and engaging as my vibrant wings, please share it with your friends and colleagues. Help us spread the excitement and keep the gamified energy soaring!
Reply