Elasticseach中可以使用在update和update_by_query中使用script来更新文档,一般情况下使用简单的赋值语句编写script就可以使用,例如:
ctx._source.txt = 'update info';
但是如果mapping中定义了join类型的父子关系的文档,使用script直接更新是有问题的,以painless脚本为例,首先,ctx._source在脚本中充当的是一个map,
如果我们脚本中直接使用‘ctx._source.map.key = value’ 当文档中map字段为空时,这时脚本找不到map,再去get(key)时会报出我们开发非常常见Null Pointer Exception,所以这时我们需要使用新建一个map去进行赋值,如下:
{
"script": {
"lang": "painless",
"source": """
HashMap map = new HashMap();
map.name = 'child';
map.parent = 'id';
ctx._source.joinMap = map;
"""
}
}
使用这样一个脚本更新就避免了NPE。
脚本其实这样就可以了,但是如果是更新join类型的文档,执行会报错:需要制定路由[_routing],原因如下
父子文档需要在同一个分片上,然后调整下脚本’ctx._routing = id;’,继续执行发现其实脚本不能更改_routing,
在painless的文档中也提到了_routing是只读的
所以这时候需要考虑下使用新的方式,ingest pipeline,pipeline中的script可以更改索引文档的信息
这样的话就可以定义一个pipeline,去更新_routing
PUT _ingest/pipeline/set_routing
{
"processors": [
{
"script": {
"source": """
ctx._routing = 'id';
"""
}
}
]
}
这样以后就可以在update_by_query中更新join类型的文档了,完整的语句为
PUT _scripts/init_lines
{
"script": {
"lang": "painless",
"source": """
HashMap map = new HashMap();
map.name = 'line';
map.parent = params.characterId;
ctx._source.character_or_line = map;
"""
}
}
PUT _ingest/pipeline/set_routing
{
"processors": [
{
"script": {
"source": """
ctx._routing = 'C0';
"""
}
}
]
}
POST hamlet_3/_update_by_query?pipeline=set_routing
{
"script": {
"id": "init_lines",
"params": {
"characterId": "C0"
}
},
"query": {
"match": {
"speaker": "HAMLET"
}
}
}
因为在做ECE考试的一道题强制要求创建一个script,所以这里使用了script + pipeline的方式,最好是全部写到pipeline的script中,这里问了下ES的员工为什么普通的script和pipeline中的script不一样,一个可以更新索引的元数据一个不可以呢,回答我贴一下
果然官方还是一针见血,具体的实现的逻辑需要有空看下源码了。
参考:
- https://www.elastic.co/guide/en/elasticsearch/reference/7.2/parent-join.html
- https://www.elastic.co/guide/en/elasticsearch/reference/7.2/script-processor.html
- https://www.elastic.co/guide/en/elasticsearch/painless/7.2/painless-update-context.html
- https://discuss.elastic.co/t/the-difference-between-using-script-in-pipeline-and-using-normal-script-to-update-document/239951