# Index sorting on low cardinality fields

**URL:** <https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047>\
**Category:** Elasticsearch\
**Created:** [September 18, 2025, 8:14am UTC](https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047 "2025-09-18T08:14:27Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![spinscale](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/spinscale/32/25011_2.png) [@spinscale](https://discuss.elastic.co/u/spinscale)\
**Post date:** [September 18, 2025, 8:14am UTC](https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047/1 "2025-09-18T08:14:27Z")

</div>

Hey,

I have been playing around with index sorting and cannot make sense of my results. Basically I have a keyword field named `is_existing` that is either `true` or `false` as possible values, or may not be set at all. Basically every search has a `is_existing: true` filter.

My assumption here was that by using index sorting on this field, I would be able to speed up all those searches, because they are touching less than 40% of the data, meaning we could basically skip 60% of the data with every search.

What I am seeing however after reindexing the data with index sorting enabled is, that searches are actually slower than without index sorting (i.e. a 70ms search now takes 85ms).

I have already played around with the sort order and putting missing fields first/last and there does not seem to be a difference. This is on Elasticsearch 8.18.

What am I missing here that could cause this?

Thanks for any pointers.

–Alex

---

<div class="post-metadata">

**Author:** ![john-wagster](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/john-wagster/32/139021_2.png) [@john-wagster](https://discuss.elastic.co/u/john-wagster)\
**Post date:** [September 18, 2025, 1:30pm UTC](https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047/2 "2025-09-18T13:30:36Z")

</div>

@spinscale that’s interesting. Can you share an example of the mapping and query that you are seeing this behavior on. I can probably take a look at it and give you some suggestions or dig into the code a bit at the very least to better explain what’s going on.

---

<div class="post-metadata">

**Author:** ![spinscale](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/spinscale/32/25011_2.png) [@spinscale](https://discuss.elastic.co/u/spinscale)\
**Post date:** [September 19, 2025, 9:18am UTC](https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047/3 "2025-09-19T09:18:21Z")

</div>

Hey John,

thanks for replying!

Unfortunately I cannot share a minimal example, because this runs against a big live production dataset and the query itself is pretty complex.

Fast variant:

No index sorting. `is_existing` is of type `keyword` and contains only `true` or `false` or nothing as possible values.

Slower variant: Same mapping as above, but index sorting is configured like this:

```auto
index": {
      "sort.field": "is_existing",
      "sort.order": "desc",
      "sort.missing" : "_last"
}

```

I also tested when changing the sort.order to `asc` with no difference. My base assumption here was that `true` should be stored before `false` and before missing, so that Lucene only has to scan the first part of a segment.

Hope that helps!

–Alex

---

<div class="post-metadata">

**Author:** ![RainTown](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/raintown/32/140206_2.png) [@RainTown](https://discuss.elastic.co/u/RainTown)\
**Post date:** [September 19, 2025, 1:42pm UTC](https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047/4 "2025-09-19T13:42:28Z")

</div>

In such a scenario I might consider putting the data into different indices depending on value of that field. If, as you state, the queries are almost always for a specific value.

---

<div class="post-metadata">

**Author:** ![john-wagster](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/john-wagster/32/139021_2.png) [@john-wagster](https://discuss.elastic.co/u/john-wagster)\
**Post date:** [September 19, 2025, 2:46pm UTC](https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047/5 "2025-09-19T14:46:24Z")

</div>

> [@spinscale](#):
>
> is\_existing

I think your assumptions are good about how this would work.

Docs would agree as well: [Index sorting settings | Reference](https://www.elastic.co/docs/reference/elasticsearch/index-settings/sorting)

What I was hoping to get out of an example was to validate a couple of things. Sorting will only show a performance improvement when both the index and the query are sorted. It’s not a general purpose filtering mechanism.

So a modified example from the docs is something like this:

```auto
PUT events
{
  "settings": {
    "index": {
      "sort.field": "is_existing",
      "sort.order": "desc"
    }
  },
  "mappings": {
    "properties": {
      "timestamp": {
        "type": "keyword"
      }
    }
  }
}

```

AND

```auto
GET /events/_search
{
  "size": 10,
  "sort": [
    { "is_existing": "desc" }
  ],
  "track_total_hits": false
}

```

Are you setting up the search request appropriately? Note that `track_total_hits` set to `false` is necessary so we aren’t looking to count everything that matches outside of the first 10 in this example.

Also just because it pings in my head. Have you considered using a `boolean` field there instead of a `keyword` it wouldn’t surprise me if that’s much more efficient (but to be fair that’s a completely separate optimization).

---

<div class="post-metadata">

**Author:** ![spinscale](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/spinscale/32/25011_2.png) [@spinscale](https://discuss.elastic.co/u/spinscale)\
**Post date:** [September 23, 2025, 10:02am UTC](https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047/6 "2025-09-23T10:02:28Z")

</div>

Hey John,

you figured out where my assumptions are broken, thanks so much!

My assumption was, that with index sorting and the ability to look this mapping configuration up at query time, index sorting was automatically enabled, regardless of sorting - as I thought this would do always result an early termination and thus is always enabled.

I’ll try to add that sorting before the score and see what happens, might take a couple of days until I get to that, but will report back!

Thanks for all the time invested and explanations, much appreciated!

–Alex

---

<div class="post-metadata">

**Author:** ![Victor-Lee](https://avatars.discourse-cdn.com/v4/letter/v/d2c977/32.png) [@Victor-Lee](https://discuss.elastic.co/u/Victor-Lee)\
**Post date:** [September 23, 2025, 1:34pm UTC](https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047/7 "2025-09-23T13:34:57Z")

</div>

Index sorting won’t always speed things up for a true/false field. Since it’s low variety, the extra sorting work can actually slow searches a bit. You might get better results by relying on the filter cache or checking your mappings instead.

---

<div class="post-metadata">

**Author:** ![john-wagster](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/john-wagster/32/139021_2.png) [@john-wagster](https://discuss.elastic.co/u/john-wagster)\
**Post date:** [September 23, 2025, 1:46pm UTC](https://discuss.elastic.co/t/index-sorting-on-low-cardinality-fields/382047/8 "2025-09-23T13:46:12Z")

</div>

> I’ll try to add that sorting before the score and see what happens, might take a couple of days until I get to that, but will report back!

Awesome! I’ll keep an eye out and looking forward to seeing how it goes.
