The Scroll API in Elasticsearch
Learn how Elasticsearch lets you read a very large set of results in steady batches from a frozen view of the data, how that view is kept alive inside the cluster, and when it is the right tool or the wrong one.
Imagine a public library that holds ten million index cards. A researcher needs a copy of every card that mentions a certain topic, and there may be two million of them. The librarian cannot hand over two million cards in one armful. Instead, the librarian takes a photograph of the whole card catalogue as it looks right now, gives the researcher a numbered ticket, and says: “Come back with this ticket, and I will hand you the next tray of one thousand cards each time.”
Even while the researcher walks back and forth, new cards may be filed in the real catalogue and old ones may be thrown away. It does not matter, because every tray comes from the photograph taken at the start. When the researcher has all the cards, the ticket is returned and the photograph is put away. That ticket and that photograph are the heart of the Scroll API in Elasticsearch.
1The Problem: Too Many Results for One Answer
Search is easy when you want ten hits. It gets hard when you want ten million.
Elasticsearch is a search and analytics engine. Most searches ask for a small answer, such as the top ten products or the latest fifty log lines. But real projects also need to read everything that matches. A team may need to export all customer records to a spreadsheet, copy an entire index into a new index, feed millions of documents into a machine learning job, or run a nightly report over a whole month of events.
Asking for everything in one response is impossible in practice. The answer would be enormous, it would exhaust memory on the servers and on the client, and it would take so long that the connection might time out. So the work must be split into many small steps. The real question is how to split it so that each step continues exactly where the last one stopped, without missing documents and without repeating any.
Think of moving house. You cannot carry the whole house in one trip. You make many trips with the car, and you need a checklist so that you never forget an item and never carry the same sofa twice. The Scroll API is that checklist for search results.
A second difficulty: the data keeps changing
Elasticsearch is usually a live system. While your export runs for ten minutes, other programs may add new documents, update old ones and delete some. If each trip looked at the current state of the index, you could see a document twice, because it moved position, or miss one entirely, because it shifted past your bookmark. A trustworthy export needs a stable picture of the data for the whole journey.
The Scroll API solves both difficulties together. It gives you batches, and it gives you a consistent view. As you will see, it pays for this with some resources held inside the cluster, and it has a newer relative that is now preferred for many jobs. Understanding both the power and the price is the goal of this tutorial.
2What Is the Scroll API?
A plain definition, then the words you will keep meeting.
The Scroll API is an Elasticsearch feature that lets you run one search and then fetch its results in many consecutive batches, all taken from a consistent snapshot of the data as it was when the search began. It works like a cursor in a traditional database: you open it, read from it repeatedly, and close it when you are done.
The process starts with an ordinary search request that carries one extra piece of information, a time value telling Elasticsearch how long to keep the search alive between batches. The response contains the first batch of documents and a special identifier called the scroll ID. You then send that ID back in follow-up requests, and each one returns the next batch, until a response comes back with no documents left.
Words you need to know
Scroll
The overall operation of reading a large result set in batches from a frozen view, like turning through the pages of a long scroll.
Scroll ID
A token returned by Elasticsearch that identifies your open scroll. You send it back to get the next batch.
Batch
One group of hits returned by a single scroll request. Its size is set by the size parameter of the first search.
Keep-alive
The time value that says how long each shard should hold your scroll open after serving a batch. It is refreshed on every request.
Search context
The internal object each shard keeps to remember your frozen view and your position. It is what makes a scroll stateful.
Snapshot view
The state of the index at the moment the scroll began. Later changes to documents are not visible inside the scroll.
Sliced scroll
A way to divide one scroll into several independent parts that can be read in parallel by different workers.
Clear scroll
An explicit request that closes a scroll early and frees the resources it holds.
Elastic’s documentation says the scroll API is no longer recommended for deep pagination. For paging through more than ten thousand hits while keeping a stable view, the newer approach of a point in time combined with search after is preferred. Scroll is still used widely, including inside Elasticsearch’s own reindex feature, so you must understand it either way.
3Why Normal Search Cannot Go Deep
To appreciate the scroll, first see why ordinary paging breaks down.
The simplest way to page through results is to use the from and size parameters. The first page has from zero and size ten. The second page has from ten and size ten. And so on. This is fine for a user browsing a few pages of results. It becomes a problem for deep pages, and the reason lies in how Elasticsearch distributes the work.
The hidden cost of deep pages
An index is split into shards, and each shard searches its own data. Suppose you want the page starting at position one hundred thousand with ten results. Each shard cannot just return its own ten. It must find its top one hundred thousand and ten hits and send them to the coordinating node, which merges all of them from every shard, sorts them and throws away all but the last ten. The deeper you go, the more each shard and the coordinator must work, and the memory grows with the depth.
flowchart TD
A["Client asks for page at from 100000, size 10"] --> B["Coordinating node"]
B --> C1["Shard 1 finds its top 100010"]
B --> C2["Shard 2 finds its top 100010"]
B --> C3["Shard 3 finds its top 100010"]
C1 --> D["Coordinating node merges 300030 hits"]
C2 --> D
C3 --> D
D --> E["Sorts, drops 100000 hits, keeps 10"]
E --> F["Client receives only 10 hits"]
The safety limit
To protect the cluster, Elasticsearch sets a limit called index.max_result_window, ten thousand by default. A request whose from plus size goes beyond that limit is rejected. This is a wise guard. It means ordinary paging can reach only the first ten thousand hits of any search.
Imagine asking three librarians, each in a different wing, to find the entries ranked 100,001 to 100,010 in a giant contest. Each librarian must first rank and count their own hundred thousand entries just to be able to tell you where their contribution falls. The work is enormous, yet you asked for ten entries.
The data-changes problem again
Paging with from and size also suffers from the shifting-data problem. Each page request is a fresh search of the current index. If a document is added between page one and page two, every later position shifts by one, and you may see one result twice or miss one. The scroll fixes both issues, since it holds one view and moves a cursor through it.
Raising the maximum result window to a huge number is not a real fix for deep paging. It only removes the safety rail while the cost keeps growing with depth. Use a method that is built for reading many results instead.
4The Big Idea: A Frozen View and a Bookmark
Two simple ideas combine to make scrolling work.
The first idea is the frozen view. When you start a scroll, each shard involved in the search takes note of the exact state of its data at that moment and promises to keep serving from that state until the scroll ends. New documents that arrive later, updates to existing documents and deletions are all ignored by that scroll. This is often called a snapshot, though it is unrelated to the backup feature that has the same name.
The second idea is the bookmark. Each shard remembers how far it has already delivered. When you ask for the next batch, the shard continues from its bookmark instead of starting the search again from the top. Because it continues, the work of each batch stays about the same however far you have travelled. Deep positions cost no more than early positions.
Reading a photocopied book with a physical bookmark is quick, no matter which page you are on. Reading a library book by asking a clerk “please count to page 4,000 and read me the next ten lines” every time would be painfully slow. The bookmark saves the counting.
Stateful means the cluster remembers you
A normal search is stateless. Once the response is sent, the cluster forgets the request. A scroll is stateful. The cluster must remember your frozen view and your position between requests. That memory lives inside the search contexts on the shards, and it is the source of both the strengths and the costs of scrolling. Anything kept alive on your behalf uses memory, file handles and disk space, so it must eventually be released.
| Property | Ordinary search with from and size | Scroll |
|---|---|---|
| Remembers state between requests | No | Yes, in a search context on each shard |
| Sees changes made between pages | Yes, each page is a fresh search | No, it uses the frozen view |
| Cost of deep positions | Grows with depth | Roughly the same for each batch |
| Depth limit | Ten thousand by default | Not limited by the result window |
| Cleanup needed | None | Yes, contexts should be cleared or allowed to expire |
| Suited to | User-facing pages of results | Batch reading and data export |
Every property of the scroll, good or bad, follows from one fact: Elasticsearch must hold a frozen view for you. Keep that fact in mind and the rest of the tutorial will feel natural.
5The Scroll Flow, Step by Step
A scroll always follows the same three phases: open, read repeatedly, close.
Open with an initial search
You send an ordinary search request to an index, add the keep-alive time and a batch size, and optionally a sort. Elasticsearch creates search contexts on the shards and runs the query.
Receive the first batch and a scroll ID
The response contains the first group of hits, plus the scroll ID that represents your open scroll.
Process the batch
Your program does whatever it needs with those hits: write them to a file, transform them, send them onward.
Ask for the next batch
You call the scroll endpoint with the most recent scroll ID and a fresh keep-alive time. The shards continue from their bookmarks and return the next hits.
Repeat until a batch comes back empty
An empty list of hits means every matching document has been delivered. The loop stops.
Clear the scroll
You send a clear request with the scroll ID, so the shards can release the frozen view immediately instead of waiting for the keep-alive to run out.
sequenceDiagram
participant C as Client
participant N as Coordinating Node
participant S as Shards
C->>N: Initial search with scroll time and batch size
N->>S: Create search contexts and run query
S-->>N: First hits and context references
N-->>C: First batch and scroll ID
loop Until a batch is empty
C->>N: Scroll request with scroll ID and new keep-alive
N->>S: Continue from each bookmark
S-->>N: Next hits
N-->>C: Next batch and scroll ID
end
C->>N: Clear scroll
N->>S: Release search contexts
The scroll ID may change
Each response includes a scroll ID. In many cases it stays the same, but the documentation says it can change between requests, so always use the most recent one you received. Treat it as an opaque token. Never try to read or build it yourself.
Where the ID travels
The scroll ID can be long, especially for indices with many shards, since it encodes references to each shard’s context. For that reason, send it in the body of the request instead of the web address, to avoid hitting limits on address length.
Forgetting to send the new keep-alive value in each scroll request. If you omit it, the contexts may expire after the original short time, and your next request will fail with a missing search context error.
6The Keep-Alive Time
The most misunderstood part of scrolling is how long it should stay alive.
The time you give, such as one minute, is not the total time of the whole scroll. It is the time the shards will hold your context after each request. Every request that reaches the shards resets that timer. So a scroll of two hours may work perfectly with a keep-alive of only one minute, as long as you always come back for the next batch within that minute.
A library reading room lets you keep your table reserved for five minutes whenever you step out. Each time you return and touch your papers, the clock starts again. You may keep the table all day, as long as you never leave it for more than five minutes at a time.
How to choose a value
The keep-alive should be long enough to process one batch and come back for the next, with a healthy safety margin. It should not be long enough to cover the entire job. A shorter value means that if your program crashes, the cluster releases the resources sooner. A longer value gives more room for slow processing but holds resources longer after a failure.
| Situation | Reasonable keep-alive | Why |
|---|---|---|
| Fast in-memory processing of each batch | One minute | Plenty of time and quick release after a failure |
| Batch is written to a slow remote system | Several minutes | Cover the slowest expected batch |
| Workers pause between batches for rate limiting | Longer than the pause plus processing | Avoid expiry during the wait |
| Unknown or variable processing time | Measure first, then add a margin | Prevents both premature expiry and waste |
The cluster ceiling
Elasticsearch protects itself with a maximum keep-alive, controlled by the setting search.max_keep_alive, which is twenty-four hours by default. Asking for a longer time is rejected. There is also a default keep-alive for cases where none is given, controlled by search.default_keep_alive. Both are cluster settings that an administrator can adjust, though raising the maximum should be done with care.
Time your first few batches during a test and set the keep-alive to several times the slowest one. Then watch for missing context errors in production, which are a clear sign that some batches take longer than you assumed.
Setting the keep-alive to hours “to be safe”. If the client crashes, the frozen view stays on the cluster for all those hours, holding memory, file handles and disk space. Prefer short values that are refreshed on every request.
7Batch Size and Sorting
Two small choices strongly affect how fast a scroll runs.
The batch size
The size parameter of the first search decides how many hits each batch contains. A larger size means fewer round trips, but each response is bigger and needs more memory on the servers and on your client. A very small size makes the job chatty and slow. A common starting range is between several hundred and a few thousand documents per batch, adjusted to document size and network speed.
There is a limit here as well. The size of a scroll batch must not exceed the index result window setting, ten thousand by default, because a scroll batch costs as much memory as a result window of the same size. If you ask for more, Elasticsearch replies with an error that mentions this setting.
Document size
Large documents make each batch heavy. Use a smaller size for big documents, and a larger one for small ones.
Network speed
A slow network favours fewer, larger batches, while a fast local network handles many small ones easily.
Client memory
The whole batch must fit in the client’s memory along with whatever processing you do on it.
Source filtering
Ask only for the fields you need. Smaller hits mean faster batches at any size.
The sort order
If you do not care about the order of the documents, ask for them sorted by the special value _doc. This means “in the natural index order”. Elastic’s documentation notes that scroll requests are faster when sorted this way, because Elasticsearch does not have to compare and order documents by score or by a field, and each shard can simply walk through its data.
| Sort choice | Speed | When to use |
|---|---|---|
| By the _doc value | Fastest | Exports and copies where order does not matter |
| By relevance score | Slower | When you truly need best matches first, rare for large scrolls |
| By a field such as a timestamp | Slower, needs sorting work | When downstream processing needs chronological order |
If you only need to photocopy every card in a drawer, you take them in the order they sit. If someone insists that the copies come out alphabetically, you must sort as you go, and the job takes longer.
If you need ordered output but the order is not required during transfer, scroll unsorted using the natural order and sort afterwards in your own system, where sorting a large file is often cheaper than forcing the cluster to sort during every batch.
8Ending a Scroll: Clearing the Search Context
Finishing politely is part of the contract.
A scroll ends in one of three ways. The best way is that your program finishes reading and sends a clear scroll request naming the scroll ID. Elasticsearch then releases the search contexts on every shard at once. The second way is that the keep-alive expires because no request touched the scroll in time, and the contexts are removed automatically. The third way is a failure, such as a node restart, which destroys the contexts along with the node’s memory.
Why you should always clear
Automatic expiry is a safety net, not a plan. If you rely on it, then for the entire keep-alive after your last request the cluster keeps holding a frozen view that nobody will read. Multiplied by many jobs, this quickly adds up. Clearing a finished scroll is a small extra call that returns resources at once.
flowchart TD
A["Scroll is open"] --> B{"How does it end?"}
B -->|Client sends clear request| C["All shard contexts released immediately"]
B -->|Keep-alive runs out| D["Contexts expire and are removed automatically"]
B -->|Node restarts or shard is lost| E["Context disappears, next request fails"]
C --> F["Resources returned right away"]
D --> G["Resources held until the timeout"]
E --> H["Client must start a new scroll"]
Clear one scroll
Send the specific scroll ID to the clear endpoint. This is the normal way to finish.
Clear several scrolls
Send a list of scroll IDs together, useful for sliced scrolls where each slice has its own ID.
Clear all scrolls
A special value clears every open scroll for the user. It is handy in emergencies, but it can disrupt other jobs, so use it with great care.
When you finish using the library’s photograph of the catalogue, you hand back your ticket. The librarian then shreds the photograph and frees the shelf space. If you walk away without handing back the ticket, the photograph sits there until the ticket expires.
Clearing only in the success path of the program. If an error occurs halfway, the scroll is abandoned. Put the clear call in a cleanup step that always runs, such as a finally block or an equivalent in your language.
9Internal Working: Search Contexts, Readers and Bookmarks
Now let us look inside a shard to see how the frozen view really works.
Each shard is a Lucene index made of immutable segments. When the initial scroll search reaches a shard, the shard creates a search context. Inside it is a reader that points at the exact list of segments that existed at that moment, along with the record of which documents in those segments were alive. This reader is the frozen view.
Because segments never change once written, holding on to a set of segments is a cheap and elegant way to freeze data. Nothing needs to be copied. The shard simply refuses to delete those segment files while a reader still uses them. New documents go into new segments that the frozen reader does not know about, so the scroll never sees them.
graph TD
A["Scroll starts"] --> B["Shard creates search context"]
B --> C["Reader pins segments A, B and C"]
C --> D["Live index continues"]
D --> E["New segment D appears, invisible to the scroll"]
D --> F["Merge combines A and B into segment E"]
F --> G["Old segments A and B stay on disk while the scroll needs them"]
C --> H["Each batch continues from the bookmark inside the pinned view"]
How the bookmark works
Along with the reader, the search context stores the position reached so far for the chosen sort order. When the next batch is requested, the shard resumes after that position. It does not re-run the whole search from the beginning and discard the first hits. This is why a scroll’s cost per batch stays steady, unlike deep paging.
How the batches come together
For each batch, the coordinating node asks every shard for its next portion, merges the portions, keeps the best according to the sort order and returns exactly the requested batch size. Each shard advances only as far as the hits that were actually delivered, so nothing is lost or repeated between batches.
Imagine each wing of the library has its own clerk holding a numbered photograph of the wing’s cards. Each clerk remembers the last card handed over. When the researcher asks for the next tray, each clerk continues from that card, and the head librarian combines the results into one tray.
The context is tied to a specific copy of each shard on a specific node. That is why a scroll cannot simply move to another node if the first one fails. The frozen view exists only where it was created.
10The Price of Freezing: Old Segments, Disk and File Handles
Holding a frozen view is not free, and the cost appears in three places.
Elasticsearch continually merges small segments into larger ones in the background, and afterwards deletes the old small ones. A scroll disturbs this tidy routine. Any segment still needed by an open scroll cannot be deleted, even if a merge has already replaced it. The old files stay on disk until the scroll finishes.
Extra disk space
Both the old pinned segments and the newly merged ones exist at the same time. On a busy index with long scrolls, this can add up to a large amount of temporary space.
More open file handles
Every segment file that is kept alive occupies an operating system file handle. Many long scrolls on many shards can approach the limit.
Memory for contexts
Each context holds its reader, and some memory structures, on the heap and in caches, for as long as it lives.
Deleted data lingers
Documents deleted after the scroll started still occupy pinned segments, so space from deletes is not returned until the scroll ends.
A cleaner is about to throw out old cardboard boxes after repacking their contents into bigger boxes. But a researcher is still reading from a photograph of the old boxes. The cleaner must wait, so both old and new boxes crowd the storeroom.
Why this matters most on busy indices
If an index is nearly static, such as yesterday’s log index, a scroll pins segments that will not change anyway, and the extra cost is small. If the index is busy, with constant indexing and heavy merging, a long scroll can hold a large amount of obsolete data. The longer the scroll runs and the more churn the index sees, the greater the temporary footprint.
A forgotten scroll on a busy index can quietly keep gigabytes of obsolete segments alive. If a disk is filling faster than expected, check the number of open scroll contexts among the first suspects.
Elasticsearch documentation asks operators to give nodes ample free file handles, and to monitor the number of open contexts. Both are cheap habits that prevent scroll-related surprises.
11Limits and Settings You Should Know
A few guard rails protect the cluster from runaway scrolls.
| Setting | What it controls | Typical default |
|---|---|---|
| search.max_open_scroll_context | The maximum number of scroll contexts open at the same time on a node | 500 |
| search.max_keep_alive | The longest keep-alive a request may ask for | 24 hours |
| search.default_keep_alive | The keep-alive used when a request does not specify one | 5 minutes |
| index.max_result_window | The largest allowed batch size, and the depth limit of ordinary paging | 10,000 |
| index.max_slices_per_scroll | The largest number of slices allowed in a sliced scroll | 1,024 |
Defaults can change between versions, and some settings are dynamic while others are static. Always confirm the current names and defaults in the documentation for your release before changing anything.
What happens when limits are hit
If too many scrolls are open on a node, new scroll requests are rejected with an error stating that too many scroll contexts are open. The message names the limit. This usually means that scrolls are being started without being cleared, or that many parallel jobs are running at once. Raising the limit is possible, but the better fix is usually to find out why so many contexts are needed and to clear them properly.
If the requested keep-alive is greater than the maximum, the request is rejected as invalid, with a message that points to the maximum setting. If the batch size is above the result window, the request fails with a message about batch size. All three errors are clear and quick to diagnose once you know the settings behind them.
A reading room has a limited number of tables, and each visitor may reserve one for a limited time. If every table is reserved, the next visitor is turned away with a polite note, and the fix is to encourage people to return their tickets.
Before raising any limit, look at the current number of open contexts using the node statistics. If the number is far below the limit during normal operation and spikes only during broken jobs, fix the jobs instead.
12Sliced Scroll: Reading in Parallel
One reader is sometimes too slow. Slicing lets many readers share the work.
A single scroll walks through results one batch after another, with only one client busy at a time. For very large jobs, you can split the work into slices. Each slice is an independent scroll that covers a separate portion of the documents. You give every slice a number, from zero up to the total number of slices minus one, and the total count. Different workers can then read different slices at the same time.
graph LR
Q["One query and one frozen view idea"] --> S0["Slice 0 with its own scroll ID"]
Q --> S1["Slice 1 with its own scroll ID"]
Q --> S2["Slice 2 with its own scroll ID"]
S0 --> W0["Worker 0"]
S1 --> W1["Worker 1"]
S2 --> W2["Worker 2"]
W0 --> R["Together they read every matching document once"]
W1 --> R
W2 --> R
How documents are divided
By default, Elasticsearch decides which slice a document belongs to using a hash of its identifier, spread evenly. Each document falls into exactly one slice, so together the slices cover all matches without overlap. You may also name a numeric field with doc values, and the slice is chosen from that field’s value instead. That can be more efficient in some cases, but the field must exist in all documents and should hold a single value each.
| Choice | Advice |
|---|---|
| Number of slices | Start with a number equal to or below the number of shards, so each slice maps neatly to shards |
| More slices than shards | Possible, but the slice filter can be slow at first, so measure carefully |
| Custom field for slicing | Use a numeric field with doc values, with one value per document |
| Keep-alive per slice | Each slice needs its own keep-alive refreshed with its own requests |
| Clearing | Clear every slice’s scroll ID when finished |
Instead of one researcher reading a huge catalogue photograph tray by tray, three researchers each take every third card. They work at the same time, and together they see every card once.
Slices multiply the resources held on the cluster. Ten slices means ten sets of contexts and ten streams of requests. Increase parallelism only as far as the cluster and the destination system can comfortably handle.
13Scroll Versus Search After With a Point in Time
Elastic now recommends a newer pair of features for many jobs that once used scroll.
A point in time, often shortened to PIT, is a lightweight handle that freezes a view of one or more indices for a chosen period, similar to the frozen view of a scroll. Search after is a way of continuing a search by giving the sort values of the last hit you saw. When you combine them, you get stable paging that does not need a heavy per-request cursor.
| Aspect | Scroll | Point in time with search after |
|---|---|---|
| Frozen view | Yes, held in each search context | Yes, held by the point in time |
| Where the position lives | Inside the cluster, in the search context | In your client, as the last sort values |
| Arbitrary sort order | Supported, but the natural order is fastest | Supported, with a tiebreaker to keep order stable |
| Random access or restart | Not possible, you can only move forward | You can resume from any sort values you saved |
| Parallel reading | Sliced scroll | Slicing is also supported in modern versions with a point in time |
| Documentation recommendation | No longer recommended for deep paging | Recommended for deep paging with a stable view |
| Common uses today | Internal operations and older integrations | New export and paging code |
Why the newer way is often better
With search after, your program stores the sort values of the last hit. If the program crashes, it can restart from those values instead of losing its place. The cluster holds less per-request state, and the cost of keeping the frozen view alive is lower. Search after is also friendlier to user-facing paging, where the user moves forward and backward.
When scroll still makes sense
Scroll remains valid and is very common in existing tools. It is simple to reason about, it has a natural fit for reading everything once, and many client libraries offer helper functions that wrap it. If your Elasticsearch version or your client library does not yet support the newer approach, scroll is a safe choice.
Scroll is a librarian who remembers exactly where you stopped. Search after is you writing down the last card number you read. If the librarian goes home sick, your own note still tells you where to resume.
For new code, prefer a point in time with search after. For maintaining existing scroll-based tools, keep them working and plan to migrate when convenient, checking your version’s documentation for details.
14Choosing the Right Tool for Reading Results
Different reading jobs need different tools.
| Your job | Best fit | Reason |
|---|---|---|
| Show the first few pages of results to a user | Ordinary from and size | Simple, stateless, cheap for shallow depths |
| Let a user page deeply with a stable view | Point in time and search after | Stable, lightweight and restartable |
| Export all matching documents once | Point in time with search after, or scroll | Reads everything from a consistent view |
| Copy an index to another index | The reindex API | Built for the job, handles batches and slicing for you |
| Change many documents by a query | Update by query | Uses a consistent view and batching internally |
| Remove many documents by a query | Delete by query | Same reasons |
| Only summaries, such as counts and totals | Aggregations | No need to read every document |
| Real-time updates to a live feed | Stream or poll with fresh searches | A frozen view would hide new data |
Ask whether you need the documents at all
Many scroll jobs read millions of documents only to compute a total, an average or a top-ten list. Aggregations do that work inside the cluster and return a tiny answer. Before writing an export loop, check whether a single aggregation can give you what you need.
Scroll is not meant for serving real-time user requests. A user clicking through result pages should not each hold a scroll open. The resources held per scroll make that approach expensive and fragile at scale.
15Scroll Inside Other Elasticsearch Features
You may be using scrolls without knowing it.
Several built-in features of Elasticsearch are, at heart, a loop that scrolls through the matching documents and does something with each batch. Knowing this helps you understand their behaviour, their resource use and the settings that affect them.
Reindex
Reads documents from a source index in batches and writes them into a destination. It exposes a batch size setting and supports slices to speed up the copying.
Update by query
Finds documents that match a query and rewrites each one, working through batches taken from a consistent view.
Delete by query
Finds matching documents and deletes them batch by batch. Because it relies on a consistent view, documents that change while it runs may cause version conflicts.
Client helper functions
Official client libraries include helpers that wrap the scroll loop, handling scroll IDs, refreshing the keep-alive and clearing at the end.
Data shippers and connectors
Tools that copy data out of Elasticsearch into other systems often use scrolling to read whole indices safely.
Analytics and machine learning exports
Jobs that read large sets of documents for training or reporting may use scroll-style reading to gather them.
A removal firm uses a truck and a checklist for every house it empties. Whether you call the job “moving”, “clearing out” or “storing”, the same truck-and-checklist routine sits underneath. Reindex and the by-query APIs are such jobs, and the scroll is their truck and checklist.
What this means for you
When a long reindex or update by query runs, it holds frozen views on the source index, with all the disk and file handle costs discussed earlier. This is one reason why large reindex jobs are best run during quieter periods, and why their duration matters. It also explains version conflicts in update and delete by query: the frozen view may contain an older version of a document than the live index now holds.
Prefer these built-in APIs over writing your own copy loop when they fit your task. They already handle batching, retries, throttling and slicing, and they are maintained together with the rest of the product.
16Consistency: What You Will and Will Not See
The frozen view gives strong guarantees, and a few surprises.
What the scroll guarantees
Every document that was searchable at the moment the scroll began, and matches your query, will be delivered exactly once. No document appears twice, and none is skipped because of changes made after the start. This is the property that makes scrolls trustworthy for exports and migrations.
What the scroll will not show
| Event after the scroll started | Does the scroll see it? | Explanation |
|---|---|---|
| A new document is indexed | No | It lives in segments the frozen view does not know about |
| A document is updated | No, the scroll returns the old version | The old version remains in the pinned view |
| A document is deleted | No, the scroll still returns it | The deletion marker is not part of the frozen view |
| A document was indexed but not yet refreshed at the start | No | Only searchable documents are included in the view |
| The index is deleted | The scroll may fail | The underlying data disappears with the index |
sequenceDiagram
participant W as Writer
participant I as Live Index
participant S as Scroll View
participant R as Reader
Note over S: Scroll starts, view frozen with documents 1 to 5
W->>I: Add document 6
W->>I: Update document 2
W->>I: Delete document 4
R->>S: Request next batch
S-->>R: Documents from the frozen view only
Note over R: Reader sees old document 2 and document 4, and never sees document 6
The difference between consistent and current
The scroll is consistent, but not current. Its answers are perfectly repeatable, and they describe the index at one moment. They may be minutes or hours out of date by the time you finish. If your task needs the freshest data, such as syncing a live system, you must plan a follow-up pass to catch changes made during the scroll, for example by reading the documents whose timestamps are newer than the scroll’s start.
A photograph of a busy street is always accurate for the moment it was taken. It never changes, but it also cannot tell you that a bus arrived later. For an export, that is exactly what you want. For live monitoring, it is exactly what you do not want.
Using a scroll to keep two systems in sync without a plan for changes made during the scroll leaves gaps. Record the start time, then catch up on later changes in a second pass.
17Failure Behaviour: Restarts, Timeouts and Lost Contexts
Because scrolls are stateful, some failures cannot be repaired by simply retrying.
The search context lives in the memory of the node that holds the shard copy. It is not saved to disk and it is not copied to other nodes. If that node restarts, crashes, or if the shard is moved away, the context disappears. The next scroll request that refers to it fails with an error saying that no search context was found for the given identifier.
| Event | Effect on the scroll | What to do |
|---|---|---|
| Keep-alive expires between requests | The context is removed, the next request fails | Start a new scroll, and increase the keep-alive if batches take long |
| A node holding a shard copy restarts | Its contexts are lost | Start again, ideally from a saved position if you track one |
| A shard relocates to another node | The old context is not carried over | Avoid long scrolls during rebalancing, or be ready to restart |
| Client crashes | Contexts remain until the keep-alive expires | Use short keep-alives to release resources quickly |
| Network glitch on one request | The request may be safe to retry | Retry with the same scroll ID, with limits |
| Too many open contexts on a node | New scrolls are rejected | Clear finished scrolls and review parallel jobs |
Designing for restartability
Long export jobs should expect to fail sometimes. A robust design records progress outside the cluster, for example the last value of a sort field that was safely written, and knows how to begin a new scroll from that point using a range query. That way, a lost context costs a few minutes of work, not the whole job.
If the librarian holding your photograph goes home sick, the photograph goes with them. A researcher who kept notes of the last card number copied can ask a new librarian to take a fresh photograph and continue from the noted card.
For jobs that run longer than a few minutes, sort by a stable field, save checkpoints, and make every write on the destination side idempotent. Then restarting from a checkpoint is safe even if a few documents are read twice.
18Real-World Examples
The same technique serves very different teams.
Nightly compliance export
A bank must send all transaction records of the previous day to an archive system. A job scrolls through yesterday’s index, which no longer receives writes, writes each batch to files and uploads them. Because the index is static, extra disk and merge costs are small, and the consistent view gives an exact copy.
Rebuilding an index with a new mapping
A retailer needs to change how a field is mapped. Since mappings of existing fields cannot be changed in place, the team creates a new index and copies all documents into it using the reindex API, which scrolls through the old index in batches, optionally sliced for speed.
Feeding a data warehouse
An analytics team loads Elasticsearch documents into a data warehouse each week. A sliced scroll with several workers reads different slices in parallel, and each worker saves checkpoints. The team records the start time so a follow-up pass can catch documents changed during the run.
Machine learning training data
A data science team needs millions of labelled documents for training. They read them with a scroll, sorted in natural order for speed, and stream them into their training pipeline. They ask only for the fields they need to keep the batches small.
Bulk clean-up after a bad ingest
A faulty pipeline added malformed documents to an index. An operator uses delete by query, which scrolls through the matches and deletes them in batches. They run it in a quiet period and watch the number of open contexts and the free disk space while it works.
Legacy tool that still uses scroll
An older reporting tool loops through search results with the scroll API. The team leaves it in place but adds proper clearing of contexts, sets a modest keep-alive, and plans to move it to the newer point-in-time approach at the next upgrade.
19Security and Compliance Considerations
A scroll ID is a key to a set of results, so treat it with care.
When security features are enabled in Elasticsearch, a scroll is tied to the user who created it. The cluster checks that scroll requests come from the same authenticated user as the initial search. A different user who somehow obtains the scroll ID should be refused. This protects results from being read by another account, but it is still wise to avoid leaking scroll IDs in logs or shared tools.
Least privilege
Give export jobs a role that can read only the indices they need, and nothing that can write or administer.
Document and field rules apply
Any document-level or field-level security on the user’s role is applied at the start of the scroll, so exports contain only what that user may see.
Protect scroll IDs
Do not print scroll IDs into shared logs, tickets or chat messages. Treat them as short-lived credentials for reading data.
Encrypt the export destination
A scroll may copy millions of records out of the cluster. Make sure the files or systems that receive them are encrypted and access-controlled.
Bulk export is the moment when data most easily leaves its protected home. Keep an audit trail of who ran which export, when, and where the output went. Enable audit logging in Elasticsearch, and log the export job’s own actions too.
Frozen views and deletion promises
Because a scroll pins a snapshot, a document deleted for privacy reasons can still be delivered by a scroll that started before the deletion. If you have a policy that data must vanish immediately, be aware that an in-flight scroll may still hold it, and limit scroll duration accordingly. The segments holding it are also kept on disk until the scroll ends.
Run exports with dedicated service accounts, never with personal or administrator accounts. That keeps audit trails clear and limits the damage if credentials leak.
20Monitoring, Logging and Metrics
You should know how many scrolls are open before they cause trouble.
Elasticsearch reports scroll activity in its node and index statistics. A handful of numbers gives you a clear picture of whether scrolls are healthy, and they are easy to chart and alert on.
| Signal | Where to see it | What it tells you |
|---|---|---|
| Open search contexts | Node stats for search | How many frozen views are currently held, a direct indicator of leaks |
| Current scrolls | Node and index search stats | How many scroll operations are running right now |
| Total scrolls and total scroll time | Node and index search stats | How much scrolling has been done overall, and how long each takes on average |
| Segment count and deleted documents | Segments and cat APIs | Whether pinned segments are inflating storage |
| Disk free space | Cat allocation and host metrics | Whether scrolls plus merges are consuming too much room |
| Open file descriptors | Node stats for the process | Whether handles are approaching the operating system limit |
| Rejections and errors | Client logs and cluster logs | Too many contexts, missing contexts or invalid keep-alive requests |
| Slow search logs | Index slow log settings | Which scroll batches are slow, and on which shards |
Alerts worth having
Open contexts high
Warn when open contexts on a node exceed a comfortable fraction of the configured maximum.
Contexts not falling
Warn when the count stays high for a long time with no matching jobs, a sign of leaked scrolls.
Low disk during scrolls
Warn when free space drops while scrolls are running on busy indices.
Repeated missing context errors
Warn when clients report lost contexts often, which suggests keep-alive values that are too short.
Add scroll statistics to the same dashboard as disk space and file descriptors. Trouble from scrolls is usually a combination of all three, and seeing them side by side saves hours of guessing.
21Design Patterns and Anti-Patterns
Habits that keep scrolls safe, and traps that catch careful teams.
Patterns worth copying
Short keep-alive, refreshed every batch
Use a modest keep-alive, send it with each request, and let crashes release resources quickly.
Always clear in a cleanup step
Place the clear request in a block that runs on success and on failure.
Natural order for exports
Sort by the natural document order when order does not matter, and sort later in your own system if needed.
Checkpointed jobs
Record progress outside the cluster so that a lost context costs minutes, not hours.
Scroll static indices
Schedule big reads on indices that no longer change, such as yesterday’s, so pinned segments cost little.
Prefer built-in APIs
Use reindex, update by query and delete by query instead of custom loops when they fit the task.
Anti-patterns to avoid
Problem
A website opens a scroll for each visitor who views search results and keeps it open so that the visitor can click “next page” whenever they like.
Why It’s Harmful
Each visitor pins a frozen view on every shard, holding memory, file handles and disk. A busy day opens thousands of contexts, hits the limit and blocks other searches, while most visitors never return for a second page.
Correct Approach
Use ordinary paging for the first pages, and use search after, with a point in time when a stable view is needed. Reserve scroll for batch jobs.
Problem
A script never clears its scrolls and uses a keep-alive of several hours “to be safe”, then is run many times a day.
Why It’s Harmful
Contexts pile up, each holding pinned segments. Disk usage climbs, file handles run short, and eventually new scrolls are rejected because the maximum is reached.
Correct Approach
Use a short keep-alive refreshed with every batch, and clear the scroll when finished, including when errors occur.
Problem
A team scrolls through every document to compute totals and averages in application code.
Why It’s Harmful
Millions of documents travel over the network only to be reduced to a few numbers. The cluster and the client both do far more work than needed, and a frozen view is held for the whole run.
Correct Approach
Use aggregations, which compute summaries inside the cluster and return a tiny result.
Problem
An export job uses a huge number of slices and a very large batch size to finish faster, and runs on the busiest index during peak hours.
Why It’s Harmful
Many parallel contexts and large batches strain memory and disk, compete with live searches and indexing, and can trigger rejections or slow the whole cluster for users.
Correct Approach
Choose a moderate number of slices, size batches for the documents involved, and run heavy exports during quiet hours on static indices where possible.
22Advantages and Trade-offs
An honest balance sheet for the scroll API.
Advantages
- Reads very large result sets in manageable batches.
- Gives a consistent view, so every document is delivered exactly once.
- Cost per batch stays steady, no matter how deep you are.
- Fastest when sorted by natural order, ideal for exports and copies.
- Supports slicing for parallel reading by several workers.
- Widely supported by clients, tools and built-in features.
Disadvantages / Trade-offs
- Holds resources in the cluster: memory, file handles and pinned segments.
- Is stateful, so node restarts and shard moves can destroy the context.
- Shows old data, since changes after the start are invisible.
- Cannot go backward or jump, only forward from the current position.
- Unsuitable for real-time, user-facing paging.
- No longer the recommended approach for deep paging in current documentation.
23Best Practices and Common Mistakes
A checklist to keep next to any export script.
Best practices
Ask whether you need scroll at all
Check whether an aggregation, a built-in API or a point-in-time approach would serve better.
Keep the keep-alive modest
Long enough for one batch plus a safety margin, and refreshed on every request.
Fetch only what you need
Use source filtering to request specific fields, making batches smaller and faster.
Always use the latest scroll ID
Update your variable after every response, and send the ID in the request body.
Clear when finished
Release the frozen view immediately, in a cleanup step that runs even on errors.
Plan for failure
Save checkpoints and make the destination writes safe to repeat.
Monitor open contexts
Track the number of open contexts, disk space and file handles during and after big jobs.
Common mistakes
Treating the keep-alive as total job time
Setting a keep-alive to cover the entire export rather than the time between batches, and so holding resources far longer than needed.
Reusing an old scroll ID
Sending the very first scroll ID in every request instead of the most recent one, which can produce errors or repeated batches.
Ignoring the end condition
Failing to stop when a batch is empty, or stopping early on a short batch when more batches may follow.
Expecting fresh data
Being surprised that documents added during the scroll never appear. The scroll shows the state at its start.
Scrolling a busy index at peak time
Running heavy long scrolls when the index is under constant writes, which pins a lot of obsolete data.
24Troubleshooting Scroll Problems
Match the symptom to the likely cause and check the simplest explanation first.
| Symptom | Likely cause | What to check |
|---|---|---|
| Error saying no search context was found | Keep-alive expired, node restarted or shard moved | Batch processing time versus keep-alive, node restarts and relocations |
| Error about too many scroll contexts | Scrolls are not being cleared, or too many jobs run at once | Open context counts, job schedules and clear calls |
| Error about keep-alive being too large | Requested time exceeds the configured maximum | The maximum keep-alive setting and the value your client sends |
| Error about batch size being too large | Size is above the result window | The index result window setting and the size parameter |
| Disk usage grows during an export | Pinned old segments on a busy index | Segment counts, merge activity and duration of scrolls |
| Documents added later are missing | Frozen view excludes changes after the start | Expected behaviour, plan a catch-up pass if needed |
| Scroll is slow | Sorting by score or field, huge documents or slow client processing | Sort choice, source filtering and time spent between requests |
| Web address too long error | Long scroll ID sent in the URL | Send the scroll ID in the request body |
| Another user cannot continue a scroll | Scroll is bound to the user that created it | Which credentials each request uses |
A calm routine
First, read the exact error message, because most scroll errors name their cause and the setting behind it. Second, look at the number of open contexts and the free disk space on the nodes involved. Third, compare the keep-alive with the real time between requests. Fourth, check whether nodes restarted or shards moved during the job. These four checks explain the vast majority of scroll problems.
Clearing all scrolls can free resources quickly, but it will also break other people’s running jobs. Use it only when the cluster is in real trouble, and tell the affected teams afterwards.
25Frequently Asked Questions
Short answers to doubts that come up again and again.
It lets you run one search and read its results in many consecutive batches from a consistent snapshot of the data, using a scroll ID to mark your position.
No. It is designed for batch processing such as exports and reindexing. User-facing pages should use ordinary paging or search after with a point in time.
No. It only needs to cover the time between one request and the next. Each request refreshes it.
No. The scroll shows the index as it was when the scroll started. Later additions, updates and deletions are not visible to it.
It will expire on its own after the keep-alive, but clearing it as soon as you finish frees memory, file handles and pinned disk space at once, so you should always clear it.
Each shard can simply walk through its documents without comparing scores or field values, so batches are cheaper to produce.
A way of dividing one scroll into several independent slices, each with its own scroll ID, so that different workers can read different portions in parallel.
The contexts held by that node are lost, and your next request fails with a missing search context error. You must begin again, ideally from a checkpoint you saved.
Both provide a stable view. With scroll the cluster remembers your position in a search context. With search after your client remembers the last sort values, which makes restarting easier and the cluster’s load lighter. Current documentation recommends the latter for deep paging.
Segments still needed by the scroll cannot be deleted after merges, so old and new segments coexist. This is worst on busy indices with long scrolls.
With security enabled, the scroll is bound to the authenticated user who created it, so other users should be refused. Still, avoid exposing scroll IDs in logs or shared tools.
Yes, they read documents in batches from a consistent view in a similar way, which is why their behaviour, resource use and conflicts resemble those of a scroll.
26Summary and Key Takeaways
The Scroll API lets you read a very large result set in steady batches from a frozen view of the data. You open it with a search that carries a keep-alive time, receive a scroll ID, come back for batch after batch, and clear it when finished. Inside the cluster, each shard holds a search context that pins the segments of the frozen view and remembers your position, which keeps every batch cheap but costs memory, file handles and disk space until the scroll ends. It is ideal for exports, reindexing and batch clean-ups, poorly suited to real-time paging, and today the documentation prefers a point in time with search after for deep paging. Use it with short keep-alives, natural ordering, proper clearing, monitoring and a plan for failure.
Key Takeaways
- Batches from a frozen view — a scroll reads results in consecutive batches from a snapshot of the index taken when the scroll began.
- The scroll ID is your bookmark — always send the latest ID, in the request body, and stop when a batch comes back empty.
- Keep-alive is per request — it must cover the time between batches, not the whole job, and each request refreshes it.
- Stateful means costly — open contexts pin segments and hold memory and file handles, so clear scrolls promptly and watch disk and open contexts.
- Sort by natural order and slice for speed — use the natural document order for exports, and a moderate number of slices for parallel reading.
- Consistent, not current — changes after the start are invisible, and failures such as restarts destroy the context, so checkpoint long jobs.
- Prefer the right tool — use aggregations for summaries, built-in APIs for copying and clean-up, and a point in time with search after for new deep paging code.