Elasticsearch Dump 失败问题排查:Store: True 导致的字段数组化问题

问题背景

在进行 Elasticsearch 数据迁移时,多个索引出现 dump 失败问题。查看日志发现单次 dump 报错:

复制代码
Error Emitted => {
  "error": {
    "root_cause": [{
      "type": "illegal_argument_exception",
      "reason": "Malformed action/metadata line [1], expected a simple value for field [timestamp] but found [START_ARRAY]"
    }],
    "type": "illegal_argument_exception",
    "reason": "Malformed action/metadata line [1], expected a simple value for field [timestamp] but found [START_ARRAY]"
  },
  "status": 400
}

错误信息显示:期望 timestamp 字段是单个值,但实际收到了数组(START_ARRAY)。

排查过程

第一阶段:检查配置和编码问题

首先怀疑是配置或编码问题,但检查后发现:

  • ✅ 配置文件格式正确
  • ✅ 编码没有问题
  • ✅ 网络连接正常

第二阶段:怀疑 timestamp 字段类型不匹配

错误信息指向 timestamp 字段,开始怀疑是字段类型不匹配问题。

发现的问题
  1. Mapping 配置timestamp 字段在 mapping 中定义为 long 类型
  2. 实际数据_sourcetimestamp 字段存在两种格式:
    • 字符串格式:"timestamp": "1508112000"
    • 数字格式:"timestamp": 1508112000
Elasticsearch 存储机制深入理解

在这个过程中,深入理解了 Elasticsearch 的存储机制:

1. _source 字段与 Mapping 的关系

关键理解_source 字段存储的是原始 JSON 文档完全不受 mapping 类型影响

json 复制代码
// 写入的原始文档
{
  "timestamp": "1508112000",  // 字符串
  "pmid": "12345"
}
json 复制代码
// ES 存储的 _source(完全一样)
{
  "_source": {
    "timestamp": "1508112000",  // 仍然是字符串!
    "pmid": "12345"
  }
}

重要点

  • _source 永远保持原始格式
  • ✅ Mapping 类型不会改变 _source 中的值
  • _source 用于:返回搜索结果、重新索引、更新文档
2. Mapping 类型的作用范围

Mapping 类型只影响索引层 (用于搜索、排序、聚合的字段),不影响 _source

json 复制代码
// Mapping 定义
{
  "mappings": {
    "properties": {
      "timestamp": {
        "type": "long"  // 只影响索引层
      }
    }
  }
}

行为

  • ES 会尝试将 _source.timestamp 的值(如 "1508112000")转换为 long 类型
  • 转换后的值存储在索引层(不可见,用于搜索)
  • _source 中的原始值保持不变
3. 类型转换机制

场景1: 字符串数字 → long(自动转换)

json 复制代码
// _source(原始)
{
  "timestamp": "1508112000"  // 字符串
}

// Mapping
{
  "timestamp": {
    "type": "long",
    "coerce": true  // 默认开启
  }
}

结果

  • _source.timestamp = "1508112000"(字符串,保持不变)
  • ✅ 索引层 timestamp = 1508112000(long,用于搜索)
  • ✅ 查询 timestamp: 1508112000 可以找到这个文档

场景2: 无法转换的字符串

json 复制代码
// _source(原始)
{
  "timestamp": "-1.0"  // 字符串,但包含小数
}

// Mapping
{
  "timestamp": {
    "type": "long"  // long 不支持小数
  }
}

结果

  • ❌ 转换失败("-1.0" 无法转换为整数)
  • ✅ 如果设置了 ignore_malformed: true
    • 文档可以写入
    • _source.timestamp = "-1.0"(保持不变)
    • 索引层 timestamp = null(无法搜索)
结论

经过分析,发现:

  • ✅ Mapping 是有效的:它控制索引层的行为
  • _source 保持原始格式是正常的:这是 ES 的设计
  • ✅ 字符串数字可以自动转换:ES 会在查询时自动转换
  • ❌ 但错误信息说的是数组,不是字符串或数字类型不匹配

第三阶段:尝试使用 Reindex API

由于 elasticdump 一直失败,尝试使用 ES 原生的 Reindex API:

json 复制代码
POST _reindex
{
  "source": {
    "remote": {
      "host": "https://source_host:9200"
    },
    "index": "source_index"
  },
  "dest": {
    "index": "target_index"
  }
}

遇到的问题

  • ❌ Reindex 需要配置白名单reindex.remote.whitelist
  • ❌ 白名单只能在 ES 配置文件中配置,无法动态修改
  • ❌ 需要重启 ES 集群才能生效
  • ❌ 由于无法修改 ES 配置,只能回到 elasticdump

第四阶段:关键发现

导出到本地文件检查

将 dump 改为先导出到本地 JSON 文件:

bash 复制代码
NODE_TLS_REJECT_UNAUTHORIZED=0 elasticdump \
  --input="https://user:pass@host:9200/index" \
  --output="./dump_sample.json" \
  --type=data \
  --limit=10
发现同名字段

检查导出的 JSON 文件,发现同一行数据中有两个同名的 timestamp 字段

json 复制代码
{
  "_source": {
    "timestamp": "1508112000",  // 第一个:正常值(字符串)
    "timestamp": [1508112000]   // 第二个:数组值!
    // ... 其他字段
  }
}
问题根源:Store: True

经过分析,发现问题根源是 store: true 配置

Store: True 的行为

