Elasticsearch Mapping 设计:搜索质量与性能从建模开始

Elasticsearch 的查询性能和结果质量很大程度在写入前就被 Mapping 决定。动态映射虽然方便,但可能把标识符识别成数字、把日期误判、为大量随机字段创建 Mapping,最终引发字段爆炸和不可控查询。

text 与 keyword 分工

需要全文检索的标题、正文使用 text;用于精确过滤、聚合、排序的状态、标签和用户 ID 使用 keyword

{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "title": {
        "type": "text",
        "fields": {"raw": {"type": "keyword", "ignore_above": 256}}
      },
      "status": {"type": "keyword"},
      "publish_time": {"type": "date"}
    }
  }
}

不要对 text 直接做排序和聚合,也不要把文章正文同时保存大量无用子字段。keywordignore_above 可以避免异常超长值进入倒排结构,但原始 _source 仍可能保存它。

分词器必须用真实语料验证

中文搜索需要根据产品选择分词插件或自定义 Analyzer。先准备几十到几百条真实 Query 和期望结果,用 _analyze 检查分词,再决定同义词、停用词和 Edge N-gram。不要因为 Demo 搜到了一个词就直接上线。

索引时 Analyzer 与查询时 Search Analyzer 可以不同,例如写入做更细分词,查询保持较稳健。改 Analyzer 通常需要新建索引并 Reindex,不能把它当作随时可改的普通配置。

对象、Nested 与扁平字段

普通 Object 数组会被扁平化,字段之间的配对关系可能丢失。需要“同一个子对象同时满足多个条件”时使用 nested,但 Nested 会增加隐藏文档与查询成本。任意 Key-Value 元数据可评估 flattened,避免每个 Key 都成为新字段。

控制索引规模

  • 不参与搜索、聚合的字段可关闭索引。
  • 大正文如果只展示而不搜索,不必建立倒排。
  • 避免每个租户、每天都创建小索引,Shard 有固定成本。
  • Bulk 写入按响应大小和处理时间压测,过大批次会抬高内存峰值。
  • 初次批量导入可适当增大 Refresh Interval,完成后恢复。

查询技巧

精确过滤放在 filter 上下文,避免无意义评分并利于缓存;全文相关性放 must。深分页不要无限使用 from + size,大量结果遍历使用 search_after 和稳定排序键。聚合前确认字段基数,超高基数 Terms 聚合可能消耗大量内存。

零停机变更

使用版本化索引与 Alias:

  1. 创建 article-v2 新 Mapping。
  2. 从数据库或旧索引 Reindex。
  3. 对比文档数、抽样搜索和延迟。
  4. 原子切换读写 Alias。
  5. 保留旧索引一段回退窗口后删除。

参考资料