4. 索引与映射迁移策略

索引与映射迁移是 Elastic 平台升级或跨集群搬迁中最容易“卡住”的环节。数据本身可以复制,但映射一旦定义错误,写入、聚合、排序都会连锁失败。本节围绕四类典型问题展开:Mapping 变更、字段类型冲突、多类型索引拆分,以及 Reindex API 与远程 Reindex 的实战用法。

一、Mapping 变更:为什么不能直接改字段类型

Elasticsearch 的字段映射在索引创建后大部分是不可变的。例如把一个字段从 text 改成 keyword,或从 integer 改成 long,直接调用 PUT /index/_mapping 会报错。原因是底层 Lucene 的倒排索引、doc_values 和列式存储结构已经按旧类型构建,无法原地重写。

因此 Mapping 变更的正确路径是“新建索引 + 重新索引 + 别名切换”。常见可安全新增的内容包括:新增字段、为已有字段增加 multi-field(如 keyword 子字段)、调整 ignore_above、dynamic 等参数。而修改字段类型、分词器、doc_values 开关,则必须走重建流程。

迁移原则:先冻结写入 → 用新 Mapping 建目标索引 → Reindex 回填 → 校验文档数与抽样字段 → 原子切换别名 → 解冻写入。

二、字段类型冲突:迁移中最常见的“隐形炸弹”

类型冲突通常出现在两种场景:一是同一字段在不同索引中类型不一致,二是目标索引已有 Mapping 与源数据推断类型不符。例如源索引中 price 是 float,目标模板却定义为 keyword,Reindex 时该文档会失败并进入错误队列。

处理冲突前,先做一次全量类型盘点。可以用如下方式快速比对源与目标的字段类型:

GET /source_index/_mapping
GET /target_index/_mapping

# 对比字段类型差异(示例思路)
# 源: price -> float
# 目标: price -> keyword  → 冲突,需要统一

冲突解决策略有三种:统一为目标类型(推荐,需确认业务语义)、使用 ingest pipeline 做类型转换(如 convert 处理器把字符串转数值)、保留为多字段(同时提供 text 与 keyword)。若源数据本身脏,例如数值字段混入 "N/A",应在 pipeline 中用 convert 的 ignore_missing 与 on_failure 兜底,避免整批失败。

三、多类型索引拆分

Elasticsearch 6.x 之前允许一个索引下多个 type,7.x 起彻底移除。迁移旧集群时,常见一个索引里混有 user、order、product 多种 type。正确做法是按 type 拆成独立索引,例如 legacy_user、legacy_order、legacy_product,再分别定义各自的 Mapping。

拆分时要注意:不同 type 的字段同名但类型不同,必须分别建模;文档 _id 可能重复,需加前缀或使用 _id 重写规则;跨 type 的父子关系要改为 join 字段或独立索引 + 应用层关联。拆分后建议用别名统一对外,例如 user 别名指向 legacy_user,减少应用改动。

四、Reindex API 与远程 Reindex

Reindex 是把源索引文档复制到目标索引的核心工具。基本用法:

POST _reindex?wait_for_completion=false
{
  "source": {
    "index": "legacy_order",
    "_source": ["order_id", "amount", "created_at"]
  },
  "dest": {
    "index": "order_v2",
    "pipeline": "order_convert_pipeline"
  },
  "conflicts": "proceed"
}

关键参数说明:wait_for_completion=false 返回 task ID,适合大数据量异步执行;_source 过滤只迁移必要字段,降低网络与存储开销;pipeline 在写入前做类型转换与字段清洗;conflicts: proceed 让版本冲突不中断任务,但需后续核对。

远程 Reindex 用于跨集群迁移,需在目标集群的 elasticsearch.yml 中配置白名单:

reindex.remote.whitelist: "source-host:9200"

然后调用:

POST _reindex?wait_for_completion=false
{
  "source": {
    "remote": {
      "host": "http://source-host:9200",
      "username": "reindex_user",
      "password": "******"
    },
    "index": "legacy_user",
    "size": 1000
  },
  "dest": { "index": "user_v2" }
}

远程 Reindex 的 size 控制每批拉取量,默认 1000,网络不稳定时可调小。注意远程集群版本不能高于目标集群,且需保证源集群有足够读权限。

索引与映射迁移核心路径 源集群 / 旧索引 legacy_user (type: user) legacy_order (type: order) legacy_product (type: product) 类型冲突检测 • price: float vs keyword • amount: 混入 "N/A" • 多 type 字段同名异型 • _id 跨 type 重复 ingest pipeline 转换 目标集群 / 新索引 user_v2 order_v2 product_v2 别名: user / order 统一 Mapping + 别名切换 Reindex 执行链路 冻结写入 源索引只读 Reindex 异步 task 校验 文档数/抽样 别名切换 原子操作 解冻 恢复写入 远程 Reindex 需配置 reindex.remote.whitelist,并控制 size 批次大小

五、实战校验与判断依据

迁移完成后不能只看“任务成功”。建议核对四项:文档总数(源与目标 _count 一致)、抽样字段值(随机取 10 条比对关键字段)、聚合结果(对数值字段做 sum/avg 对比)、错误队列(查看 _tasks 返回的 failures)。若失败数不为 0,需按 _index、_id 定位并重跑。

远程 Reindex 不支持跨大版本(如 5.x → 8.x)直接迁移,需先升级到中间版本或使用 Logstash / snapshot restore 过渡。同时注意源集群的 _source 必须开启,否则无法重建。

最后,别名切换是迁移的“最后一公里”。建议用 _aliases 原子操作,把读写别名一次性指向新索引,避免应用侧出现双写或读空窗。切换后保留旧索引至少一个版本周期,确认无回滚需求后再删除。