索引排序

索引排序配置指南 #

索引排序(Index Sorting)允许在索引阶段对文档按指定字段排序存储。这可以显著加速按排序字段的查询和聚合操作,但会增加索引写入开销。


工作原理 #

默认情况下,Lucene 按文档写入顺序存储。启用索引排序后,每个段(Segment)内的文档将按排序字段有序存储:

默认存储:   doc1 → doc2 → doc3 → doc4 → doc5
索引排序后: doc3 → doc1 → doc5 → doc2 → doc4  (按 timestamp 降序)

优势:

  • 按排序字段查询时可提前终止(Early Termination),大幅减少扫描量
  • 提高按排序字段做聚合的效率
  • 改善压缩率(相邻文档字段值更接近)

代价:

  • 索引写入速度降低 ~40%–50%(段合并时需要排序)
  • 索引大小可能增加
  • 排序字段只能在索引创建时指定,无法修改

配置参数 #

index.sort.field #

项目说明
参数index.sort.field
默认值无(不排序)
属性静态(仅在索引创建时设置)
说明排序字段列表。支持多字段排序。字段类型必须是 booleannumericdatekeyword

index.sort.order #

项目说明
参数index.sort.order
默认值asc
可选值ascdesc
属性静态
说明每个排序字段的排序方向。元素个数必须与 index.sort.field 一致

index.sort.mode #

项目说明
参数index.sort.mode
默认值min(asc 时)/ max(desc 时)
可选值minmax
属性静态
说明多值字段的取值模式。min 取最小值排序,max 取最大值排序

index.sort.missing #

项目说明
参数index.sort.missing
默认值_last(asc 时)/ _first(desc 时)
可选值_last_first
属性静态
说明缺失值的排列位置。_first 排在最前,_last 排在最后

使用示例 #

按时间戳降序排序 #

最常见的场景——日志/时序数据按时间降序存储,加速"最新数据"查询:

PUT /logs-2024
{
  "settings": {
    "index": {
      "sort.field": "timestamp",
      "sort.order": "desc",
      "number_of_shards": 3,
      "number_of_replicas": 1
    }
  },
  "mappings": {
    "properties": {
      "timestamp": {
        "type": "date"
      },
      "level": {
        "type": "keyword"
      },
      "message": {
        "type": "text"
      }
    }
  }
}

多字段排序 #

按租户 ID + 时间戳排序,适用于多租户场景:

PUT /multi-tenant-index
{
  "settings": {
    "index": {
      "sort.field": ["tenant_id", "timestamp"],
      "sort.order": ["asc", "desc"],
      "sort.missing": ["_last", "_first"]
    }
  },
  "mappings": {
    "properties": {
      "tenant_id": {
        "type": "keyword"
      },
      "timestamp": {
        "type": "date"
      }
    }
  }
}

提前终止(Early Termination) #

索引排序的核心收益是查询提前终止。当查询的 sort 顺序与索引排序一致时,Easysearch 可以在收集到足够文档后提前停止扫描:

# 索引按 timestamp desc 排序
# 查询也按 timestamp desc 排序 + size=10
# → 每个段只需读取前 10 个文档
GET /logs-2024/_search
{
  "size": 10,
  "sort": [
    { "timestamp": "desc" }
  ],
  "query": {
    "match_all": {}
  }
}

效果对比(示例):

场景无索引排序有索引排序
Top 10 最新日志扫描全部文档每段仅读 10 条
按时间范围 + Top N全段扫描快速跳过无关数据
聚合(按排序字段分桶)全量计算可利用有序性优化

⚠️ 提前终止仅在查询 sort 与索引 sort 完全匹配时生效。如果查询按其他字段排序,索引排序不会带来查询收益。


适用场景 #

✅ 推荐使用 #

场景排序配置理由
日志/时序数据timestamp desc大多数查询是"最新 N 条"
多租户tenant_id asc, timestamp desc按租户隔离查询
电商订单created_at desc最新订单优先
传感器数据device_id asc, timestamp desc按设备查最新数据

❌ 不推荐使用 #

场景理由
高写入吞吐索引排序增加 40–50% 写入开销
全文搜索为主查询不按固定字段排序,无法提前终止
排序字段频繁变化索引排序一旦设定无法修改
text 类型字段排序不支持

性能影响 #

写入性能 #

索引排序的主要开销在段合并时的排序操作:

指标无排序有排序影响
索引吞吐基准-40%~-50%显著下降
段合并时间基准+50%~+100%显著增加
索引大小基准+5%~-10%取决于数据(有序数据可能压缩更好)

查询性能 #

匹配排序方向时收益显著:

查询类型无排序有排序提升
Top N(排序匹配)全量扫描提前终止10x–100x
范围查询 + 排序全量扫描减少扫描2x–10x
不匹配的排序基准无收益
聚合(排序字段)基准有序优化2x–5x

注意事项 #

  1. 索引排序不可修改:排序字段和方向在索引创建时确定,无法通过 _settings API 修改
  2. Reindex 保留排序:对排序索引执行 reindex,目标索引需要重新配置排序
  3. 字段类型限制:仅支持 booleannumericdatekeyword 类型
  4. _source 无关:索引排序影响 Lucene 段内的存储顺序,_source 中的字段顺序不变
  5. 副本同步:主分片和副本分片的段排序一致

延伸阅读 #