Sorting and deep pagination

Elasticsearch · Practical · 10 min

Result lists need stable ordering and paging, and naive deep paging hits hard limits.

By default hits are ordered by _score. "sort": [{ "price": "asc" }, { "_score": "desc" }] sorts by fields; sort on keyword, numeric or date fields, never on analyzed text.

from/size paging ("from": 20, "size": 10 for page 3) is fine for the first pages. Each shard must produce from + size hits and the coordinating node merges them all, so cost grows with depth, and requests beyond index.max_result_window (10,000 by default) are rejected.

For deep pagination or exporting results use search_after:

  • open a point in time (POST /index/_pit?keep_alive=1m) so pages see a consistent snapshot even while documents change,
  • sort by your fields plus the implicit _shard_doc tie-breaker that PIT searches add,
  • pass each last hit's sort values as search_after in the next request,
  • close the PIT when done.

search_after can't jump to page 500 directly, which matches how infinite scroll and exports work anyway. The older scroll API still exists but is no longer recommended for deep pagination; prefer PIT with search_after.

track_total_hits controls exact counting: false skips it entirely for speed, true counts exactly at a cost.

Example

POST /orders/_pit?keep_alive=1m
# { "id": "46ToAwMDaWR5BXV1..." }

GET /_search
{
  "size": 100,
  "query": { "term": { "status": "paid" } },
  "pit": { "id": "46ToAwMDaWR5BXV1...", "keep_alive": "1m" },
  "sort": [ { "placed_at": "desc" } ]
}
# each hit ends with "sort": [1760700000000, 4294967298]

GET /_search
{
  "size": 100,
  "query": { "term": { "status": "paid" } },
  "pit": { "id": "46ToAwMDaWR5BXV1...", "keep_alive": "1m" },
  "sort": [ { "placed_at": "desc" } ],
  "search_after": [1760700000000, 4294967298]
}

DELETE /_pit
{ "id": "46ToAwMDaWR5BXV1..." }

Remember

  • Sort on keyword, numeric or date fields
  • from/size for shallow pages; limited to 10,000 by default
  • Deep paging: point in time + search_after
  • Close PITs when you're done

Watch out for

  • Raising max_result_window to millions to keep using from/size, which loads the cluster heavily.

Check yourself

  1. An export job must read all 3 million matching documents consistently. What should it use?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html