Elasticsearch:由于映射冲突而重新索引数据流
作者来自 Elastic Lisa Larribas了解如何通过重新索引数据流来修复 Elasticsearch 映射冲突。本博客解释了重新索引过程以及如何确保新数据被正确映射。如果你是 Elasticsearch 新手加入我们的 Elasticsearch 入门网络研讨会。你也可以现在开始免费的云试用或者在你的本地机器上试用 Elastic。当字段中出现映射冲突时无论这些字段是 Elastic Common Schema – 标准ECS 标准还是特定于数据源使用 Dev Tools 对数据进行重新索引就变得很有必要。这些冲突可能会对摄取之后的任何下游功能产生负面影响可能导致结果不准确或者阻止在可视化、仪表板、Security 应用以及聚合等功能中使用完整的数据集。本博客详细说明了该重新索引过程的步骤。本博客内容基于 Elastic 版本 9.2.8 和 8.19.14以及 Filestream Integration 版本 2.3.0 和 1.2.0 开发并验证。重要说明根据你的环境某些步骤可能需要进行特定修改。此外请注意从 Filestream Integration 版本 2.3.3 开始动态模板已从 package 组件模板中移除。在开始重新索引过程之前考虑你当前环境中的存储分配非常重要。下面概述的步骤涉及创建现有 backing index 的副本该副本将临时驻留在 hot 层。Elasticsearch 数据分层HotHot 层是时间序列数据进入 Elasticsearch 的入口存储最新、最常被搜索的数据。Hot 层节点需要快速读写因此需要更多资源和更快的存储SSD。该层是必需的新数据流索引会自动分配到这里。Warm当时间序列数据的查询频率低于最近在 hot 层中索引的数据时可以移动到 warm 层。Warm 层通常存储最近几周的数据。仍然允许更新但通常不频繁。Warm 层节点通常不需要像 hot 层那样快。为了提高弹性warm 层中的索引应配置一个或多个副本。Cold不常被搜索的数据可以从 warm 层移动到 cold 层。Cold 层仍然可搜索但更侧重于降低存储成本而不是搜索速度。或者cold 层可以存储带副本的常规索引而不是可搜索快照从而在不减少磁盘空间需求的情况下使用更便宜的硬件存储较旧的数据。Frozen很少被查询或不再被查询的数据会从 cold 层移动到 frozen 层直到生命周期结束。该层使用快照仓库和部分挂载的索引来存储和加载数据从而减少本地存储和成本同时仍然允许搜索。由于 Elasticsearch 可能需要从快照仓库获取冻结数据因此在 frozen 层上的搜索通常比 cold 层更慢。我们建议使用专用的 frozen 层节点。更多有关数据分层的知识请详细阅读文章 “ElasticsearchSearchable snapshot - 可搜索的快照”。前提条件确定哪些字段存在冲突要确定哪些字段存在映射冲突请导航到Stack Management-Data Views-logs-使用 logs-数据视图可以查看所有以 logs- 前缀开头的最高层级数据。如果存在冲突会显示一个黄色提示框。你可以点击 “View conflicts”或者在搜索框旁边的Field type下拉框中选择 “conflict”。点击黄色的Conflict按钮将显示哪些索引关联了哪些映射类型。这种情况字段同时被映射为 keyword 和 long通常是因为在为相关数据流的组件模板定义特定映射类型之前数据就已经被摄取。在这种情况下Elasticsearch 会尝试根据其动态模板来设置映射。为了确定该字段应使用哪种映射类型以及该字段是否属于 ECS 字段需要参考 ECS 字段参考文档进行验证。如果该字段不是 ECS 字段则需要检查其值以确定正确的映射类型。如果某个字段例如本例中的 log.offset没有在 ECS 中记录接下来的步骤是分析该字段的值确定哪种冲突的映射类型对应的 backing index 数量最多并检查其他索引的组件模板。通常与最多索引关联的映射类型是正确的但我们仍建议你验证该字段的实际值以确认这一点。要确认某种映射类型例如 long是否有效还必须验证该字段的值是否符合该类型的要求。可以通过使用Discover搜索该字段来完成此验证。同时查看包含相同字段的其他数据流也可以提供额外的确认。要查看存在映射问题字段的取值请返回之前提到的黄色Conflict按钮点击该按钮选中其中一个 backing index并将其粘贴到Discover会话中。你的 Kibana Query LanguageKQL语句应类似如下截图所示包含 _index: 字段限定符。准备新的 backing index 自定义组件模板为了解决数据流中的映射冲突首先检查相关的 package 组件模板。你可以在Stack Management-Index Management-Component Template中找到它。搜索对应的数据流并选择相应的 package 链接。该模板开箱即包含字段映射虽然映射不匹配的情况并不常见但仍有可能忽略更合适的类型。检查模板以确认其中包含目标字段所需的字段嵌套和映射。例如如果模板错误地将 log.offset 定义为 keyword这就是问题的根源。重要说明由于不建议修改 package/managed 模板你必须使用或创建一个 custom 组件模板来修正映射类型例如 log.offset以确保未来的数据使用正确的映射。我们不建议修改 package/managed 模板因为当你将集成升级到较新版本时你对 package 模板所做的任何更改都会被覆盖。这也是我们推荐使用 custom 模板的原因。如果数据流存在映射冲突你需要在该数据流的 custom 组件模板中添加所有缺失的字段包括 ECS 和非 ECS的嵌套或映射。如果该模板尚未创建请先创建并确保为相关字段指定正确的映射类型。如果你的数据视图中存在多个冲突建议一次性为数据流补齐所有必要的映射这样只需执行一次 reindex而不是多次执行。在 custom 组件模板中正确配置数据类型将确保未来的数据摄取遵循相同的映射规范。要创建 custom 组件模板或验证其已被使用且已配置请导航到 Index Templates输入目标数据流的名称然后点击该数据流正在使用的 custom 模板。如果模板尚未创建会出现一个黄色提示框允许你通过 UI 创建该模板。下面的截图显示了选择Create component template后进入的下一页。在第一页保持默认设置不变点击 “Mappings” 或 “Next”直到进入 Mappings 页面。为了显式为新进入的字段设置映射或者更新存在映射冲突的字段当由于 index lifecycle policy 中的配置导致数据流发生 rollover 时需要为发生冲突的字段添加一条映射配置。下面的操作将会在 filestream 数据流的 custom 组件模板中为 log.offset 字段设置映射。如果需要可以重复这些步骤为该数据集添加其他自定义字段或使用适当的映射类型更新 package 中需要调整的字段。在本例中当将 offset 设置为 Long 时字段类型应选择 Numeric而 Numeric 的具体类型选择 Long。点击 “Add field”然后点击该区域外部以继续操作。当所有需要的字段都添加完成后点击继续进入 review 页面进行检查确认无误后选择 “Create component template” 以完成创建。从此步骤之后所有新摄取的数据中log.offset 将被设置为 long 类型。创建新的 backing index 结构新的 backing index 需要包含数据流组件模板中的现有映射以及 ECS 的 ecsmappings 组件模板。ecsmappings 组件模板会在数据流组件之后应用作为“兜底catchall”机制用于补充之前组件模板可能未覆盖到的其他字段映射。请打开数据流的 package mappings 对应浏览器标签页。路径Stack Management-Index Management-Component Template- logs-filestream.genericpackage -Manage-Edit。进入后点击 Review 部分然后选择 Request最后点击右侧的 Copy 按钮。复制得到的组件模板 JSON 内容将确保在更新 log.offset 字段映射的同时其余字段映射和设置得以保留。该 JSON 将作为新 reindex 后 backing index 的基础结构。重要提示如果没有复制模板 JSON 就继续进行 reindex 操作虽然 log.offset 冲突会被解决但由于未保持当前映射的完整性可能会与集成产生新的冲突从而导致需要重复处理原本的问题增加额外工作量。打开第二个浏览器标签页进入 Dev Tools并粘贴刚刚复制的内容。现在需要对粘贴内容进行清理与调整对请求的修改1. 索引名称将_component_template/logs-filestream.genericpackage替换为你计划重新索引的 backing index 名称并在末尾加上-1。例如PUT 待重新索引的 backing index-1。这里的-1表示这是一个 reindex 操作不会与默认基于创建时间的 ILM rollover 设置冲突。2. Settings删除template第 3 行以及整个 JSON payload 最后的闭合大括号。第 3 行应从settings: {开始。将settings内部内容替换为index.codec: best_compression应用最佳压缩index.lifecycle.name: logs应用 logs ILM 策略如果你使用不同策略请相应修改index.lifecycle.rollover_alias: 留空但必须保留该字段否则 ILM 在 hot 之后阶段可能报错3. 结构调整当前请求应包含两个主要部分settingsmappings在mappings: {中应包含dynamic_templatesproperties包含字段及其映射4. dynamic_templates 修改当前 dynamic templates 中包含一些与后续 ecsmappings 重复的字段这会造成冗余。需要删除dynamic_templates中除第二部分_embedded_ecs-data_stream_to_constant之外的所有内容然后按上述方式将 ecsmappings component template 中的 dynamic mappings 一并加入建议直接从 UI 复制 ecsmappings 的完整 mappings 内容然后粘贴到 Dev Tools 的 dynamic_templates 中再去重整理最终dynamic_templates应保留_embedded_ecs-data_stream_to_constant并在其后追加整理后的 ecsmappings 内容。⚠️重要说明如果不移除或正确整理 dynamic_templates会导致字段出现重复映射例如 text keyword 同时存在从而造成新的 mapping 冲突并在 data view 中引发字段解析问题。最终剩余结构应主要为mappings下的properties部分。5.元数据删除删除最后一个名为 “_meta” 的部分以及名为 “version” 的部分如果存在。6.格式化对剩余部分进行自动缩进并调整或删除任何不必要的花括号以避免执行失败。7. 映射更改导航到 “properties” 部分找到 “log”然后定位其下的 “offset”。将类型从 keyword 更改为 long并删除标记为 “ignore_above”: 1024, 的行包括逗号。如果之前创建的 custom 组件模板中添加了多个条目也需要在此处一并包含。你的 Dev Tools 控制台视图现在应类似于下方提供的示例。PUT .ds-logs-filestream.generic-default-2026.04.14-000001-1 { settings: { index.codec: best_compression, index.lifecycle.name: logs, index.lifecycle.rollover_alias: }, mappings: { dynamic_templates: [ { _embedded_ecs-data_stream_to_constant: { path_match: data_stream.*, mapping: { type: constant_keyword } } }, { ecs_timestamp: { mapping: { ignore_malformed: false, type: date }, match: timestamp } }, { ecs_message_match_only_text: { path_match: [ message, *.message ], mapping: { type: match_only_text }, unmatch_mapping_type: object } }, { ecs_non_indexed_keyword: { path_match: [ *event.original ], mapping: { index: false, type: keyword, doc_values: false } } }, { ecs_non_indexed_long: { path_match: [ *.x509.public_key_exponent ], mapping: { index: false, type: long, doc_values: false } } }, { ecs_ip: { path_match: [ ip, *.ip, *_ip ], mapping: { type: ip }, match_mapping_type: string } }, { ecs_wildcard: { path_match: [ *.io.text, *.message_id, *registry.data.strings, *url.path ], mapping: { type: wildcard }, unmatch_mapping_type: object } }, { ecs_path_match_wildcard_and_match_only_text: { path_match: [ *.body.content, *url.full, *url.original ], mapping: { fields: { text: { type: match_only_text } }, type: wildcard }, unmatch_mapping_type: object } }, { ecs_match_wildcard_and_match_only_text: { mapping: { fields: { text: { type: match_only_text } }, type: wildcard }, unmatch_mapping_type: object, match: [ *command_line, *stack_trace ] } }, { ecs_path_match_keyword_and_match_only_text: { path_match: [ *.title, *.executable, *.name, *.working_directory, *.full_name, *file.path, *file.target_path, *os.full, *email.subject, *vulnerability.description, *user_agent.original ], mapping: { fields: { text: { type: match_only_text } }, type: keyword }, unmatch_mapping_type: object } }, { ecs_date: { path_match: [ *.timestamp, *_timestamp, *.not_after, *.not_before, *.accessed, created, *.created, *.installed, *.creation_date, *.ctime, *.mtime, ingested, *.ingested, *.start, *.end, *.indicator.first_seen, *.indicator.last_seen, *.indicator.modified_at, *threat.enrichments.matched.occurred ], mapping: { type: date }, unmatch_mapping_type: object } }, { ecs_path_match_float: { path_match: [ *.score.*, *_score* ], mapping: { type: float }, path_unmatch: *.version, unmatch_mapping_type: object } }, { ecs_usage_double_scaled_float: { path_match: *.usage, mapping: { scaling_factor: 1000, type: scaled_float }, match_mapping_type: [ double, long, string ] } }, { ecs_geo_point: { path_match: [ *.geo.location ], mapping: { type: geo_point } } }, { ecs_flattened: { path_match: [ *structured_data, *exports, *imports ], mapping: { type: flattened }, match_mapping_type: object } }, { all_strings_to_keywords: { mapping: { ignore_above: 1024, type: keyword }, match_mapping_type: string } } ], properties: { input: { properties: { type: { ignore_above: 1024, type: keyword } } }, timestamp: { ignore_malformed: false, type: date }, ecs: { properties: { version: { ignore_above: 1024, type: keyword } } }, log: { properties: { file: { properties: { inode: { ignore_above: 1024, type: keyword }, path: { ignore_above: 1024, type: keyword }, device_id: { ignore_above: 1024, type: keyword }, fingerprint: { index: false, type: keyword } } }, offset: { type: long }, level: { ignore_above: 1024, type: keyword } } }, data_stream: { properties: { namespace: { type: constant_keyword }, type: { type: constant_keyword }, dataset: { type: constant_keyword } } }, event: { properties: { original: { index: false, type: keyword, doc_values: false }, module: { type: constant_keyword, value: filestream }, dataset: { type: constant_keyword, value: filestream.generic } } }, message: { type: match_only_text }, tags: { ignore_above: 1024, type: keyword } } } }当你的控制台视图与示例一致并已包含任何额外的自定义字段以及你环境中的自定义值后执行命令以创建新 backing index 的“空壳”并在过程中暂停处理出现的任何错误。开始 reindex 过程当新 backing index 的空壳成功创建后下一步是执行 reindex 并解决映射冲突。⚠️重要说明如果存在映射冲突的 backing index 是最新的索引并且它是当前的写入索引例如 backing index 结尾为-000001则需要对数据流执行 rollover。这是因为当前写入索引正在接收实时数据属于 live backing index无法直接修改。在通过之前创建的 custom 组件模板将正确字段映射应用到新的写入索引之后所有新写入的文档都会自动使用新的映射配置。接下来通过执行以下操作完成该步骤POST full data stream name/_rollover例如POST logs-filestream.generic-default/_rollover重新索引Reindexing是指在相同命名规范下将数据从现有的 backing index 复制到一个新的 index 中通常用于应用必要的变更。这些修改可能包括更新 component template或为数据添加新的 ingest pipeline 以进行处理。接下来数据将从存在映射错误的 backing index 复制到一个新的 backing index 中。原始 backing index 已经完成 rollover因此不会再接收新的文档。新的 backing index 将遵循相同的命名规范以保证数据可见性和完整性并应用正确的 ILM 策略但会增加-1后缀以表示它已被重新索引。请根据需要调整 index 名称并将以下代码粘贴到 console 中。通过设置wait_for_completionfalse你可以跟踪文档复制进度从而估算剩余 reindex 时间。如果不使用该设置则无法通过GET _tasks命令查看任务状态只能通过GET backing index name-1/_count查看新 backing index 中的文档数量。重要说明如果 reindex 过程中出现问题不要重新运行 reindex 命令因为这样会重新开始整个过程并在以-1结尾的 index 中产生重复数据。如果需要重试应先删除带-1后缀的 index然后重新执行之前的 PUT 命令以重新创建新的 backing index 空壳。POST _reindex?wait_for_completionfalse { source: { index: source backing index }, dest: { index: new backing index-1 } } i.e. POST _reindex?wait_for_completionfalse { source: { index: .ds-logs-filestream.generic-default-2026.04.13-000001 }, dest: { index: .ds-logs-filestream.generic-default-2026.04.13-000001-1 } }执行后响应中会包含一个 task ID。你可以使用该 ID通过以下命令监控 reindex 进度GET _tasks/。reindex 的持续时间取决于原始 index 中的数据量。可以通过执行 GET 命令来跟踪完成情况当看到 “completed”: true 时表示任务已完成其输出应类似如下所示。随着 reindex 过程中文档数量部分已完成下一步是验证新 backing index 以及相关字段的映射是否正确。GET backing index-1/_mapping例如GET .ds-logs-filestream.generic-default-2026.04.13-000001-1/_mapping你可以验证 log.offset 的映射如下所示。为了确认其他字段只有单一映射条目而不是同时存在 text 和 keyword可以将它们与之前 PUT 命令中未包含在 dynamic template 部分的字段进行对比。如果正在重新索引的 backing index 包含大量文档检查这些文档复制到新 backing index 的状态会很有帮助可以通过以下两个 Dev Tools 命令来对比文档数量。一旦确认文档数量一致且正确的映射已存在就需要更新数据流以包含新的 backing index避免在 index management 中出现孤立的 backing index否则 ILM 策略将不会在该 backing index 上执行。如果操作成功返回结果应为 true 的确认响应。POST _data_stream/_modify { actions: [ { add_backing_index: { data_stream: logs-filestream.generic-default, index: .ds-logs-filestream.generic-default-2026.04.14-000001-1 } } ] }使用以下命令验证新的 backing index 是否已被正确添加并确保 ilm_policy 配置正确GET _data_stream/logs-filestream.generic-default接下来使用以下命令检查该 backing index 的 ILM 状态通常情况下看到该 index 处于 hot 阶段是正常的因为它是刚刚创建的请参考第 8 行或第 10 行。GET .ds-logs-filestream.generic-default-2026.04.14-000001-1/_ilm/explain执行以下操作将 backing index 从 hot 层迁移到该数据流 ILM 策略中 hot 阶段之后的下一个合适层级。current_step中的 phase、action 和 name 的具体值可以分别参考上方截图中的第 11、13、15 行。next_step的值表示该 index 将要进入的下一个 ILM 阶段或数据分层。例如POST _ilm/move/.ds-logs-filestream.generic-default-2026.04.14-000001-1 { current_step: { phase: hot, action: rollover, name: check-rollover-ready }, next_step: { phase: warm } }虽然不是必须步骤但作为安全检查你可以再次执行_ilm/explain命令以确认该 backing index 已经进入下一个阶段并且不再处于 hot 状态。当满足以下条件时你可以安全删除最初存在映射冲突的 backing index已成功创建新的 backing index文档已迁移到新 index并且文档数量一致映射已修正包括数据流特定映射和 ECS 映射数据流已包含新的 backing indexILM 策略已生效并且 index 已从 hot 阶段迁移出去重要说明或者在删除原始 index 之前你可以检查Data Views页面。选择 logs-*并确认重新索引后的 backing index以 -1 结尾已出现在 long 类型部分。而原始 backing index 应仍然出现在 keyword 类型下。如果 reindexed 的 backing index 未出现在 long 部分请返回前面的步骤并进行必要的修正。例如DELETE .ds-logs-filestream.generic-default-2026.04.14-000001在解决冲突之后返回Data Views页面并选择 logs-*。如果冲突仅与 log.offset 相关那么你将不再看到任何冲突列表。如果还存在其他冲突原始 backing index 应不再出现在冲突列表中相反新的 backing index 应已出现在 long 部分。你也可以在 Discover 中进行验证确认 log.offset 字段现在显示的是正确的图标。继续重复上述流程对每一个存在映射冲突的 backing index 执行相同步骤直到所有冲突都成功解决。参考资料ECS 字段参考重新索引文档最终说明通过遵循本文中的步骤你可以解决映射冲突并确保所有新数据都被正确映射。这是通过将必要的组件模板关联到你的数据源来实现的。该工作流不仅修复了当前问题还建立了一套安全且可重复的流程以便在数据和需求不断演进时管理 schema 变更。原文https://www.elastic.co/search-labs/blog/elasticsearch-mapping-conflicts-reindex-data-streams