ES Nested 类型
有个服务写 es,遇到了以下报错:
|
|
使用的 es版本为5.3.2,在 es 的数据项中,使用了 nested 类型,字面意思是这个 index 下的 fileds 超过了 1000,禁止写入了。
问题原因很简单,但其中涉及到 es 的 objects 、nested类型,下面我们来具体分析下这俩类型的异同。
object 类型
例子 1
我们先看个 object 类型的例子,下面这个文档是一篇文章的评论信息,包含了 3 条 comment,且该 comment 为 object 类型:
|
|
写入 es 后,我们来查询 name = wangwu 的评论,分别测试 age = 10、20、33 等 case,查询语法如下,修改 age 为对应的值即可(用 kibana 语法表示更简明):
|
|
测试结果如下:
- 1>
comments.name: "wangwu" && comments.age: 10:返回空 - 2>
comments.name: "wangwu" && comments.age: 20:返回_id=1的文档(也即我们刚写入的那条数据) - 3>
comments.name: "wangwu" && comments.age: 33:返回_id=1的文档
从上面的现象可知,其实 3> 条结果才是我们的预期,而 2> 这个 case,虽然并不存在 name = wangwu AND age = 20 的情况,但也返回了。
这是为什么呢,下面来一探究竟。
分析
在 es中, object fields 1 支持 json 格式的文档存储,也即支持多层嵌套,然后可以对其进行嵌套搜索,如上面例子所见:comments.name: "wangwu"。
但在其在内部实际存储该类型时,会被展开成扁平化的 key-value pairs ,其中 value 是同一个 key 的聚合,也即,上面的实例中,最后被格式化为了如下格式:
|
|
一眼便知,为什么会出现上面例子中 2> 这个 case,因为 comments.age 这个 key 的 multi-value里,有 20 这个值,所以便被匹配出来。
问题来了,我们就是精确匹配呢,使用 nested 类型可解决。
nested 类型
Nested fileds2 看起来和 object fields 类似,都支持多层嵌套查询,但实际上,它的内部存储方式却不一样。
例子 2
我们新建个 coments 类型为 nested 的 index:
|
|
然后插入 例子 1 中的测试数据,同样的做相关测试,具体过程就省略了,注意,查询语法的区别3:
|
|
测试结果如下(kibana 不支持这种查询语法,下面的搜索条件只是沿用上面例子的,为了方便查看):
- 1>
comments.name: "wangwu" && comments.age: 10:返回空 - 2>
comments.name: "wangwu" && comments.age: 20:返回空 - 3>
comments.name: "wangwu" && comments.age: 33:返回_id=1的文档
我们发现,这种搜索结果才是预期的。
分析
nested fileds 内部实现方式与 object 不同之处在于:
object:在内部,会被展开成扁平化的key-value pairs
|
|
nested:在内部,会索引数组中的每个对象作为单独的隐藏文档,这意味着每个嵌套对象可以独立于其他对象查询。
|
|
问题解决
以上,我们知道 object 与 nested 这俩者的区别,接下来,下面回到开头的问题。
在实际的 es 使用中,我们是按天 shard,且有几个字段为 nested 类型, 根据 dynamic mapping 策略,nested 类型的 key 会被解析成 fileds,而业务有个场景是以 随机值 作为 key 写入 es,就导致了 fileds 快速膨胀,达到了 1000 的限制。
那为什么会限制在 1000 呢,因为如果不限制,mapping 快速膨胀后,会引起索引和副本数据膨胀,从而导致性能退化、内存问题等4。
当然,这个限制也可以人为修改:
|
|
需要注意的是,dynamic mapping 时,会以先写入的 filed 的数据格式作为当天的数据格式,后续写入不能修改,在弱类型语言中,比如 php 也即一旦创建完成,就不允许修改,否则会写入失败:
- 后续继续写入已有的数据,数据格式必须一致,因为此时
field datatype已经建立好了; - 后续继续写入新增的
field,是可以成功写入的。