当字段设置了 store: true 时:

  1. ✅ 字段值会被单独存储 一份(除了 _source
  2. ✅ 可以使用 stored_fields API 获取
  3. ⚠️ 返回格式总是数组,即使只有一个值
为什么会出现数组?

Elasticsearch 的 stored_fields API 设计

  • 为了支持多值字段(multi-valued fields)
  • 为了保持一致性,即使单值字段也返回数组格式
  • 例如:stored_fields.timestamp 总是返回 [1508112000] 而不是 1508112000

Elasticdump 的行为

  • 可能同时读取 _sourcestored_fields
  • 合并数据时,将 stored_fields 的数组值覆盖或合并到文档中
  • 导致最终的 timestamp 变成数组 [1508112000]

写入目标索引时

  • 目标索引的 mapping 期望 timestamp 是单个值(long 类型)
  • 但实际收到的是数组 [1508112000]
  • Bulk API 在解析阶段就失败:expected a simple value but found [START_ARRAY]
影响范围

所有设置了 store: true 的字段都可能遇到这个问题,包括:

  • timestamp
  • lat_lon
  • 所有动态模板中设置了 store: true 的字段(如 *_int, *_long, *_kwd 等)

解决方案

方案1: 使用 searchBody 参数(推荐)

在 elasticdump 命令中添加 --searchBody 参数,明确指定只使用 _source

bash 复制代码
NODE_TLS_REJECT_UNAUTHORIZED=0 elasticdump \
  --input="https://user:pass@host:9200/source_index" \
  --output="https://user:pass@host:9200/target_index" \
  --type=data \
  --limit=100 \
  --maxSockets=5 \
  --timeout=600000 \
  --retryAttempts=5 \
  --scrollTime=10m \
  --ignore-errors \
  --quiet \
  --searchBody='{"_source": true}'

优点

  • 一次性解决所有字段的问题
  • ✅ 不需要修改 mapping
  • ✅ 不需要重新创建索引
  • ✅ 明确告诉 ES 只返回 _source,不返回任何 stored_fields

工作原理

  • 明确指定只使用 _source
  • ES 不会返回 stored_fields
  • elasticdump 只读取 _source 中的数据
  • 所有字段(无论是否设置了 store: true)都从 _source 读取,格式正常

方案2: 移除 Store: True(不推荐)

如果移除所有 store: true

  • ❌ 需要修改 mapping
  • ❌ 需要重新创建索引(已存在的索引无法直接修改 store 属性)
  • ❌ 如果业务需要 stored_fields,会影响功能

方案3: 使用中间文件处理(备选)

如果 searchBody 不工作,可以先导出到文件,然后处理:

bash 复制代码
# 1. 导出到文件
elasticdump --input="..." --output="./dump.json" --searchBody='{"_source": true}'

# 2. 使用脚本处理数组(如果需要)
python3 fix_timestamp.py dump.json dump_fixed.json

# 3. 导入到目标索引
elasticdump --input="./dump_fixed.json" --output="..." --type=data

技术要点总结

1. Elasticsearch 存储机制

  • _source 字段:存储原始 JSON,不受 mapping 影响
  • 索引层:根据 mapping 类型索引,用于搜索
  • stored_fields:单独存储的字段值,返回格式为数组

2. Store: True 的影响

  • ✅ 字段值被单独存储,可以通过 stored_fields API 获取
  • ⚠️ stored_fields API 总是返回数组格式
  • ⚠️ 在 dump 时可能导致字段数组化问题

3. Dump 工具的选择

  • Elasticdump :灵活,支持各种参数,但需要注意 stored_fields 问题
  • Reindex API:原生支持,但需要配置白名单,不够灵活

4. 最佳实践

  • ✅ 在 dump 时使用 --searchBody='{"_source": true}' 明确指定只读取 _source
  • ✅ 可以继续使用 store: true(如果业务需要),在 dump 时使用正确的参数即可
  • ✅ 不需要为了 dump 而改变 mapping 设计

经验教训

  1. 深入理解 ES 存储机制_source 和索引层是分离的,mapping 不影响 _source
  2. 仔细分析错误信息:错误说"数组"而不是"类型不匹配",这是关键线索
  3. 导出到本地文件检查:这是定位问题的关键步骤
  4. 理解工具的行为 :elasticdump 可能同时读取 _sourcestored_fields
  5. 使用正确的参数--searchBody 参数可以解决所有 store: true 字段的问题
相关推荐
石小千2 小时前
配置Jenkins使用tag发布
jenkins
数峦云数字孪生三维可视化2 小时前
数字孪生沙盘——亚运智力场馆之杭州棋院(智力大厦)
大数据·人工智能·物联网·数字孪生·三维可视化
可以吧可以吧2 小时前
前端vue jenkins打包资源增加阿里云oss+cdn加速
前端·vue.js·jenkins
启芯硬件2 小时前
电源XL6009E1的dieshot细节分析-芯片设计干货
大数据·经验分享·硬件工程·1024程序员节
跨境海外仓小秋2 小时前
东南亚海外仓费用计算指南,精准计费避坑攻略
大数据·人工智能
Hello.Reader2 小时前
Flink ML LinearRegression 用 Table API 训练线性回归并输出预测值
大数据·flink·线性回归
曹牧2 小时前
Java:Jenkins
java·开发语言·jenkins
菩提祖师_2 小时前
基于大数据背景下智能手机营销对策研究
大数据·智能手机·软件工程
武子康2 小时前
Java-218 RocketMQ Java API 实战:同步/异步 Producer 与 Pull/Push Consumer
java·大数据·分布式·消息队列·rocketmq·java-rocketmq·mq