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 数据分层
- Hot:Hot 层是时间序列数据进入 Elasticsearch 的入口,存储最新、最常被搜索的数据。Hot 层节点需要快速读写,因此需要更多资源和更快的存储(SSD)。该层是必需的,新数据流索引会自动分配到这里。
- Warm:当时间序列数据的查询频率低于最近在 hot 层中索引的数据时,可以移动到 warm 层。Warm 层通常存储最近几周的数据。仍然允许更新,但通常不频繁。Warm 层节点通常不需要像 hot 层那样快。为了提高弹性,warm 层中的索引应配置一个或多个副本。
- Cold:不常被搜索的数据可以从 warm 层移动到 cold 层。Cold 层仍然可搜索,但更侧重于降低存储成本而不是搜索速度。或者,cold 层可以存储带副本的常规索引,而不是可搜索快照,从而在不减少磁盘空间需求的情况下,使用更便宜的硬件存储较旧的数据。
- Frozen:很少被查询或不再被查询的数据会从 cold 层移动到 frozen 层,直到生命周期结束。该层使用快照仓库和部分挂载的索引来存储和加载数据,从而减少本地存储和成本,同时仍然允许搜索。由于 Elasticsearch 可能需要从快照仓库获取冻结数据,因此在 frozen 层上的搜索通常比 cold 层更慢。我们建议使用专用的 frozen 层节点。
更多有关数据分层的知识,请详细阅读文章 “Elasticsearch:Searchable 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 Language(KQL)语句应类似如下截图所示,包含 _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 的 ecs@mappings 组件模板。ecs@mappings 组件模板会在数据流组件之后应用,作为“兜底(catchall)”机制,用于补充之前组件模板可能未覆盖到的其他字段映射。
请打开数据流的 @package mappings 对应浏览器标签页。(路径:Stack Management->Index Management->Component Template-> logs-filestream.generic@package ->Manage->Edit。)进入后,点击 Review 部分,然后选择 Request,最后点击右侧的 Copy 按钮。复制得到的组件模板 JSON 内容将确保在更新 log.offset 字段映射的同时,其余字段映射和设置得以保留。该 JSON 将作为新 reindex 后 backing index 的基础结构。
重要提示:如果没有复制模板 JSON 就继续进行 reindex 操作,虽然 log.offset 冲突会被解决,但由于未保持当前映射的完整性,可能会与集成产生新的冲突,从而导致需要重复处理原本的问题,增加额外工作量。
打开第二个浏览器标签页,进入 Dev Tools,并粘贴刚刚复制的内容。现在需要对粘贴内容进行清理与调整:
对请求的修改
1. 索引名称:
将_component_template/logs-filestream.generic@package替换为你计划重新索引的 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_templates""properties"(包含字段及其映射)
4. dynamic_templates 修改:
当前 dynamic templates 中包含一些与后续 ecs@mappings 重复的字段,这会造成冗余。
需要:
删除
dynamic_templates中除第二部分_embedded_ecs-data_stream_to_constant之外的所有内容然后按上述方式,将 ecs@mappings component template 中的 dynamic mappings 一并加入
建议直接从 UI 复制 ecs@mappings 的完整 mappings 内容,然后粘贴到 Dev Tools 的 dynamic_templates 中,再去重整理
最终dynamic_templates应保留_embedded_ecs-data_stream_to_constant,并在其后追加整理后的 ecs@mappings 内容。
⚠️重要说明:
如果不移除或正确整理 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_completion=false,你可以跟踪文档复制进度,从而估算剩余 reindex 时间。如果不使用该设置,则无法通过GET _tasks命令查看任务状态,只能通过GET <backing index name>-1/_count查看新 backing index 中的文档数量。
重要说明:如果 reindex 过程中出现问题,不要重新运行 reindex 命令,因为这样会重新开始整个过程,并在以
-1结尾的 index 中产生重复数据。如果需要重试,应先删除带-1后缀的 index,然后重新执行之前的 PUT 命令以重新创建新的 backing index 空壳。
POST _reindex?wait_for_completion=false { "source": { "index": "<source backing index>" }, "dest": { "index": "<new backing index>-1" } } i.e. POST _reindex?wait_for_completion=false { "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 index
- ILM 策略已生效,并且 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
