The Percolator in Elasticsearch
How Elasticsearch turns search upside down: instead of asking which documents match a query, it asks which saved queries match a brand-new document.
Imagine a property website with two million visitors who have each saved a search. One person wants a two-bedroom flat in Leeds under nine hundred pounds a month. Another wants a house with a garden near a good school. A third wants any studio close to a train station. Every day, thousands of new listings arrive. When a new flat is posted, the site must work out, within moments, which of those two million saved searches it matches, so that the right people get an email or a phone alert.
The obvious approach is painful. You could run all two million saved searches every time a listing arrives, which would bury your servers. Or you could run each visitor’s search once every few minutes and compare the results with the last run, which is slow, wasteful and still late. Neither feels right, because the problem is the wrong way round. You are not looking for documents that match one query. You have one document and want to know which queries it matches.
Elasticsearch has a built-in feature made for exactly this backwards question. It is called the percolator. This guide explains it from zero. Every new word is explained the first time it appears, and each idea builds on the last. By the end, you will know what the percolator is, how it works inside, where it shines, what it costs and which traps to avoid.
1What Is the Percolator?
We start with the simple idea behind the name, and then look at how Elasticsearch makes that idea real.
Search Normally Works One Way
Elasticsearch is a search engine. It stores items called documents, such as products, articles or listings, inside containers called indices. When someone runs a query, which is a description of what they are looking for, Elasticsearch finds the documents that match it. In this normal direction, the documents are stored and the queries are temporary. A query arrives, gets answered and is forgotten.
The Percolator Flips It
The percolator turns this around. It lets you store queries as if they were documents. Later, when a new document arrives, you hand it to Elasticsearch and ask, “Which of my stored queries match this?” Elasticsearch replies with the list of matching queries. The queries are permanent, and the incoming document is the temporary visitor.
The name comes from the coffee percolator, where hot water passes through a basket of coffee grounds. Here, a document passes through a large collection of stored queries, and the ones that “catch” it are the matches.
Think of a mailroom in a big company. Each employee has left a note at the desk saying what kind of mail they want to hear about: “anything from the tax office”, “parcels from the lab supplier”, “letters mentioning the merger”. When a new letter arrives, the clerk does not go to each employee and ask. The clerk reads the letter once and checks it against the pile of notes to see whose notes it fits. The notes are stored queries. The letter is the incoming document. The clerk is the percolator.
The percolator is an Elasticsearch feature that lets you store queries in an index, using a special field type, and then run a document against them to find out which stored queries would match that document. It is often called reverse search.
What the Percolator Is Not
The percolator does not watch your data by itself. It does not send emails or push alerts. It only answers one question when you ask it: “Which stored queries match this document?” Your own application must ask that question for each new document and then act on the answer. The percolator is also not a machine-learning classifier that guesses. It matches exactly according to the rules in the stored queries. And it is not meant for everyday searching, where ordinary queries against stored documents are the right tool.
2Why We Need Reverse Search
To understand why the percolator exists, look at what goes wrong when people try to solve matching problems with normal search.
The Two Naive Solutions
The first naive solution is to run every saved search for every new document. If you have two million saved searches and five thousand new documents an hour, that is ten billion query runs an hour. Even if each run is tiny, the total is enormous, and almost all of the runs will find nothing.
The second naive solution is polling, which means running each saved search on a schedule, say every ten minutes, and checking for anything new. Polling wastes effort, because most runs find no change. It also adds delay, because a person may wait up to ten minutes for an alert. Shorten the schedule to reduce delay and the cost climbs even higher.
Too Many Queries
Running every saved search against every new document multiplies two big numbers, and the result overwhelms even large clusters.
Late Alerts
Polling on a schedule means an alert may arrive minutes after the matching item appeared, and popular items may be gone by then.
Empty Runs
Most polled searches find nothing new. The cluster does real work to learn that nothing happened.
Custom Matching Code
Writing your own matching engine in application code means rebuilding text analysis, ranges and geography rules that Elasticsearch already knows.
Different Rules in Two Places
If saved searches are matched by custom code while normal search uses Elasticsearch, the two can disagree about what “matches” means.
Cost That Scales Badly
Doubling users and doubling documents quadruples the naive work. The problem gets worse faster than the business grows.
Suppose a newspaper wants to tell readers when a story about their favorite topic appears. One way is to have an assistant call every reader every ten minutes and ask, “Has a story about your topic appeared?” That wastes thousands of phone calls. The smarter way is for the editor to read each new story once, check a list of reader interests and phone only the readers whose interests match. The percolator is that smarter editor.
Why Not Build It Yourself?
A team could write its own matcher, but real queries are complicated. They involve words that must be broken into pieces and reduced to their roots, numbers within ranges, locations within distances, combinations with “and” and “or”, and exclusions. Elasticsearch already implements all this for ordinary search. The percolator reuses the same query language and the same text analysis, so a saved query means exactly the same thing whether it is used in search or in reverse search.
3Core Concepts You Need First
A handful of words appear in every percolator discussion. Learn them here and the later chapters will feel easy.
Stored Query
A query saved inside an index instead of being run once. It is the “note” that describes what someone wants to be told about.
Percolator Field
A special field type that holds a stored query. Its name is chosen by you, and it lives in an index next to any other fields you want to keep with the query.
Percolate Query
The query you run to find matching stored queries. You give it a document, and it returns the stored queries that match.
Candidate Document
The incoming document you are testing. It is not stored. It is only passed through the stored queries.
Mapping
The description of an index’s fields and their types. The percolator index needs a mapping for the fields used in stored queries and in candidate documents.
Query Extraction
At the time a query is stored, Elasticsearch pulls out key terms from it and saves them, so it can later find likely matches quickly.
Candidate Selection
A fast first step that narrows millions of stored queries to the few that could possibly match the incoming document.
Verification
A second, exact step in which each remaining candidate query is really run against the document to confirm the match.
Metadata Filter
A normal filter on extra fields stored beside the query, such as the subscriber’s region, used to skip queries that cannot apply.
Analyzer
The text-processing recipe that breaks text into searchable terms. The same analyzer is used for stored queries and incoming documents, so they line up.
The Chain of Ideas
Here is how these words fit in one sentence. You store queries in a percolator field, in an index with a proper mapping. At storage time, Elasticsearch performs query extraction. Later, your application sends a candidate document inside a percolate query. Elasticsearch does candidate selection to narrow the field, then verification to confirm matches, and returns the matching stored queries. Read the chain twice, and the rest of the guide will feel familiar.
Consider an airport security desk with a list of watch notes. A quick glance at a passenger’s name and destination rules out almost every note. Only a few notes even come close, so the officer reads those few in full to confirm. The quick glance is candidate selection, and reading the few notes in full is verification. Without the quick glance, the officer would have to read thousands of notes for every passenger, and the line would stretch out the door.
Stored queries use the same query language as ordinary search. If you can write a query that finds documents, you can store it as a percolator query. There is no second language to learn.
4Forward Search Versus Reverse Search
Putting the two directions side by side makes the percolator’s role very clear.
flowchart LR
subgraph FWD["Forward search: ordinary Elasticsearch"]
Q1["One query arrives"] --> IDX1["Many stored documents"]
IDX1 --> R1["Matching documents returned"]
end
subgraph REV["Reverse search: the percolator"]
D2["One document arrives"] --> IDX2["Many stored queries"]
IDX2 --> R2["Matching queries returned"]
end
| Question | Forward Search | Reverse Search With the Percolator |
|---|---|---|
| What is stored? | Documents | Queries |
| What arrives at run time? | A query | A document |
| What comes back? | Matching documents | Matching stored queries |
| Typical question | Which flats match my search? | Whose saved searches match this new flat? |
| Typical size of the stored side | Millions of documents | Thousands to millions of queries |
| Typical use | Finding, browsing and analyzing | Alerting, tagging and routing |
Pull and Push
Another way to see the difference is through the words pull and push. Ordinary search is pull: a person goes to the search box and pulls the results they want. The percolator supports push: when something new arrives, it tells you who should be pushed a message about it. Most alerting systems are push systems, which is why the percolator fits them so well.
Visiting a library and asking the librarian to find books about gardening is a pull. Signing up with the library to be phoned whenever a new gardening book arrives is a push. Both need a catalog of books and a list of interests. The first uses the catalog as the stored side. The second uses the list of interests as the stored side, and that is what the percolator holds.
The push happens in your own application, not inside Elasticsearch. The percolator answers “who matches?” and your code decides how to notify them. Teams sometimes expect Elasticsearch to send the notifications by itself, and they are surprised when nothing happens.
5Architecture and Components
Let us look at the parts that work together in a percolator setup, from the people who save searches to the people who receive alerts.
The Parts of the System
API Gateway and Load Balancer
The gateway is the single entrance for requests from web and mobile apps, handling login checks and rate limits. The load balancer spreads requests across the application servers so that none is overloaded.
Subscription Service
Your code that lets people create, edit and delete saved searches. It checks each request and writes the resulting stored query into the percolator index.
Matching Service
Your code that receives each new document and asks Elasticsearch which stored queries match. It then hands the answer to the notification side.
Percolator Index
An index with a percolator field for stored queries, plus fields that describe the queries, such as the owner, region and status.
Document Index
The ordinary index that holds the real documents, such as listings. It is separate from the percolator index and serves normal search.
Queue and Notification Service
A message queue holds the matches. A notification service sends emails, push messages or webhooks to the people whose queries matched.
flowchart TD
U["People on web and mobile apps"] --> G["API Gateway"]
G --> LB["Load Balancer"]
LB --> SUB["Subscription Service: save, edit, delete searches"]
SUB --> PIDX["Elasticsearch percolator index: stored queries"]
SRC["New documents from publishers or data pipeline"] --> ING["Ingestion Service"]
ING --> DIDX["Elasticsearch document index: real documents"]
ING --> MATCH["Matching Service"]
MATCH --> PIDX
PIDX --> MATCH
MATCH --> Q["Message queue"]
Q --> NOTIF["Notification Service"]
NOTIF --> U
MON["Monitoring: latency, match counts, errors"] --> MATCH
MON --> PIDX
Reading the Diagram
Look at the top-left first. People use web and mobile apps, and their requests pass through the gateway and load balancer to the subscription service. This service writes each saved search into the percolator index as a stored query. That is the “left side” of the system, which is quiet and changes only when people edit their searches.
Now look at the right side. New documents come from publishers or a data pipeline. The ingestion service stores each one in the ordinary document index, and also passes it to the matching service. The matching service asks the percolator index which stored queries match, receives a list of matches, and places them on a message queue. The notification service picks them up and sends the alerts to the right people. Monitoring watches the matching service and the percolator index, since they carry the real-time part of the system.
The message queue is not part of the percolator, but it is a very wise addition. It separates finding the matches from sending the messages, so a slow email provider cannot slow down matching, and a burst of matches cannot overwhelm the notification service.
6Internal Working: Life of One Percolate Request
Follow one new document from the moment it arrives to the moment the list of matching queries comes back.
sequenceDiagram
participant PUB as Publisher
participant MS as Matching Service
participant CN as Coordinating node
participant SH as Percolator index shards
participant ML as Notification queue
PUB->>MS: New document arrives
MS->>CN: Percolate query with the document
CN->>SH: Send the request to every shard
SH->>SH: Build a tiny in-memory index of the document
SH->>SH: Candidate selection using extracted terms
SH->>SH: Verify each candidate query against the document
SH-->>CN: Matching stored queries
CN-->>MS: Merged list of matches
MS->>ML: Queue one alert per match
A document arrives
A publisher posts a new flat, article or event. Your matching service receives it and prepares a percolate query that carries the document inside it.
The request reaches the cluster
Elasticsearch accepts the request on a coordinating node. Since stored queries are spread across shards, the coordinating node forwards the request to every shard of the percolator index.
Each shard builds a tiny index
On each shard, the incoming document is analyzed with the analyzers from the mapping and loaded into a very small in-memory index. This index holds only this one document, and it disappears when the work ends.
Candidates are selected
The shard looks at the terms in the document and quickly finds stored queries whose key terms could possibly be satisfied. Most stored queries are ruled out here, without being run at all.
Candidates are verified
For each remaining candidate, the shard runs the real stored query against the tiny in-memory index. If the query finds the document, it is a true match.
Matches return as ordinary hits
Each matching stored query comes back as a normal search hit, along with the extra fields stored next to it, such as the owner’s identifier. The coordinating node merges the hits from all shards.
Your application acts
The matching service reads the list, looks at the owner identifiers, and places an alert for each one on the queue. Elasticsearch’s part is finished.
Why the Tiny Index Is So Clever
The trick of building a small in-memory index for the document lets Elasticsearch reuse its entire ordinary search machinery. A stored query does not know or care that the document is temporary. It runs against the tiny index exactly as it would against a large one. This is why any query that works in normal search can, with a few exceptions, work as a stored query too.
Imagine a teacher marking exam papers with a marking guide made of many separate questions. Instead of inventing a new marking system for every student, the teacher puts the student’s answer sheet on the same desk, in the same format as all the others, and applies the same marking guide. The tiny in-memory index is that desk: it makes the temporary document look like any other document so that the standard guide can be used.
7Storing Queries: The Percolator Field and Mapping
Before any document can be percolated, the queries must be stored in an index that is prepared for them. This chapter explains the preparation.
The Percolator Field Type
Every field in an Elasticsearch index has a type, such as text, number, date or location. The percolator adds one more type, also called percolator. A field of this type holds a whole query. You choose its name, for example “query”, and then each document in the index carries one stored query in that field. In effect, the percolator index is a table of saved searches, where each row is one saved query with some extra columns.
The Index Needs Two Kinds of Fields
The percolator index must contain the percolator field, and it must also describe every field that the stored queries and the candidate documents will use. Suppose stored queries talk about “title”, “price”, “city” and “bedrooms”. Then the index mapping must define those four fields with the correct types, even though the index does not store real listings. The mapping is the shared vocabulary that lets stored queries and incoming documents understand each other.
Think of a form for saved requests at a library. The form has a box for the request itself, which is like the percolator field. It also needs the same categories that books are filed under, such as author, subject and year, so the request can refer to them. If the request mentions “subject” but the library’s filing system has no such category, nobody can tell what the request means.
Extra Fields Beside the Query
You can add ordinary fields next to the percolator field. They describe the stored query and are very useful. Typical examples are the identifier of the person who owns the saved search, the region it applies to, whether it is active, when it was created and the language of the alerts. These extra fields are normal, searchable fields, so you can filter and sort by them.
Query Field
The percolator field that holds the stored query. One per stored query document.
Owner Identifier
Tells your application whom to notify when this query matches.
Region or Category
Lets you filter out queries that cannot apply to the incoming document, before any matching is done.
Status and Timestamps
Lets you disable a saved search without deleting it, and lets you find old or unused ones later.
Fields Must Exist First
The fields used inside a stored query must already be defined in the mapping when the query is stored. Elasticsearch checks the query at that moment, and it will reject a query that refers to unknown fields. This is helpful, because it catches typing mistakes early, but it also means that adding a new searchable field requires updating the mapping first, before anyone can save a query that uses it.
Stored queries are analyzed and prepared at the moment they are saved, using the mapping as it is at that time. If you later change how a field is analyzed, existing stored queries do not update themselves. You will need to save them again, which means re-indexing the percolator index. Plan your text analysis carefully before you store millions of queries.
Keep the percolator index separate from your ordinary document index. The two have different sizes, different traffic and different tuning needs, and a separate index lets you scale, back up and rebuild each one on its own.
8Candidate Selection: How the Percolator Stays Fast
The secret to handling millions of stored queries is that the percolator almost never runs most of them. This chapter explains how.
The Problem It Solves
Imagine one million stored queries and one incoming document. If Elasticsearch had to run every query against the document, it would perform one million query runs for a single document. Even at a tiny cost each, this adds up to a large delay, and it would be hopeless at thousands of documents per minute. Yet in almost every real system, only a handful of the stored queries could possibly match any given document.
Step One: Extract Terms When the Query Is Stored
When a query is saved, Elasticsearch studies it. It looks for the terms that must be present in a document for the query to have any chance of matching. For a simple query that requires the word “garden”, the key term is “garden”. For a query that requires both “leeds” and “balcony”, Elasticsearch picks terms that a matching document is certain to contain. It stores these extracted terms in hidden fields next to the query.
Step Two: Look Up Candidates Fast
When a document arrives, Elasticsearch collects the terms that appear in it and asks one fast question of the hidden fields: “Which stored queries have key terms that this document actually contains?” This uses the same fast lookup structure that ordinary search uses, so it is quick even with millions of entries. Only the queries that pass this test become candidates.
Step Three: Verify the Candidates
The candidate list is a shortlist, not the final answer. A query might have the right key term but fail on another condition, such as a price range. So each candidate is run for real against the tiny in-memory index of the document. Only those that truly match are returned.
flowchart TD
S["Stored queries: extracted key terms saved at storage time"] --> C1
D["Incoming document"] --> T["Collect the document's terms"]
T --> C1["Fast lookup: which stored queries share terms with the document"]
C1 --> CAND["Shortlist of candidate queries"]
CAND --> V["Run each candidate query against the in-memory copy of the document"]
V --> M["Confirmed matches"]
V --> X["Rejected candidates"]
Think of a lost-property office with a huge wall of tickets, each describing what a person lost. A worker holds up a found umbrella. Instead of reading every ticket, the worker scans the wall for tickets that mention “umbrella”, which is very quick. That gives twenty tickets. Then the worker reads only those twenty in full to check the color, size and place, and finds two real matches. The quick scan is candidate selection, and reading the twenty is verification.
When Selection Cannot Help
Some queries cannot be narrowed by key terms. A query that matches everything has no term to look up, so it is always a candidate and must always be verified. Certain other shapes, such as some negations or unusual combinations, also give little to extract. A percolator index full of such queries loses the benefit of candidate selection, and performance falls toward the naive “run everything” case.
Specific Word Queries
Queries that require particular words give sharp key terms. Most candidates are ruled out immediately.
Combinations With “And”
Queries where several conditions must all hold give Elasticsearch a choice of terms and narrow the shortlist well.
Match-Everything Queries
Nothing to look up. Always verified against every document.
Very Broad Queries
Queries on extremely common words become candidates for almost every document, so the shortlist stays long.
Encourage, or require, users to make saved searches specific. A saved search such as “any flat anywhere” will be a candidate for every single document and will slow every percolate request. A saved search such as “two bedrooms, Leeds, under 900” is a sharp query that is easy to rule out for most documents.
9Percolating Documents: One, Many or Already Stored
There are several ways to hand documents to the percolator. Each fits a different situation.
One Document Inline
The simplest way is to include the document inside the percolate query. You name the percolator field and give the document’s contents. Elasticsearch builds the tiny in-memory index and returns matching stored queries. This is what a real-time matching service does for a single new document, for example when a new listing is posted.
Many Documents Together
You can also give a list of documents in the same request. Elasticsearch then builds a small in-memory index holding all of them and matches each stored query against the group. In the response, each hit reports which of your documents matched, using a position number called a slot. Percolating documents in batches is usually much more efficient than one at a time, because the fixed costs of the request are shared. It is a natural fit for systems that receive documents in bursts.
A postal worker sorting letters could take each letter to the mailbox wall, one by one, walking back and forth. Or the worker could carry a stack of letters to the wall once, and sort them all in one trip. The stack takes some care to keep track of, since you need to remember which letter goes where. The slot number is the way the percolator remembers which letter matched which box.
An Already Stored Document
If the document already lives in an ordinary index, you do not need to send its contents again. You can point the percolate query at the index and the document’s identifier, and Elasticsearch fetches the document and runs it against the stored queries. This helps when you index first and percolate afterward, or when you want to re-check old documents against newly created queries.
| Way To Percolate | You Provide | Best For | Watch For |
|---|---|---|---|
| One inline document | The document’s contents | Real-time matching of each new item as it arrives | Many small requests add up in overhead |
| Several inline documents | A list of documents | Bursts, batches and backfills | Very large batches use more memory on each shard |
| An existing stored document | An index name and identifier | Re-checking stored documents against new queries | The document must exist and be readable when you ask |
What Comes Back
The response looks like an ordinary search response. Each hit is a stored query, complete with the extra fields stored beside it, such as the owner identifier and region. When you percolated several documents, each hit also says which documents matched. The score on each hit is not a measure of how good the document is. It reflects how the stored query was matched, and most systems ignore it and simply treat the list of hits as a list of matches.
Choose the batch size by testing. A batch of a few dozen documents is often a good starting point. Too small wastes overhead, and too large can make each request slow and memory-hungry. Measure with your real queries and documents.
10Filtering, Combining and Highlighting
The percolate query is a normal query, which means it can be combined with other queries. This makes real systems much faster and much more useful.
Filter Before You Percolate
Stored queries come with extra fields such as region, category and status. You can combine the percolate query with ordinary filters on those fields. For example, “only stored queries that are active and belong to the Leeds region.” Elasticsearch applies these cheap filters first, so the expensive matching runs on far fewer stored queries. In systems with regions, languages or customers, this single habit can reduce the work by an order of magnitude.
A librarian with a huge box of reader requests receives a new book about local history in Leeds. Instead of checking every request, the librarian first takes out only the requests from people living in Leeds who have not paused their membership. The box is now much smaller, and checking each request is quick. Filters by extra fields are that first sorting step.
Combining Several Percolate Queries
A single request can hold more than one percolate query, joined with “and”, “or” or “not” logic. A common use is to percolate several different kinds of documents at once, or to run two different percolator fields against the same document. Each part can be given a name, and the response reports which named part matched, which helps you tell the reasons for a match apart.
Highlighting the Reason
Sometimes it is useful to know not just that a stored query matched, but why. The percolator supports highlighting, which marks the exact words in the incoming document that caused the match. For an alert email, this lets you show “Your search for ‘garden’ matched this listing, which says ‘a private garden and terrace’.” It is a small feature that makes alerts feel far more intelligent and trustworthy.
Metadata Filters
Filter stored queries by region, status or tenant to shrink the work before matching begins.
Boolean Combinations
Join percolate queries with other conditions to express more complex needs in a single request.
Named Parts
Give each percolate query a name so that the response tells you which one matched.
Highlighting
Show which words in the document caused each match, for clearer alerts and easier debugging.
Sorting and Paging
Order matches by a field such as creation date, and page through very large result lists.
Field Selection
Return only the fields you need, such as the owner identifier, to keep responses small and fast.
A match that is popular may return thousands of stored queries for a single document, for example when a very attractive listing matches many saved searches. Ask for only the fields you need, and page through results, or the response itself can become a performance problem.
11What Kinds of Queries Can Be Stored?
Almost every query type works, but a few do not. Knowing the limits early avoids surprises.
The Broad Rule
A stored query can use the normal query language: words and phrases, exact values, numeric and date ranges, geographic distances, combinations with “and”, “or” and “not”, and many more. The core rule is that the query must be answerable using only the information inside the incoming document and the mapping. A query that needs to look at some other document, or at the current clock, cannot be prepared ahead of time.
| Query Kind | Works as a Stored Query? | Why |
|---|---|---|
| Full-text word and phrase queries | Yes | They depend only on the document’s text and the analyzers |
| Exact-value and range queries | Yes | They compare the document’s fields with fixed values |
| Geographic distance queries | Yes | They compare a location in the document with a fixed point |
| Boolean combinations | Yes | They simply combine the queries above |
| Queries that join to other documents | No | They need to look at other documents, but the percolator sees only one temporary document |
| Queries that look up values elsewhere at run time | No | They need a lookup outside the incoming document |
| Ranges based on the current time | Not accepted | The meaning of “now” would be frozen at storage time, not at matching time |
The Trouble With “Now”
People often want alerts such as “listings posted within the last day.” A stored query cannot say “within the last day” using the current time, because the stored query is prepared once, when it is saved, and would keep using that moment forever. The usual solution is to move the time logic into your application. Since the percolator sees each document as it arrives, “just posted” is already true for every document you percolate. If you need age rules, add a field to the document such as “hours since posting” at percolate time, or apply the rule in your own code afterward.
Suppose you write on a note, “Tell me about deliveries arriving in the next two hours,” and pin it to a board. The words “next two hours” make sense on the day you write the note, but the note stays on the board for months. Two hours from when? The note cannot hold a moving clock. A better note says what you truly care about, such as “Tell me about deliveries of flowers,” and the clerk applies the timing when the delivery arrives.
Fields Must Match Between Queries and Documents
A stored query and an incoming document must use the same field names with the same meanings. If a stored query talks about “price” as a number, but the incoming document sends “price” as text like “nine hundred pounds”, the match will fail or be rejected. Agree on one clear document shape, and validate both queries and documents against it.
Because supported and unsupported query types can change between versions, check Elastic’s documentation for your version before designing around a specific type. Test any unusual query shape by actually storing it and percolating a sample document.
12Use Case One: Alerts and Saved Searches
The most common use of the percolator is the alert: “tell me when something new matches what I am looking for.”
The Alert Pipeline
flowchart LR
NEW["New listing posted"] --> ING["Ingestion Service"]
ING --> IDX["Store in the document index"]
ING --> PERC["Percolate against stored searches"]
PERC --> HITS["List of matching saved searches"]
HITS --> DEDUP["Remove duplicates and respect quiet hours"]
DEDUP --> QUEUE["Alert queue"]
QUEUE --> EMAIL["Email"]
QUEUE --> PUSH["Mobile push"]
QUEUE --> HOOK["Webhook to partner system"]
The flow is simple. A new listing arrives and is stored in the ordinary index so that it appears in normal search. At the same moment, it is percolated against all the saved searches. The list of matches is cleaned up, for example by removing duplicates and respecting quiet hours, and placed on a queue. Separate workers turn queue items into emails, phone notifications or messages to partner systems.
A travel-deal website is like a fisherman’s net cast over a stream of arriving deals. Each traveler has left a small hook describing the trip they want. When a deal floats past, it snags on some hooks and slips by others. The percolator decides which hooks catch which deals, and the notification workers pull each snagged deal back to the traveler.
Designing Good Alerts
- Be timely. Percolate as soon as a document is indexed. The value of an alert falls quickly with time, especially for scarce items.
- Be specific. Ask users for enough detail that their queries are precise. Vague queries create noisy alerts and slow matching.
- Avoid duplicates. If a document is edited several times, decide whether each edit should alert again. Keep a record of which alerts were sent.
- Respect limits. Give users a way to control the number and frequency of alerts, and offer a daily digest as an alternative to instant messages.
- Explain the match. Use highlighting to show why the alert was sent, which makes alerts feel useful and not random.
Example: A Job Board
A job board lets candidates save searches such as “data engineer, remote, Python”. Each saved search becomes a stored query with the candidate’s identifier and location preference beside it. When a new job is posted, the job board percolates it and gets back the list of candidates whose searches match. Within seconds, matching candidates receive an email with the job title highlighted against their search words.
Store the same query in your ordinary user interface as a saved search, so that when someone clicks the alert, the page shows the results of the same query. Keeping one source of truth for “what this saved search means” avoids confusing differences between alerts and search pages.
13Use Case Two: Classification and Tagging
The percolator can also sort and label documents using rules that people write, without any machine learning.
Rules as Stored Queries
Imagine a news site that wants to label each incoming article with topics such as “football”, “elections” or “technology”. Editors write rules for each topic: the words and phrases that signal it. Each rule is stored as a query. When an article arrives, the percolator returns which topic rules matched, and the system attaches those topics as tags. Adding a new topic requires only saving a new query. No new code is needed.
A warehouse sorting line has a row of labeled chutes. Each chute has a rule, such as “parcels marked fragile” or “parcels going to the north region”. As each parcel travels down the belt, the sorter checks which rules apply and drops it into the right chutes. The rules are stored queries, and the belt is your stream of documents.
Why Use Rules Instead of Machine Learning?
Machine learning classifiers can be powerful, but they have costs: they need training data, they can be hard to explain, and they can change behavior when retrained. Rules are transparent, quick to change and easy to audit. For classification tasks where the categories are clear and the signals are keywords, or combinations of fields, rules stored in the percolator are often simpler and more trustworthy. Many teams also combine both: rules for the clear cases and a model for the fuzzy ones.
| Approach | Strengths | Weaknesses |
|---|---|---|
| Percolator rules | Transparent, instantly editable, explainable, no training needed | Rules must be written and maintained by people; misses subtle cases |
| Machine learning classifier | Learns subtle patterns from examples | Needs labeled data, harder to explain, changes when retrained |
| Both together | Rules cover clear cases, models help with the rest | More parts to build and maintain |
Typical Tagging Tasks
Topic Labels
Tag articles and videos by topic so they appear on the right pages and in the right newsletters.
Ticket Routing
Match incoming support requests to teams by keywords, product names and customer tier.
Product Categories
Assign products to categories and collections based on their titles, brands and attributes.
Editorial Flags
Mark content that needs review, such as mentions of restricted topics or sensitive words.
Keep a small set of example documents for each rule, and test every new or edited rule against them before it goes live. A rule that quietly matches too much can tag thousands of documents wrongly before anybody notices.
14Use Case Three: Monitoring, Compliance and Routing
The percolator is also a general “rules on a stream” engine. Any time you have many rules and a flow of incoming events, it can help.
Watching Streams of Events
Operations teams handle streams of logs, messages and events. Each team may care about different patterns: a specific error code, a mention of a certain customer, a payment above a limit in a certain country. Instead of building one big program with all rules hard-wired, the team stores each rule as a query. As events flow past, the percolator reports which rules fired, and the system triggers the right action for each.
Compliance and Safety Checks
Regulated industries must check communications and content for banned or sensitive language, or for patterns of concern. Compliance staff maintain a list of rules. Each new message is percolated, and any that match are flagged for review. Because the rules are ordinary stored queries, compliance staff can add or change them quickly when regulations change, without waiting for a software release.
A building’s security desk has a card listing who should be stopped or reported, and why. A guard watches each visitor walk past and checks the card. When the rules change, the desk simply replaces the card. The guard’s routine stays the same. The percolator index is the card, and it can be updated in seconds.
Routing Work to the Right Place
The same idea helps route work. An insurance company might route each incoming claim to a specialist team, based on rules about the type of loss, the region and the amount. A logistics company might route shipment events to the right customer dashboards. Each routing rule is a stored query, and the percolator says which routes apply to each event.
Example: Payment Screening
A payment company keeps rules such as “transfers above a set amount to certain countries” and “several small transfers to the same account within a short window.” Each rule is a stored query. As transactions stream in, the percolator flags the ones that match, and flagged items go to analysts. When new rules are agreed, the compliance team adds them to the percolator index, and they apply to the next transaction.
The percolator judges one document at a time. Rules that need to look at patterns over time, such as “three failed logins in five minutes”, cannot be answered by the percolator alone, because it does not remember earlier documents. Use a stream-processing tool or an alerting feature for time-window logic, and use the percolator for per-document rules.
15The Percolator Versus Other Approaches
Several other tools can seem to solve the same problem. Comparing them shows where the percolator fits best.
| Approach | How It Works | Best For | Main Limit |
|---|---|---|---|
| Percolator | Stores queries and matches each new document against them | Many user-defined rules with a stream of new documents | Judges one document at a time, and you build the notification side |
| Polling saved searches | Runs each saved search on a schedule | A small number of saved searches with relaxed timing | Slow, wasteful and expensive as the number of searches grows |
| Scheduled alerting rules | Runs a query on a timer and checks conditions on the results | Operational alerts on trends, thresholds and time windows | Runs by schedule, so it is not per-document and not suited to millions of user rules |
| Enrichment at ingest | Looks up matching reference records while indexing a document | Adding fields from a reference table by exact value, range or location | Not for full-text rules or arbitrary query logic |
| Custom matching code | Your own program tests rules against each document | Very special rules that queries cannot express | You must rebuild analysis, ranges and geography yourself |
| Machine learning classifier | A trained model labels each document | Fuzzy categories learned from examples | Needs data, and is harder to explain |
Alerting Features Versus the Percolator
Elasticsearch and Kibana also have alerting features. They usually run a search on a schedule and fire an action if the results meet a condition, such as “more than a hundred errors in the last five minutes.” That is perfect for operational monitoring. The percolator is different: it reacts to each new document, and it can hold millions of separate rules belonging to millions of users. Think of alerting rules as a smoke detector that watches a room over time, and the percolator as a doorman who checks each guest against a guest list as they enter.
Enrichment Versus Percolation
An enrich feature at ingest time adds information to a document by looking up matching reference data, for example adding a region name based on a postcode. It answers “what extra data belongs to this document?” The percolator answers “which rules does this document satisfy?” They can work together: enrich the document first so that it has the fields that rules need, then percolate it.
Ask two questions. Do I have many separate rules, perhaps belonging to different users? Do I need to react to each new document? If both answers are yes, the percolator is a strong fit. If you only have a few operational conditions over time windows, scheduled alerting rules may be simpler.
16Text Analysis and the Percolator
Text search depends on how words are broken up and simplified. For the percolator, this process must be identical on both sides.
What Analysis Does
Before text can be searched, Elasticsearch runs it through an analyzer. An analyzer splits text into words, converts them to lower case, and may reduce them to a root form, so that “running”, “runs” and “ran” can be treated as related. It may also remove tiny words such as “the”. This is what lets a search for “garden flat” find a listing that says “Flats with Gardens”.
Both Sides Use the Same Recipe
In the percolator, the stored query and the incoming document are both analyzed using the mapping of the percolator index. Because they share the same recipe, their terms line up. If the recipe changed in the middle, the stored queries would hold terms produced by the old recipe, while new documents would be broken up by the new one. Matches could silently disappear.
Two people agree to meet at a place they both call “the old market”. If one calls it by that name and the other switched to calling it “Market Square”, they may go to different places and never meet. Stored queries and documents must use the same naming rules, or they will miss each other.
Changing Analyzers Later
Extracted terms and prepared queries are created when a query is stored. If you change the analyzer for a field, you must store the queries again so they are prepared with the new recipe. In practice, this means creating a new percolator index with the new mapping, copying the queries into it, and switching your application to the new index. Plan this as a normal migration, with testing and a rollback plan.
Decide Early
Choose analyzers before storing large numbers of queries. Changing them later means a rebuild.
Test With Real Text
Try real queries and real documents. Simple examples hide surprises such as plurals, accents and punctuation.
Keep Versions
Keep the source of every stored query in your own database too, so the percolator index can be rebuilt at any time.
Match the Search Index
Where possible, use the same analysis as your ordinary search index, so saved searches behave as they do on the results page.
A silent mismatch of analysis is one of the hardest percolator bugs to find. Nothing fails. Alerts simply stop arriving for some kinds of words. Keep a small suite of known query and document pairs that must match, and run it after every mapping change.
17Performance and Scalability
The percolator can handle very large collections of stored queries, but only if you understand where the time goes.
Where the Time Goes
Each percolate request has a few parts. First, the request travels to every shard of the percolator index. Second, each shard builds a tiny in-memory index of the incoming document. Third, each shard performs candidate selection over its stored queries. Fourth, each shard verifies the candidates by running them. Finally, the results travel back and are merged. The biggest variable is usually the number of candidates that need verification, and the cost of each one.
Metadata Filters
Filtering stored queries by region, tenant or status before matching is the single most effective speed-up in most systems.
Query Specificity
Specific queries have sharp key terms and are ruled out early. Vague queries become candidates for many documents.
Query Cost
Cheap queries verify fast. Expensive ones, such as wildcards on common prefixes, slow every request in which they are candidates.
Batching
Percolating several documents in one request shares the fixed overhead and raises throughput.
Shard Count
More shards add parallelism, but every shard repeats the work of building the in-memory index, so too many shards waste effort.
Replicas and Hardware
Replicas let more percolate requests run at once. Fast processors matter most, since the work is mainly CPU-bound.
Parallel Work Across Shards
Stored queries are spread across shards, and each shard works on its own share at the same time. If one shard holds a quarter of the stored queries, it does about a quarter of the matching. This is why percolator indices scale well as they grow: adding shards and nodes spreads the stored queries thinner. However, the setup cost per shard, such as building the tiny index, is repeated on every shard. Choose a shard count that keeps each shard busy with a meaningful number of queries and no more.
Imagine a team of clerks each holding a portion of the guest-list cards. When a guest arrives, every clerk checks their own cards at once. That makes the check fast. But each clerk must first take a look at the guest, which takes a few seconds. If you have fifty clerks, each with only ten cards, the looking takes longer than the checking. There is a sweet spot between too few clerks, who are overloaded, and too many, who spend all their time looking.
Throughput Versus Latency
Latency is how long one request takes. Throughput is how many requests or documents can be handled per second. A system that percolates each document individually may have low latency for each but limited throughput. A system that batches documents raises throughput but adds a little waiting time for each document while the batch fills. Choose based on your needs: instant alerts need small batches, and overnight backfills can use large ones.
Test with your real queries. Systems tested with a few hundred neat queries can behave very differently with a million messy user-written ones. One heavy query type that accounts for one percent of stored queries can dominate the time of every request if it is a candidate for most documents.
Example: Filtering to Speed Up Matching
A classifieds site has a million stored queries across twenty countries. Each stored query has a country field. Percolating a new advertisement against all of them takes about 300 milliseconds. The team adds a filter so that only stored queries for the advertisement’s country are considered. That cuts the pool to about fifty thousand, and the time falls to around 25 milliseconds. Nothing else changed.
18Data Flow and the Life of a Stored Query
A stored query passes through a series of stages, from the moment a person saves a search to the day it is removed.
flowchart LR
C["Created by a person"] --> V["Validated by your service"]
V --> S["Stored in the percolator index"]
S --> X["Terms extracted and indexed"]
X --> A["Active: matched against new documents"]
A --> E["Edited: replaced by a new version"]
E --> V
A --> P["Paused: status set to inactive"]
P --> A
A --> D["Deleted or expired"]
P --> D
Created
A person fills in a form or clicks “save this search”. Your service turns that into a query written in the Elasticsearch query language.
Validated
Your service checks that the query is allowed: only supported query types, sensible size, no expensive patterns, and within the user’s limits.
Stored
The query is written into the percolator index, with extra fields such as owner, region and status.
Extracted
Elasticsearch analyzes the query and saves its key terms for candidate selection. This happens automatically at storage time.
Active
The query takes part in every percolate request that its filters allow, and produces matches.
Paused or Deleted
People may pause alerts or remove them. Old and unused queries should expire, so the index does not grow forever.
Making Changes Safely
Stored queries are ordinary documents, so editing one means replacing it with a new version. There is a brief moment during which the old version is still visible, until the change is refreshed and searchable. Elasticsearch makes new documents visible on a short schedule, usually about a second. For alerts, this delay is fine. But be aware that a query saved just now may not match a document percolated in the same instant.
Think of a notice board at a community center. When someone pins a new note, it takes a moment for the clerk to update the master list that everyone consults. During that short moment, the clerk still works from the old list. A note that was pinned only a second ago may be missed by a letter that arrives right now.
Expiry and Cleanup
Saved searches that nobody uses waste space and processing time. Give each stored query a creation date and a last-used date, and remove those that have been quiet for a long time, after warning the owner. Some systems also let users set an end date, such as “alert me until I move house.” A good cleanup habit keeps the percolator index small and matching fast.
19A Worked Example, Start to Finish
A short story with numbers makes the ideas concrete. Here is an invented example of a job board and its job alerts.
The Setting
A job board has two million saved searches from candidates. About six thousand new jobs are posted each hour, which is under two per second. The team wants alerts sent within a few seconds of a job being posted. All figures in this example are invented to illustrate the reasoning and are not from a real company.
The Design
| Question | Decision | Reason |
|---|---|---|
| Where do saved searches live? | A separate percolator index with fields for owner, country, status and the stored query | Separate scaling and tuning from the ordinary job index |
| How do we narrow the pool? | Filter by the job’s country and by active status before matching | Cuts two million queries to a few hundred thousand |
| How do we percolate? | Small batches of a few jobs, collected for up to one second | Shares overhead while keeping alerts near real time |
| What do we return? | Only the owner identifier and alert preferences, plus highlights | Small responses and clearer alerts |
| How do we notify? | Matches go to a queue, and workers send emails or push messages | Slow email services cannot slow down matching |
What Happens to One Job
A company posts a job called “Senior Data Engineer, Remote, Python and Spark” in the United Kingdom. The ingestion service stores it in the job index and sends it to the matching service. The percolate request filters to active saved searches for the United Kingdom, about 240,000 of them. Candidate selection looks at the words in the job and shortlists about 300 stored queries that share key terms. Verification confirms that 42 of them truly match, including candidates who asked for “data engineer” and “python”, and one who asked for “spark remote”. The response carries the 42 owner identifiers and highlights of the matching words.
The matching service removes six candidates who already received an alert for a near-identical job an hour earlier, then queues 36 alerts. Workers send the emails, each one showing the highlighted words that matched. Total time from posting to queue is under a hundred milliseconds, and emails leave within a few seconds.
What Went Well, and What Was Tuned
In the first test, without the country filter, the same request took several times longer, because more than a million stored queries were checked in candidate selection. Adding the filter fixed most of the delay. A second problem was a small number of very vague saved searches such as “any job”, which were candidates for every posting and produced huge, unwanted alert volumes. The team added a rule requiring at least two conditions in every saved search, and the noise stopped.
Two simple habits did most of the work: filtering stored queries by a metadata field, and requiring saved searches to be specific. The percolator itself did what it always does. The design around it decided whether the system was fast and pleasant or slow and noisy.
20Designing the Percolator Index
Good index design makes everything else easier. This chapter covers the main decisions.
One Index or Many
You can keep all stored queries in one index or split them into several. A single index is simplest, and works well when all queries use the same mapping and similar traffic. Separate indices help when different kinds of queries have different mappings, when tenants need strong isolation, or when different parts of the business have different performance needs. Adding an alias that points to several indices lets one percolate request cover them all when needed.
Choosing the Number of Shards
Shards spread stored queries over the cluster. Too few shards means each does too much work and only a few nodes are used. Too many shards means each does little and the fixed setup cost of every request grows. A useful method is to estimate the number of stored queries and the time to match a typical document, then test with two or three shard counts, and pick the smallest one that meets your latency target with room to spare.
Routing by Tenant or Region
Routing lets you decide which shard stores each query, using a value such as a customer identifier or region. When you percolate with the same routing value, Elasticsearch asks only the shards that hold that value, not all of them. For systems where every request concerns one tenant or one region, routing cuts the work sharply, and it also improves isolation between tenants.
A large hotel keeps guest-list cards in separate drawers for each wing. When a visitor asks for someone in the east wing, the receptionist opens only the east-wing drawer, not the whole cabinet. Routing works the same way. The drawer is chosen by a value that every card carries, and most requests know which drawer they need.
Separate From Documents
Keep stored queries apart from ordinary documents, so each can be scaled, backed up and rebuilt on its own.
Include Filter Fields
Add fields for the things you filter by, such as country, tenant, language and status, so filtering is cheap.
Use Routing Where It Fits
Route by tenant or region when most requests concern only one, to reduce the shards asked.
Plan for Rebuilds
Keep the original queries in your own database, and use an alias so you can switch to a rebuilt index without downtime.
Use an alias in your application code instead of a fixed index name. When you need to rebuild the percolator index for a new analyzer or a bigger shard count, you can build the new index in the background and switch the alias in one step.
21Security
The percolator often stores queries written by outsiders, such as users of your website. Treat those queries with care.
User-Written Queries Are Untrusted Input
If people can save their own searches, they can accidentally, or deliberately, write queries that are very expensive. A query with a wildcard on a very common prefix, a huge list of terms, or a complicated regular expression may take a long time to verify, and it would run for every document in which it is a candidate. One bad query can slow down everyone’s alerts.
The defense is to never store raw queries from users. Instead, let users fill in a structured form, such as keywords, price range and location. Your service builds the actual query from those fields, using only safe query types and limits you have chosen. This is the same rule that protects databases from injection: build queries from validated parts, not from raw user text.
A restaurant lets guests choose dishes from a menu. It does not let guests walk into the kitchen and write their own cooking instructions on the wall. The menu is the structured form, and the kitchen instructions are the stored query. The restaurant stays in control of what actually gets cooked.
Limits Per User
- Number of saved searches. Cap how many each user can have, so one account cannot flood the index.
- Size and shape. Limit the number of conditions and the number of terms in each query.
- Allowed query types. Permit only the types you have tested and know to be cheap.
- Creation rate. Limit how often a user or an automated script can create or change saved searches.
Keep Tenants Apart
In a system with many customers, one customer’s stored queries and alerts must never leak to another. Store a tenant identifier in every stored query, and always apply a filter on it in every percolate request. Consider using Elasticsearch’s security features, such as roles that limit which documents a user or service can see, so the rule is enforced by the cluster and not only by your code.
Protect the Content
Incoming documents may contain private data, such as messages or personal details. Percolate requests carry the whole document, so encrypt connections with TLS, restrict who can call the matching service, and avoid logging full documents in application logs. Alerts sent to users should show only the parts of a document that they are allowed to see.
A stored query can reveal something about its owner, such as their job search or their interests. Treat the percolator index as personal data, apply the same access controls and retention rules as you would for any user profile, and delete queries when accounts are closed.
22High Availability, Failure Scenarios and Recovery
A missed alert is a silent failure, which makes reliability especially important for percolator systems.
Reliability Basics
The percolator index is an ordinary Elasticsearch index, so it gets the usual protections: replica shards on other nodes, spread across zones, so a node failure does not lose stored queries or stop matching. Keep at least one replica of the percolator index. Since your application also holds the original queries in its own database, the percolator index can always be rebuilt from that source.
| Scenario | What You See | Likely Cause | What To Do |
|---|---|---|---|
| Matching service down | Documents arrive, but no alerts are queued | Service crash or deployment fault | Restart it, and replay documents from the ingestion queue for the missed period |
| Percolate requests time out | Slow or failing matching | Heavy queries, big batches or overloaded nodes | Add filters, reduce batch size, find the expensive queries and add capacity |
| Alerts stop for some words | Users report missing alerts | Analyzer change or mapping mismatch | Run the known-match test suite and rebuild the index if needed |
| Node lost | Brief errors, then recovery | Hardware or network failure | Replicas take over, check that cluster health returns to green |
| Alert flood | Huge number of matches for one document | Vague saved searches or a very popular item | Cap matches per document, page through results and rate-limit sends |
| Duplicate alerts | Same user gets repeated alerts | Retries, edits or replays | Record which alerts were sent and make sending idempotent |
Never Lose a Document
The percolator only matches the documents you give it. If your matching service is down, documents that arrive in that time are not matched, unless you have a way to catch up. The safe design puts new documents onto a durable queue first, and lets the matching service consume from it. If the service stops, documents wait in the queue and are matched when it returns. Alerts are late, but none is lost.
A mailroom clerk who goes on a break leaves a tray for arriving letters. When the clerk returns, the letters in the tray are sorted, and nobody’s mail is lost. Without a tray, letters arriving during the break would slide off the desk. The durable queue is the tray.
Record the last document successfully matched. After an outage, you know exactly where to resume. You can even percolate stored documents from the ordinary index for the missed time range, since the percolate query can read documents that are already stored.
23Monitoring, Logging and Metrics
Because failures are quiet, monitoring must actively look for the absence of alerts as well as errors.
The Numbers That Matter
Percolate Latency
Time per request, tracked at the average and at the slowest one or five percent. Slow tails hide trouble that averages miss.
Documents Per Second
How many documents are percolated, and how many are waiting in the queue. Growing queues warn of overload.
Matches Per Document
The average and maximum number of matches. Sudden jumps suggest vague queries or a popular item. Sudden drops suggest a break.
Number of Stored Queries
Total and per tenant or region. Growth here drives cost and latency, and unusual growth can mean abuse.
Errors and Timeouts
Failed or rejected percolate requests, and the reasons, such as memory pressure or overloaded thread pools.
Time From Document to Alert
The end-to-end delay a user experiences, from posting to notification, which is the number that really matters.
Watching for Silence
A percolator system can fail by simply matching nothing. Set an alert if the number of matches per hour drops far below the normal level for that time of day. Also keep a small “canary” query and a canary document that must always match. Percolate the canary every few minutes. If it stops matching, you know that something in the chain has broken, even if there are no error messages.
Finding Expensive Queries
Elasticsearch offers slow logs, which record requests that take longer than a limit you set. Use them on the percolator index to find slow percolate requests. If a few stored queries are the cause, you can identify them by looking at which queries are candidates most often and are slow to verify, then work with their owners to make them more specific.
Do not rely only on error counts. A system with zero errors and zero alerts is not healthy. Always monitor the positive signal, the number of matches and alerts delivered, along with the negative ones.
24Advantages, Disadvantages and Trade-offs
The percolator is a strong tool for a specific job. A fair look at its strengths and limits helps you decide when to use it.
Advantages
- Solves the “which rules match this document?” problem directly, without running every rule
- Reuses the full Elasticsearch query language and text analysis
- Scales to very large numbers of stored queries with candidate selection and filters
- Rules are data, so they can be added or changed instantly without a software release
- Supports batching, highlighting and combination with ordinary filters
- Ships as part of Elasticsearch, so there is no separate matching engine to run
- Transparent and explainable, since each match comes from a readable query
Disadvantages
- Does not notify anyone by itself, so you must build the calling and delivery parts
- Judges one batch of documents at a time and has no memory of earlier documents
- Some query types are not supported, including those that depend on the current time
- Vague or expensive stored queries can slow every request
- Changing analysis requires rebuilding the stored queries
- Fields must be mapped up front, and documents must fit the same shape
- Adds another index to size, secure and operate
Key Trade-offs to Decide
Freedom versus safety. Letting users write any query gives flexibility but exposes you to costly queries. A structured form limits freedom and protects performance. Most public systems choose the form.
Speed of alerts versus efficiency. Percolating each document immediately gives the fastest alerts, but small requests carry more overhead. Batching for a second or two improves efficiency with almost no visible delay.
One big index versus several. One index is simple to run. Several indices give isolation and independent tuning at the cost of more to manage.
Rules versus learning. Stored rules are clear and controllable but need human upkeep. Machine learning handles fuzzy cases but is harder to explain. Many systems use both.
25Design Patterns and Anti-Patterns
Teams that have run percolators in production repeat the same good habits, and the same avoidable mistakes.
Patterns That Work
Filter, Then Percolate
Always narrow stored queries with cheap metadata filters, such as region or tenant, before the matching begins.
Queue Between Match and Send
Place matches on a durable queue so that slow notification providers never block the matching step.
Structured Saved Searches
Let users fill in a form, and build the stored query in your own code with safe query types and limits.
Source of Truth Elsewhere
Keep the original definition of every saved search in your own database, and treat the percolator index as a rebuildable copy.
Micro-Batching
Collect documents for a second or so and percolate them together, balancing speed and efficiency.
Canary Checks
Run a known query and known document through the system regularly, and alert if the match ever fails.
Anti-Patterns to Avoid
What people do
They store raw, user-written queries with no checks.
Why it hurts
One expensive or malicious query can slow every percolate request, and users can create huge numbers of them. Costs and delays rise, and other users suffer.
Better approach
Use structured forms, build queries in your own code, limit query types and size, and cap the number of saved searches per user.
What people do
They percolate against every stored query with no filters at all.
Why it hurts
Candidate selection has to consider the full set of stored queries, and many irrelevant candidates need verification. Latency grows with the size of the index.
Better approach
Add fields such as country, tenant and status to every stored query, and filter on them in every request.
What people do
They expect Elasticsearch to notify users automatically.
Why it hurts
Nothing calls the percolator by itself. The project launches, and no alerts are ever sent because no one wrote the matching and delivery service.
Better approach
Design the whole pipeline: ingestion, percolate call, queue and delivery. Treat the percolator as one component in it.
What people do
They put stored queries and real documents in the same index and never separate them.
Why it hurts
The two have different sizes, traffic and mappings. Tuning one harms the other, and rebuilding the percolator side means touching the whole index.
Better approach
Keep a dedicated percolator index, and use aliases so that you can rebuild and switch without downtime.
What people do
They try to use the percolator for rules that need history, like “five failures within ten minutes”.
Why it hurts
The percolator sees only the documents in the current request. It cannot count earlier events, so the rule never fires as intended.
Better approach
Use a stream processor or scheduled alerting rules for time-window logic, and use the percolator for per-document rules.
26Best Practices and Common Mistakes
A short list of habits that separate smooth percolator projects from painful ones.
Confirm the problem fits
Check that you really have many stored rules and a flow of new documents. If you only have a few conditions over time windows, simpler alerting rules may be enough.
Design the document shape first
Agree on the fields, types and analyzers before storing queries. Changing them later means rebuilding the stored queries.
Add filter fields to every stored query
Region, tenant, language and status let you skip most stored queries before any matching is done.
Validate and limit user input
Use structured forms, allow only safe query types and cap sizes and counts per user.
Separate matching from sending
Put matches on a durable queue so failures and slowness in email or push services do not affect matching.
Test with realistic volume
Load test with your real mix of queries and documents, including the vague and messy ones, and measure the slow tail.
Common Mistakes
- Assuming automatic notifications. Forgetting that your own code must call the percolator and deliver the messages.
- Using time-relative ranges. Trying to store queries with “now” in them and finding that they are refused or behave wrongly.
- Forgetting to map fields. Storing queries that refer to fields the mapping does not define.
- Changing analyzers casually. Altering text analysis without rebuilding the stored queries, causing silent missed matches.
- Ignoring the vague queries. Leaving very broad saved searches in place, which slow every request and flood users with alerts.
- Too many shards. Splitting a modest percolator index into many shards and paying the setup cost of each request many times over.
- No source of truth elsewhere. Having no copy of the saved searches outside the percolator index, so a rebuild is impossible.
Write a short “definition of done” for the matching service: a known query and document must match, a known non-matching pair must not, a batch of documents must give the right slots and a filter must exclude the right queries. Run this checklist after every deployment and every mapping change.
27Day-Two Operations: Living With the Percolator
Setting up a percolator is a few days of work. Running it for years is the rest. These are the routine tasks that keep it healthy.
Keeping the Index Tidy
Saved searches accumulate. People change their minds, leave the service or forget they ever created an alert. Regularly remove stored queries for closed accounts, expired alerts and searches that have not produced a match in a very long time. Tell owners before removing anything they might miss. A tidy index is faster and cheaper.
Updating the Mapping
When you want to support a new searchable field, add it to the mapping first. New fields can usually be added without a rebuild. But changing the type or analyzer of an existing field cannot be done in place. For those changes, create a new percolator index with the new mapping, copy the stored queries into it from your own source of truth, test it side by side and then switch the alias. Keep the old index for a short while as a rollback.
Upgrading Elasticsearch
Read the release notes for the versions you are upgrading through. The percolator has changed across versions, and older systems used a different, separate percolate interface, which has long since been replaced by the current query. Test your stored queries and your known-match checklist on the new version before upgrading production, and watch for any query types that are no longer accepted.
A community notice board needs regular care. Old notes must be taken down, new categories need new sections, and every so often the whole board is replaced with a bigger one. If the board is renovated carelessly, notes fall off and messages are missed. A calm routine of tidying, planning and switching prevents that.
Reviewing the Heaviest Queries
Every month or so, look at which stored queries are slow or are candidates for a very large share of documents. Talk to their owners, or improve the form that created them, so that they are more specific. A short review often shows one or two patterns that account for most of the load, and fixing them can free up a great deal of capacity.
Put recurring chores on a calendar: tidy expired queries, review the heaviest queries, run the known-match checklist and rehearse a rebuild from the source of truth. Tasks that are scheduled tend to get done, while tasks kept in memory tend to wait for the next incident.
28A Simple Troubleshooting Guide
When the percolator seems to misbehave, an ordered set of checks finds the cause faster than guessing.
Is the query stored?
Fetch the stored query by its identifier and confirm that it exists, is active and has the fields you expect, including region and status.
Does the query match the document in plain search?
Index the sample document in a scratch index with the same mapping, and run the stored query against it as an ordinary search. If it does not match there, the query itself is the problem.
Do the filters exclude it?
Check that your metadata filters, such as region and status, are not removing the stored query before matching. This is a very common cause of a missing alert.
Do the fields line up?
Compare the fields in the document with the fields in the mapping. A misspelled field or a value of the wrong type can prevent a match without any error.
Is analysis consistent?
Check whether the analyzer changed since the query was stored. If so, store the query again and test once more.
Is the pipeline delivering?
If matches are found but no alert arrives, look at the queue, the workers and the notification provider. The percolator may be working perfectly while the delivery side is stuck.
Common Symptoms and First Guesses
| Symptom | First Thing To Check |
|---|---|
| A saved search never matches anything | Whether it is too specific, whether its fields are mapped and whether filters exclude it |
| Storing a query fails with an error | Whether all fields exist in the mapping and whether the query type is supported |
| Alerts arrive for some users but not others | Per-user filters, status fields and analysis differences |
| Percolate requests are slow | Missing filters, vague queries as candidates, batch size and shard count |
| Huge number of matches for one document | Very broad stored queries, or a popular item that matches many specific ones |
| Matches stopped after a mapping change | Analyzer or field type change, so stored queries need to be stored again |
Keep a small library of “must match” and “must not match” pairs for real examples that once went wrong. Every bug you fix becomes a permanent test, and after a year, this library is the best protection you have against regressions.
29Real-World Scenarios
The stories below are illustrative composites showing how different kinds of organizations use the percolator. They are not accounts of specific companies.
A Property Portal With Saved Searches
A property site lets people save searches by area, price and number of bedrooms. Every new listing is percolated against active saved searches for its region. Matching users get a message with the listing’s photo and the words that matched. Because filters cut the pool to one region, matching stays fast even on busy mornings.
A News Organization Tagging Stories
A newsroom keeps a library of topic rules written by editors: sports, politics, science and hundreds of narrower topics. Each incoming story is percolated, and matching topics become tags that decide which pages and newsletters carry the story. When a new topic emerges, an editor adds one rule and the next story is tagged immediately.
A Support Team Routing Tickets
A software company receives thousands of support requests. Each team maintains rules about the products, error messages and customer tiers it handles. Incoming tickets are percolated, and the matching rules decide which team receives each ticket. When the company launches a new product, the support lead writes rules for it in minutes.
A Regulated Firm Screening Messages
A financial firm must screen internal messages for restricted terms and phrases. The compliance team maintains the rules as stored queries. Every message is percolated, and matching messages are flagged with highlights for human review. When regulations change, updated rules are stored the same day, without a software release.
A Marketplace Watching for Prices
A marketplace lets buyers set alerts such as “vintage camera, under a certain price, within fifty kilometers.” Each new listing is percolated against alerts in its region, and buyers are notified the moment a matching item appears. The system limits each buyer to a set number of alerts so that the index stays healthy.
30Common Myths and Misunderstandings
Half-true ideas about the percolator cause wasted effort and bad surprises. Here are the ones that appear most often.
“The percolator sends notifications.”
It only tells you which stored queries match. Your own code must call it and deliver the messages.
“It watches my index automatically.”
It does not monitor anything. It answers a request that you send for each document or batch.
“It runs every stored query against every document.”
Candidate selection rules out most stored queries first, so only a small shortlist is really run.
“Any query can be stored.”
Queries that join to other documents, need outside lookups or depend on the current time are not supported.
“It is a machine-learning feature.”
It is exact rule matching. Every match comes from a readable stored query.
“It can detect patterns over time.”
It judges each document, or batch, on its own. It has no memory of earlier documents.
“The score tells me how good the match is.”
The hit score is not a measure of document quality. Most systems treat the result as a plain list of matches.
“Set it up once and forget it.”
Saved searches pile up, mappings evolve and abusive queries appear. Regular tidying and monitoring keep it healthy.
Think of the percolator as a very fast, very literal clerk. It follows the rules you give it exactly and tells you which ones a document satisfies. It does not guess, remember or deliver messages, and it works best inside a well-designed pipeline.
The percolator ships as a built-in part of Elasticsearch, but licensing terms and feature availability differ between versions and distributions. Confirm the details for your version in Elastic’s current documentation before you commit to a design.
31Glossary of Terms
A quick reference for the important words in this guide, in plain language.
| Term | Plain Meaning |
|---|---|
| Alias | A movable name that points to one or more indices, so you can switch indices without changing application code |
| Analyzer | The recipe that breaks text into searchable terms |
| Batching | Sending several documents in one request to share overhead |
| Candidate | A stored query shortlisted because its key terms appear in the incoming document |
| Candidate selection | The fast first step that shortlists stored queries that could match |
| Document | One item stored in Elasticsearch, such as a listing or an article |
| Highlighting | Marking the words in a document that caused a match |
| Index | A container for documents, or for stored queries, in Elasticsearch |
| Latency | How long one request takes |
| Mapping | The description of an index’s fields and their types |
| Metadata filter | An ordinary filter on fields stored beside a query, such as region or status |
| Percolate query | The query that takes documents and returns matching stored queries |
| Percolator | The Elasticsearch feature for storing queries and matching documents against them |
| Percolator field | The special field type that holds a stored query |
| Polling | Running a search repeatedly on a schedule to check for changes |
| Query extraction | Saving key terms from a query at storage time to speed up candidate selection |
| Replica | An extra copy of a shard that protects data and adds capacity |
| Reverse search | Finding which stored queries match a given document |
| Routing | Choosing which shard stores or serves a record using a value such as tenant or region |
| Shard | One piece of an index, stored and searched independently |
| Slot | The position number that says which of several percolated documents matched |
| Stored query | A query saved in an index so it can be matched against future documents |
| Throughput | How many documents or requests can be handled per second |
| Verification | Running each shortlisted query for real against the document to confirm the match |
32Frequently Asked Questions
Quick answers to the questions people ask most often when they first meet the percolator.
33Summary and Key Takeaways
Key Takeaways
- The percolator is reverse search. You store queries in an index using a special field type, then pass a document in to find out which stored queries match it.
- It solves the “which rules match this?” problem. It fits alerts on saved searches, tagging and classification by rules, and routing or screening of streams of events.
- Two-phase matching keeps it fast. Key terms saved at storage time let Elasticsearch shortlist likely queries, and only those are verified against a tiny in-memory copy of the document.
- Filters and specificity decide performance. Metadata fields such as region or tenant, batching, sensible shard counts and specific queries matter more than any single setting.
- It does not notify or remember. Your own pipeline must call it for each document and deliver the alerts, and it cannot detect patterns over time or use the current clock inside stored queries.
- Treat stored queries as untrusted and analysis as fixed. Use structured forms, limit sizes and counts, isolate tenants, and rebuild the index when analyzers change, keeping the source of truth in your own database.
- Watch for silence. Monitor matches and delivered alerts as well as errors, use canary queries, and keep a queue between matching and sending so no document is lost.