Sorting
The sort clause orders hits by one or more fields (or _score), overriding the default relevance ranking.
Why it matters
Tables, time-ordered logs, and price-sorted catalogs all need explicit ordering. Sorting on the wrong field type silently fails or is slow, and sorting on analyzed text is an outright error — so understanding doc_values, multi-level sort, and tiebreakers is essential, especially because deterministic sort underpins search_after pagination.
How it works
Sort reads columnar doc_values (on-disk, per-segment), not the inverted-index.
| Field | Sortable? | Mechanism |
|---|---|---|
numeric / date / boolean | yes | doc_values |
keyword | yes | doc_values |
text | no | needs fielddata (heap-heavy) |
_score | yes | computed at query time |
- Multi-level —
sortis an array; ties at level 1 break by level 2, etc. Append a unique field for stable order. missing—_first/_lastor a default value controls where null fields land.mode— for multi-valued fields choosemin/max/avg/sum/median(e.g. sort by the cheapest variantmode:min).- Skips scoring — sorting by a field alone sets
_score: null; add"track_scores": trueif you still need scores.
Example
{ "sort": [
{ "price": { "order": "asc", "mode": "min", "missing": "_last" } },
{ "rating": "desc" },
{ "_shard_doc": "asc" } ] }
Cheapest variant first; ties broken by rating, then by _shard_doc so search_after cursors stay deterministic.
Pitfalls
- Sorting on
text— throws “Fielddata is disabled”; sort on the.keywordsub-field, never enablefielddataon a high-cardinalitytextfield (it can blow heap). - Non-unique sort + pagination — ties cause skipped/duplicated rows across pages; always end with a unique tiebreaker.
- Sort vs
track_total_hits— when sorting, ES may stop early and reporttotalas a lower bound ("gte") unlesstrack_total_hits:true. - Geo/script sort cost —
_geo_distanceandscriptsorts compute per doc per shard; expensive on large result sets.