我写了一个实用程序来重新索引最初放在错误的Elasticsearch索引中的特定文档。我正在试图理解那些似乎是虚假的——或者至少是不一致的——关于重建索引失败的报告
具体的
背景
我的公司有一个系统,可以创建从现场设备接收到的事件的每日索引。由于早期的设计误解,特定事件的索引由
到达日期
在我们的系统中;而实际需求是将事件放入与其对应的索引中
发生日期
.
我在Go中编写了一个实用程序来查找这些异常事件文档并将其移动到它们所属的索引中。它使用
github.com/olivere/elastic
包装(
v6.2.26
大多数情况下,这个实用程序工作得很好,但是偶尔(而且越来越频繁地)它会停止使用
400 Bad Request
_reindex
操作,在对特定日期的批处理中仅对事件的子集重新编制索引之后。
不幸的是
elastic
包裹
doesn't return the response body
当它从返回错误时
ReindexService.Do
,所以我不得不从
this article
,它在HTTP客户机中截取并记录响应主体,以查看详细信息。
问题
下面是一些与此错误的典型发生相关联的日志行(手动重新格式化以提高可读性)。
message_id
是64位纳秒分辨率的Unix epoch,编码为十六进制字符串,表示事件发生的时间。
在这种特殊情况下,由于映射冲突(无法更改字段的类型),一批12个中的两个事件文档不会重新编制索引:
findEventTime event_time="2019-05-19 13:45:52.562001331 +0000 UTC"
event_ts=15a0198a3b5815b3
message_id=15a0198a3b5815b3
event time event_time="2019-05-19T13:45:52.562001331Z"
events_date=2019-05-19
source_index=event-store-2019-06-25
...
ES request method=POST
req_body="{\"dest\":{
\"index\":\"event-store-2019-05-19\",
\"op_type\":\"create\",
\"version_type\":\"external\"
},\"source\":{
\"index\":\"event-store-2019-06-25\",
\"query\":{
\"range\":{
\"message_id\":{
\"from\":\"159fec78e07d0000\",
\"include_lower\":true,
\"include_upper\":false,
\"to\":\"15a03b0d71cc0000\"}}}}}"
url="http://events.es.yoyodyne.com/_reindex"
ES response rsp_body="{\"took\":183,
\"timed_out\":false,
\"total\":12,
\"updated\":0,
\"created\":10,
\"deleted\":0,
\"batches\":1,
\"version_conflicts\":0,
\"noops\":0,
\"retries\":{\"bulk\":0,\"search\":0},
\"throttled_millis\":0,
\"requests_per_second\":-1.0,
\"throttled_until_millis\":0,
\"failures\":[
{\"index\":\"event-store-2019-05-19\",
\"type\":\"_doc\",
\"id\":\"5eadba4f-dd1f-4141-8fdb-4dc9e363b9ff\",
\"cause\":{\"type\":\"illegal_argument_exception\",
\"reason\":\"mapper [params.severity_metrics.torque.value] cannot be changed from type [long] to [float]\"},
\"status\":400},
{\"index\":\"event-store-2019-05-19\",
\"type\":\"_doc\",
\"id\":\"8070134f-5f2b-4cbe-85a7-c4636c1f529f\",
\"cause\":{\"type\":\"illegal_argument_exception\",
\"reason\":\"mapper [params.severity_metrics.torque.value] cannot be changed from type [long] to [float]\"},
\"status\":400}]}"
status=400
现在,这两个事件确实有
params.severity_metrics.torque.value
是点小数,所以
表面上
报告的错误似乎是合理的:
$ ./esq.sh -q -I event-store-2019-06-25 -c params.severity_metrics.torque -e id 8070134f-5f2b-4cbe-85a7-c4636c1f529f | jq '.hits.hits[] | ._source.params.severity_metrics'
{
"torque": {
"severity": "med",
"value": 3.9
}
}
$ ./esq.sh -q -I event-store-2019-06-25 -c params.severity_metrics.torque -e id 5eadba4f-dd1f-4141-8fdb-4dc9e363b9ff | jq '.hits.hits[] | ._source.params.severity_metrics'
{
"torque": {
"severity": "med",
"value": 4.4
}
}
然而。。。
1
十份文件中
已成功为此编制索引
_重新索引
手术,其中五个
也
参数严重性度量.扭矩值
是点小数(其他五个具有
0
):
$./esq.sh -q -I event-store-2019-06-25 -z 12 -c id -c params.severity_metrics.torque \
-r message_id gte 159fec78e07d0000 lt 15a03b0d71cc0000 | jq -c '.hits.hits[] | {id: ._source.id, severity_metrics: ._source.params.severity_metrics}'
{"id":"5eadba4f-dd1f-4141-8fdb-4dc9e363b9ff","severity_metrics":{"torque":{"severity":"med","value":4.4}}}
{"id":"d9274787-58c8-46bf-93ad-dd9d0d3d854a","severity_metrics":{"torque":{"severity":"high","value":29.5}}}
{"id":"1b5a2072-8b88-4a90-a12b-91db171f6210","severity_metrics":{"torque":{"severity":"none","value":0}}}
{"id":"a8e20619-d4c6-44f2-8e9a-16826e933892","severity_metrics":{"torque":{"severity":"high","value":13.3}}}
{"id":"96e951fe-2841-4d69-b7d6-36c31201ff3d","severity_metrics":{"torque":{"severity":"high","value":31.3}}}
{"id":"d63141d5-1714-4caa-9542-5860c0eb0881","severity_metrics":{"torque":{"severity":"none","value":0}}}
{"id":"432fecec-762f-4a66-81ef-3071b3544e70","severity_metrics":{"torque":{"severity":"med","value":3.5}}}
{"id":"85eb8492-7e4f-4441-b747-d5333b322992","severity_metrics":{"torque":{"severity":"high","value":381.4}}}
{"id":"8070134f-5f2b-4cbe-85a7-c4636c1f529f","severity_metrics":{"torque":{"severity":"med","value":3.9}}}
{"id":"398dee7e-4160-4697-9a2e-41e1c00c7381","severity_metrics":{"torque":{"severity":"none","value":0}}}
{"id":"5304ef1f-1160-4ed4-9423-f9cf672f7ddf","severity_metrics":{"torque":{"severity":"none","value":0}}}
{"id":"11fadf45-71d4-4d7a-b92e-9f1515ead36f","severity_metrics":{"torque":{"severity":"none","value":0}}}
$ ./esq.sh -q -I event-store-2019-05-19 -z 12 -c id -c params.severity_metrics.torque \
-s -e id d9274787-58c8-46bf-93ad-dd9d0d3d854a \
-e id a8e20619-d4c6-44f2-8e9a-16826e933892 \
-e id 96e951fe-2841-4d69-b7d6-36c31201ff3d \
-e id 432fecec-762f-4a66-81ef-3071b3544e70 \
-e id 85eb8492-7e4f-4441-b747-d5333b322992 | jq -c '.hits.hits[] | {id: ._source.id, severity_metrics: ._source.params.severity_metrics}'
{"id":"d9274787-58c8-46bf-93ad-dd9d0d3d854a","severity_metrics":{"torque":{"severity":"high","value":29.5}}}
{"id":"a8e20619-d4c6-44f2-8e9a-16826e933892","severity_metrics":{"torque":{"severity":"high","value":13.3}}}
{"id":"96e951fe-2841-4d69-b7d6-36c31201ff3d","severity_metrics":{"torque":{"severity":"high","value":31.3}}}
{"id":"432fecec-762f-4a66-81ef-3071b3544e70","severity_metrics":{"torque":{"severity":"med","value":3.5}}}
{"id":"85eb8492-7e4f-4441-b747-d5333b322992","severity_metrics":{"torque":{"severity":"high","value":381.4}}}
那么为什么
_重新索引
2
源索引和目标索引的映射都同意所讨论的字段是
long
$ curl -s $ESHOST/event-store-2019-06-25/_mapping | jq '.["event-store-2019-06-25"] | .mappings._doc.properties.params.properties.severity_metrics.properties.torque'
{
"properties": {
"severity": {
"type": "keyword"
},
"value": {
"type": "long"
}
}
}
$ curl -s $ESHOST/event-store-2019-05-19/_mapping | jq '.["event-store-2019-05-19"] | .mappings._doc.properties.params.properties.severity_metrics.properties.torque'
{
"properties": {
"severity": {
"type": "keyword"
},
"value": {
"type": "long"
}
}
}
这当然提出了一个问题:“那些
参数严重性度量.扭矩值
源索引中已存在小数
event-store-2019-06-25
首先呢?”
三。
如果我手动从源索引中删除成功重新编制索引的十个文档,然后重新运行我的实用程序,则
_重新索引
operation很乐意接受它在上一次运行中拒绝的两个文档,并将它们毫无怨言地添加到目标索引中。世界跆拳道联盟?
所以:
这一切怎么可能?
关于映射或
_重新索引
手术能合理解释我所看到的所有这些(看似矛盾的)症状?
顺便说一句,如果你想知道,
esq.sh
是我用来简化Elasticsearch查询组合的本地脚本。上述选项包括:
-I <index-name>
-c <source-field>
-e <name> <val> (equality)
-r <name> <op> <val> [<op> <valu>...] (range)
-q (quiet; suppress echo of formatted request body)
-s (should; roughly logical-OR)
-z <num> (size)
大声喊叫
Joe
让我看到了一些事情:
首先,除了具体说明
params
是一个
object
,以及——通过映射模板——任何
params.*
其值为字符串的存储/索引为
keyword
,这些索引的映射规范
没有什么
关于如何存储/索引
参数*
具有其他类型的值。
{
"mappings":{
"_doc":{
"properties":{
...
"params":{
"type":"object"
},
...
},
"dynamic_templates":[
{
"Keyword Params":{
"mapping":{
"type":"keyword"
},
"match_mapping_type":"string",
"path_match":"params.*"
},
...
]
}
}
}
不要
通常对数值消息参数值进行聚合,甚至是查询,所以目前我不想搅乱现有的映射定义。我猜最初的设计者并不在乎索引中的数值参数值是如何表示的,而只关心字符串类型的参数值不会被分析。)
显然,这个关于数值参数类型的(非)决定意味着Elasticsearch根据它看到的进入索引的字段的第一个值来选择特定索引中字段的类型。而且由于JSON封送数字的方式(如果是零则省略小数部分),如果整数值(通常为0)是该参数到达索引的第一个值,则默认情况下,该参数将是
长的
,否则它将是
float
.
这也为我澄清了一个想法,即JSON文档源中的值的表示在某种程度上独立于Elasticsearch索引/存储的映射值。这是完全合理的(如果有点混乱)
来源
像这样的价值观
4.4
3.9
对于将这些值存储为
4
和
3
. 这就是(我假设)第一次被索引的原始文档不会引起Elasticsearch的任何抱怨,即使参数值的清单表示像
4.4
无法完全由该字段的接收索引的类型映射容纳。
在重新编制索引的情况下,我还可以看到Elasticsearch将如何(或者,无论如何,
)如果源索引中字段的映射类型与目标索引中字段的映射类型不同,则标记错误。(这当然是假设
_重新索引
操作在尝试将源索引类型信息重新索引到目标时,会携带源索引类型信息,而不仅仅是索引原始JSON源文档。)
所以,现在情况的某些方面已经比较清楚了。但我还是不明白为什么会出现这样的错误:
-
对于批处理中的某些文档,但对于其他似乎具有相同问题的文档,
-
当源索引和目标索引都同意字段的类型时
为什么他们
不要
在重试相同文档时发生。
我对报告的错误(“mapper无法从类型更改”)进行了更多搜索,并找到了
this article
包含指向函数的链接(
MappedFieldType.checkTypeName
)会产生错误消息(
link for Elasticsearch 6.7
).
所以
什么东西,什么地方
Elasticsearch认为
参数严重性度量.扭矩值
特定传入文档的字段应为
浮动
长的
.
这让我想到在不同的碎片中可能会发生什么,这让我想到了这个(2014)Elasticsearch问题
#8688
(“映射更新应该是同步的”)。
这个问题已经解决了,但我现在看到的可能还是那个修复的结果。也就是说,如果存在冲突,两个映射中只有一个可以获胜,而那些失败的映射最终将在响应主体的
failures
情况可能是这样,也可能不是这样,但似乎并不具体
碎片
4.4
)作为不同的文档(值为
29.5
)那个
成功
一开始
_重新索引
请求。
$ curl -s events.es.yoyodyne.com/event-store-2019-05-19/_search?pretty -H "Content-Type: application/json" -d '{
"explain": true,
"query": {
"bool": {
"should": [
{ "term": { "id": "8070134f-5f2b-4cbe-85a7-c4636c1f529f" } }, <=== failed
{ "term": { "id": "5eadba4f-dd1f-4141-8fdb-4dc9e363b9ff" } }, <=== failed
{ "term": { "id": "d9274787-58c8-46bf-93ad-dd9d0d3d854a" } },
{ "term": { "id": "a8e20619-d4c6-44f2-8e9a-16826e933892" } },
{ "term": { "id": "96e951fe-2841-4d69-b7d6-36c31201ff3d" } },
{ "term": { "id": "432fecec-762f-4a66-81ef-3071b3544e70" } },
{ "term": { "id": "85eb8492-7e4f-4441-b747-d5333b322992" } }
]
}
}
}' | jq -c '.hits.hits[] | [._id, ._shard, ._source.params.severity_metrics.torque.value]'
["432fecec-762f-4a66-81ef-3071b3544e70","[event-store-2019-05-19][2]",3.5]
["85eb8492-7e4f-4441-b747-d5333b322992","[event-store-2019-05-19][2]",381.4]
["d9274787-58c8-46bf-93ad-dd9d0d3d854a","[event-store-2019-05-19][0]",29.5] <=== succeeded (shard 0)
["5eadba4f-dd1f-4141-8fdb-4dc9e363b9ff","[event-store-2019-05-19][0]",4.4] <=== failed (shard 0)
["8070134f-5f2b-4cbe-85a7-c4636c1f529f","[event-store-2019-05-19][3]",3.9] <=== failed (shard 3)
["a8e20619-d4c6-44f2-8e9a-16826e933892","[event-store-2019-05-19][1]",13.3]
["96e951fe-2841-4d69-b7d6-36c31201ff3d","[event-store-2019-05-19][1]",31.3]
我已经克隆了Elasticsearch repo,但它当然是一个怪物,而且我对请求如何通过Elasticsearch进行处理没有任何基础;另外,我每年只查看一次Java代码(如果幸运的话!)。所以我很怀疑我是否能花必要的时间在源代码中跟踪这个问题。
也许了解Elasticsearch内部结构的人能帮上忙吗?