重建索引API功能:
- 在集群之间传输数据
- 重新定义、更改和/或更新映射
- 通过采集管道进行处理和编制索引
- 通过清除已删除的文档回收存储空间
- 通过查询筛选器将大型索引拆分成较小的索引组
常见问题处理
症状:Kibana 开发工具中显示“backend closed connection”(后端已关闭连接)
问题
您的客户端将在 N 秒后关闭非活动套接字;以 Kibana 为例,如果重建索引操作无法在 120 秒内(v7.13 中默认的 server.socketTimeout 值)完成,您将看到“backend closed connection”(后端已关闭连接)消息。
解决方案 #1 - 获取在集群上运行的任务列表
其实这并不是问题,即使您在 Kibana 中看到这条消息,Elasticsearch 也会在后台运行重建索引 API。
您可以使用 _task API 跟踪重建索引 API 的执行情况,并查看所有指标:
代码语言:javascript复制GET _tasks?actions=*reindex&wait_for_completion=false&detailed
这个 API 将向您显示当前在 Elasticsearch 集群中运行的所有重建索引 API,如果您在此列表中没有看到您的重建索引 API,即表明它已完成。
解决方案 #2 - 将重建索引结果存储在 _tasks 上
如果已知重建索引操作需要的时间超过 120 秒(120 秒是 Kibana 开发工具的超时时间),可以使用查询参数 wait_for_completion= false 来存储重建索引 API 的结果,这样您就能使用 _task API 来获取重建索引 API 结束时的状态(也可以从“.tasks”索引获取文档,如 wait_for_completion=false 的文档中所述)。
代码语言:javascript复制POST _reindex?wait_for_completion=false
{
"conflicts": "proceed",
"source": {
"index": "<source-index-name>"
},
"dest": {
"index": "<dest-index-name>"
}
}
当您使用“wait_for_completion=false”执行重建索引时,响应将类似于以下内容:
代码语言:javascript复制{
"task" : "a9Aa_I_ZSl-4bjR5vZLnSA:247906"
}
您需要保留这里提供的任务,搜索重建索引的结果时会用到(您将看到已创建的文档数、冲突甚至是错误;完成后,您将看到所花费的时间、批次数等等):
代码语言:javascript复制GET _tasks/a9Aa_I_ZSl-4bjR5vZLnSA:247906
症状:_task API 列表中没有您的重建索引 API。
如果使用上文提到的 API 无法找到重建索引 API 操作,可能这又是另一个问题,下面我们一个一个地解决。
问题
如果重建索引 API 不在列表中,即表明操作已完成,因为没有更多的文档需要重建索引,或者是因为出现了错误。
我们将使用 _cat count API 来查看存储在两个索引中的文档数量,如果两个数值不同,则表明您的重建索引 API 执行已失败。
代码语言:javascript复制GET _cat/count/<source-index-name>?h=count
GET _cat/count/<dest-index-name>?h=count
您需要将 / 替换为您在重建索引 API 中使用的索引名称。
解决方案 #1 - 这是一个冲突问题
最常见的错误之一是存在冲突,默认情况下,如果有冲突,重建索引 API 将中止。
现在,我们有两个选择:
- 将“conflicts”设置为“proceed”,这样重建索引 API 将忽略无法索引的文档,转而索引其他文档。
- 或者,我们也可以选择修复冲突,这样就可以为所有文档重建索引。
第一个选择中的冲突设置如下所示:
代码语言:javascript复制POST _reindex
{
"conflicts": "proceed",
"source": {
"index": "<source-index-name>"
},
"dest": {
"index": "<dest-index-name>"
}
}
或者,在第二个选择中,我们将搜索并修复产生冲突的错误:
- 避免这一问题的最佳实践是在目标索引上定义映射或模板。这些错误中 99% 是源索引和目标索引之间的字段类型不匹配。
- 如果在定义了映射或模板后,问题仍然存在,则表明某些文档可能无法建立索引,并且默认情况下不会记录错误。我们需要启用记录器,以便在 Elasticsearch 日志中查看错误。
PUT /_cluster/settings
{
"transient": {
"logger.org.elasticsearch.action.bulk.TransportShardBulkAction":"DEBUG"
}
}
- 启用记录器后,我们需要再执行一次重建索引 API,如果可能的话,将“conflicts”设置为“proceed”,因为很多时候源索引和目标索引之间会有多个字段发生冲突。
- 现在,重建索引 API 正在运行,我们将在日志中使用 grep 命令查找/搜索“failed to execute bulk item”或“MapperParsingException”
failed to execute bulk item (index) index {[my-dest-index-00001][_doc][11], source[{
"test-field": "ABC"
}
或
代码语言:javascript复制 "org.elasticsearch.index.mapper.MapperParsingException: failed to parse field [test-field] of type [long] in document with id '11'. Preview of field's value: 'ABC'",
"at org.elasticsearch.index.mapper.FieldMapper.parse(FieldMapper.java:216) ~[elasticsearch-7.13.4.jar:7.13.4]",
通过这个堆栈跟踪,我们已经有足够的信息来理解冲突是什么。在我的重建索引 API 中,目标索引有一个名为 [test-field] 的字段,类型为 [long],重建索引 API 尝试将该字段设置为字符串“ABC”(您可以用自己的内容字段替换“ABC”)。
在 Elasticsearch 中,字段数据类型是可以定义的,您可以在索引创建期间或使用模板设置这些类型。索引创建完成后,类型便不能更改,您需要先删除目标索引,然后使用之前提供的选项来设置新的固定映射。
- 修复了错误之后,请记得将记录器转换为没那么冗长的模式:
PUT /_cluster/settings
{
"transient": {
"logger.org.elasticsearch.action.bulk.TransportShardBulkAction":NULL
}
}
解决方案 #2 - 没有冲突错误,但重建索引仍然失败
如果在重建索引执行期间,您在 Elasticsearch 日志中发现以下跟踪:
代码语言:javascript复制'search_phase_execution_exception', 'No search context found for id [....]')
可能有很多种原因:
- 集群存在一些不稳定问题,并且在重建索引执行过程中,一些数据节点进行了重启或不可用。 如果是这个原因,在运行重建索引之前,请确保集群是稳定的,且所有数据节点都运行良好。
- 如果您是远程执行重建索引操作,并且已知节点之间的网络不可靠:
- 建议选择快照 API(如本文结尾处所述)。
- 我们可以尝试对重建索引 API 执行手动切片,该操作可以将请求过程分割成较小的部分(当我们在同一集群中使用重建索引 API 时,可以使用这个选项)。
- 如果您的 Elasticsearch 集群存在过度分片、资源利用率高或垃圾收集问题,可能会在滚动搜索查询过程中出现超时。默认的滚动超时值为 5 分钟,因此,您可以尝试将重建索引 API 上的滚动设置为一个更高的值。
POST _reindex?scroll=2h
{
"source": {
"index": "<source-index-name>"
},
"dest": {
"index": "<dest-index-name>"
}
}
症状:Elasticsearch 日志中显示“节点未连接”
我们始终建议在集群稳定且状态为绿色的情况下运行重建索引 API,集群需要足够的容量才能运行搜索和索引操作。
问题
代码语言:javascript复制NodeNotConnectedException[[node-name][inet[ip:9300]] Node not connected]; ]
如果您的日志中出现这类错误,表明您的集群存在稳定性和连接问题,而不仅仅是重建索引 API 出现了问题。
但不妨设想一下,已知存在连接问题,但是需要运行重建索引 API,该怎么办。
解决方案
修复连接问题。
但是,假设我们知道有连接问题,可是需要运行重建索引 API,我们可以减少失败的可能性,不过这不是修复操作,并不是在所有情况下都有效。
- 将源索引或目标索引(主索引或副本)的分片移出存在连接问题的节点。使用分配筛选 API 移动分片。
- 您也可以移除目标索引上的副本(仅针对目标索引),这将加快重建索引 API 的执行速度,毕竟重建索引的运行速度越快,出现故障的可能性就越小。
PUT /<dest-index-name>/_settings
{
"index" : {
"number_of_replicas" : 0
}
}
如果这两种操作都无法让您成功执行重建索引 API,表明您需要先修复稳定性问题。
症状:日志中没有错误,但两个索引的文档计数不一致
有时,重建索引 API 已经完成,但是源索引与目标索引中的文档计数不一致。
问题
如果我们尝试在一个目标中从多个源重建索引(即在一个目标中合并多个索引),问题可能源自您为这些文档分配的 _id。
假设我们有 2 个源索引:
- 索引 A,_id:1,信息:“Hello A”
- 索引 B,_id:1,信息:“Hello B”
两个索引在 C 中合并后:
- 索引 C,_id:1,信息:“Hello B”
两个文档的 _id 相同,因此,最后索引的文档将覆盖前一个索引的文档。
解决方案
您可以选择不同的采集管道,也可以在重建索引 API 中使用 Painless。在这篇博文中,我们将使用脚本选项,在请求正文中使用“Painless”。
操作起来非常简单,只需使用原始 _id 并添加源索引名称:
代码语言:javascript复制POST _reindex
{
"source": {
"index": "<source-index-name>"
},
"dest": {
"index": "<dest-index-name>"
},
"script": {
"source": "ctx._id = ctx._id '-' ctx._index;"
}
}
还是前面的示例:
- 索引 A,_id:1,信息:“Hello A”
- 索引 B,_id:1,信息:“Hello B”
两个索引在 C 中合并后:
- 索引 C,_id:1-A,信息:“Hello A”
- 索引 C,_id:1-B,信息:“Hello B”
最佳实践
并发切片与size设置
Reindex支持切片滚动,以并行重建进程。这种并行化可以提高效率,并提供一种方便的方式将请求分解为更小的部分。默认reindex 读取源索引的 batch_size 为1000,这个size的使用限制于系统资源的分配,可以调整到10000增加速度.并发数主要通过url后的slices参数控制,下面的例子里 size设置为1w,并发数为5 .
代码语言:javascript复制POST _reindex?slices=5&refresh
{
"conflicts": "proceed",
"source": {
"index": "my-index-000001",
"size": 10000,
"query":{
"range":{
"FIELD": {
"gte": 10,
"lte": 20
}
}
}
},
"dest": {
"index": "my-new-index-000001"
}
}
怎么选择切片的数量
- 使用自动切片,将切片设置为auto, ES 会自行设置一个合理的数字。
- 设置slice数量与索引中的分片数量相等时,查询性能是最有效的。通常情况下,将slice的数量设置为高于shard的数量并不会提高效率,反而会增加开销。
在生产使用中,请通过测试设置合理的slice数量提高reindex的效率。
实际测试中,一个1.5tb的24分片索引(集群配置32c64g,24节点,索引1副本,目标索引未设置副本),使用48 slice 需要2小时完成,24 slice 则需要3小时。
reindex 减索引字段
在 source 的 query 中限定 includes 的字段,这样可以在 reindex 时去除原索引不需要的字段
代码语言:javascript复制POST _reindex?slices=5&refresh
{
"conflicts": "proceed",
"source": {
"index": "my-index-000001",
"size": 10000,
"query":{
"range":{
"FIELD": {
"gte": 10,
"lte": 20
}
}
},
"_source":{
"includes":["_id","aaa","bbb"],
"excludes":["xxx","yyy","zzz"]
}
},
"dest": {
"index": "my-new-index-000001",
"version_type":"external"
}
}
5、关于写入的版本操作
此处引用携程wood大叔在社区上的一个例子:
version_type
的internal, external都是从dest索引的视角看过去的,internal表示使用dest内部版本, external表示使用source的版本。
假设source索引有3条文档:
id: 1 text: "a" version: 4
id: 2 text: "b" version: 3
id: 3 text: "c" version: 2
dest索引有2条文档:
id: 1 text: "e" version: 1
id: 2 text: "f" version: 5
如果将source reindex到dest,文档1和2会产生冲突。
使用不同的version type做reindex,dest里的文档内容和版本变化的结果如下:
version type = internal
id: 1 text: "a" version: 2 # 覆盖 & 内部版本号 1
id: 2 text: "b" version: 6 # 覆盖 & 内部版本号 1
id: 3 text: "c" version: 1 # 新文档,创建,版本号为1
version type = external:
id: 1 text: "a" version: 4 # 外部版本号> 内部,因此覆盖文档 & 保留外部版本号4
id: 2 text: "f" version: 5 # 外部版本号小于内部,版本冲突,保留文档和内部版本号5
id: 3 text: "c" version: 2 # 新文档,创建,并保留外部版本号2
op_type = create:
id: 1 text: "e" version: 1 # dest已有文档忽略不处理
id: 2 text: "f" version: 5 # dest已有文档忽略不处理
id: 3 text: "c" version: 1 # dest没有此文档,新创建(create),版本号为1
6、reindex 生产操作流程
前提条件:有时间戳字段,保证数据查询的范围有效。使用时间戳字段来分批执行reindex,这样来减少因reindex导致的数据停写时间。如没有时间戳字段,则整个reindex需要在源索引停止写入后操作。
主要操作流程:
1、新建新索引,设置好新的字段mapping和setting;
2、根据时间字段进行reindex,将大部分数据写入新索引;
3、如果步骤2耗时漫长,比如3小时,则根据时间进行第二轮数据reindex;
4、停止数据写入,进行最后一轮的reindex。为了减少停服时间,query的时间窗口控制在半小时内,数据量控制在整体数据集的10分之一以下。
5、比对新旧索引数据量,正确后进行索引别名切换,无索引别名则应用程序切换至新索引。
7、对于大索引reindex的异步处理方案
方案:利用镜像把数据复制出来在非生产环境处理
具体操作:
1.设置临时镜像路径,提前一天把索引先镜像copy出来
代码语言:javascript复制PUT _snapshot/ops-es-snapshot-ol
备份索引
2.在单独的非生产集群reindex,注意新索引不需要带别名
3.第二天晚上把结果索引镜像复制回生产环境
4.利用时间戳把新旧索引的数据差补上。注意:delete操作不可回放
5.数据校验无误后,需要别名切换的进行别名切换,再应用切换
6.删除生产的临时镜像路径
8、磁盘空间不足问题
增加副本导致索引数据成倍增长,磁盘空间需要评估能否满足需求,最好提前扩容。
如果创建副本期间磁盘空间告警:
1. 空间完全不足以容纳新增的副本,快速动态调整副本数量设置为0
PUT /shimo_v4/_settings { "number_of_replicas": 0 }
2. 空间勉强容纳新增副本,需要调高磁盘水位线,防止触发水位限制导致限流,数据写入失败
cluster.routing.allocation.disk.watermark.low cluster.routing.allocation.disk.watermark.high
9、reindex期间双写问题
问题:因业务不能停止写入,服务滚动重启期间存在新旧索引双写的问题,部分数据可能双边更新,应用切换完成后,不能简单覆盖。
解决方法:多次reindex操作(全量 增量 补数据),version_type
统一使用external
模式。以source版本为准,dest不存在的doc直接插入,version版本比dest大的覆盖更新,version版本比dest小的版本冲突,同时需设置conflicts="proceed",确保冲突不会中断reindex操作。
优势:
- reindex对生产的资源使用减少,影响时间也更小
- reindex的时间窗口和资源配置更加灵活,成功率也更高
- 大索引下(500g以上)镜像备份的时间远远小于reindex的耗时,这样能有效减少生产变更的耗时
建议:
鉴于 reindex 在 1TB 以上数据量糟糕的表现(时间长,速度衰迭严重,任务完成无保障),大索引的重刷还是使用logstash 分段任务来处理更合适。
结论
当您需要更改某些字段的格式时,重建索引 API 是一个不错的选择。下面我们将列出一些关键方面,确保重建索引 API 尽可能顺利地运行:
- 为目标索引创建并定义映射(或模板)。
- 调整目标索引,使重建索引 API 尽可能快地索引文档。我们有一个文档页面,其中提供了用于调整和加快索引速度的所有选项。 https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html
- 如果源索引的大小为中型或大型,请定义“wait_for_completion=false”设置,以便重建索引 API 结果存储 在 _tasks API 上。
- 将索引分成更小的组,您可以使用查询(范围、术语等)定义不同的组,或者使用切片功能将请求分成较小的部分。
- 运行重建索引 API 时,稳定性是关键因素,参与重建索引 API 的索引需要处于绿色状态(最糟糕的情况是黄色状态),然后确保我们的数据节点中没有很长的 GarbageCollections,并且 CPU 和磁盘使用率为正常值。
从 v7.11 开始,我们发布了一项新功能,让您无需为数据重建索引,这项功能称为“运行时字段”。使用这个 API 可以修复错误,而无需为数据重建索引,因为您可以在索引映射或搜索请求中定义运行时字段。您可以通过这两种方式在采集数据后灵活地更改文档的模式,并生成只作为搜索查询的一部分存在的字段。