考古|一个索引最多 50 个 nested 字段,ES 2.3 加的限制怎么调

简介: 2.3 迁移文档只有四条:每个索引的 nested 字段数量被限制到 50(可调 index.map……

2.3 迁移文档只有四条:每个索引的 nested 字段数量被限制到 50(可调 index.mapping.nested_fields.limit)、Groovy 依赖从 groovy-all 瘦到 org.codehaus.groovy:groovy、Analyze API 的 filters/token_filters/char_filters 参数改名为单数形式,另外 2.3.0 和 2.3.1 的 CORS 预检请求是坏的,2.3.2 修好。

说明

来源和边界先交代:本文的依据出自 v2.4.6 标签下的官方文件(migrate_2_3.asciidoc、ClusterModule.java、MapperService.java),文中引用的原文均来自这些文件。我没有启动集群、也没有跑测试,示例取自官方示例;哪些是文档写明、哪些是我的推断,正文里逐处标注。

01-migrate_2_3

图 1:官方文件 migrate_2_3.asciidoc(v2.4.6 标签)。来源:GitHub elastic/elasticsearch v2.4.6 migrate_2_3.asciidoc

nested 字段数量上限 50

2.3 迁移文档(migrate_2_3.asciidoc @ v2.4.6)先解释了代价:

Indexing a document with 100 nested fields actually indexes 101 documents as each nested document is indexed as a separate document. To safeguard against ill-defined mappings the number of nested fields that can be defined per index has been limited to 50.

nested 对象在 Lucene 里是独立的文档,所以一个含 100 个 nested 字段的文档,实际索引出 101 个文档。由此可以推断索引的文档数、体积和合并开销都会随 nested 的数量上涨,但文档没有给增长幅度,本文也没有实测。

限制的行为细节:

  • 默认值 50,用索引设置 index.mapping.nested_fields.limit 调整。这个设置是动态的,见 ClusterModule 第 284 行,可以在已有索引上直接改;
  • 只在新建索引或更新 mapping 时检查,2.3 之前创建的老索引,除非改它的 mapping,否则不受影响;
  • 超限时抛 IllegalArgumentException,消息形如 Limit of nested fields [50] in index [xxx] has been exceeded,见 MapperService 第 530–538 行。这段代码还显示,限制值设成负数(allowedNestedFields >= 0 的判断)就等于不限制。

排障时可以记住最后一条:老索引平时没事,某天加个字段就报 nested 超限,原因就是它在更新 mapping 时才检查。

遇到超限的处理思路:

  1. 先看 mapping 设计:我的判断是,不需要查"同一个子对象内多个条件同时成立"的 nested,就该换成 object 或扁平化成多个字段(见后文),这条优先于调 limit;
  2. 确实需要更多 nested:调 index.mapping.nested_fields.limit,同时按"每个 nested 对象都是一份独立文档"估算索引增长;
  3. 若该索引的 nested 字段仍然很多,评估把部分子结构拆成独立索引,或用 parent/child(2.x 里还支持,两者的取舍这里没有展开)。

02-ClusterModule

图 2:官方文件 ClusterModule.java(v2.4.6 标签)。来源:GitHub elastic/elasticsearch v2.4.6 ClusterModule.java

Groovy 依赖瘦身

In previous versions of Elasticsearch, the Groovy scripting capabilities depended on the org.codehaus.groovy:groovy-all artifact. In addition to pulling in the Groovy language, this pulls in a very large set of functionality, none of which is needed for scripting within Elasticsearch. Aside from the inherent difficulties in managing such a large set of dependencies, this also increases the surface area for security issues. This dependency has been reduced to the core Groovy language org.codehaus.groovy:groovy artifact.

groovy-all 会带进来一大堆 ES 脚本根本用不到的功能,管理麻烦且扩大安全攻击面,所以 2.3 换成核心语言包 org.codehaus.groovy:groovy。

影响面:文档没有列出被去掉的具体类。可以推断,脚本里如果用到了 groovy-all 附带但核心包里没有的类,升级后会找不到类。结合 2.2 引入的 Security Manager 类白名单(见 2.2 的 geo_point 与脚本沙箱),脚本本来能加载的类就不多,碰上的可能性不大,具体是否受影响要靠把脚本跑一遍确认。

03-MapperService

图 3:官方文件 MapperService.java(v2.4.6 标签)。来源:GitHub elastic/elasticsearch v2.4.6 MapperService.java

Analyze API 参数改名

The deprecated filters/token_filters/char_filters parameter has been renamed filter/token_filter/char_filter. And filters/token_filters/char_filters has been removed in 5.0.0

复数的旧参数名被废弃,改用单数:filter、token_filter、char_filter。复数形式在 5.0.0 才删除,2.3 期间是"能用但已废弃"的过渡期。我的建议是别等过渡期:调用 _analyze 的脚本或工具,在升到 5.0 之前就改掉。

2.3.0 与 2.3.1 的 CORS 问题

这一条是已知缺陷而不是设计变更:

A change to the CORS implementation means that CORS support for pre-flight OPTIONS requests is broken in 2.3.0 and 2.3.1. This is fixed in 2.3.2.

预检 OPTIONS 请求在这两个版本里不工作。浏览器直连 ES 做跨域调用时,预检失败后后续请求发不出去,这是对预检作用的推断,文档只说了"broken"。建议不要停在 2.3.0 和 2.3.1,直接上 2.3.2 或更高。

三条改动各自的指向

文档给出的理由:nested 限制是防止"定义不当的 mapping",Groovy 瘦身是减依赖和攻击面,Analyze 参数改名为 5.0 移除旧名字做准备。升级时需要主动检查的是 nested 字段数量,因为它只在改 mapping 时才暴露。

nested 用多了,问题不在 50 这个数

限制 50 个 nested 字段的目的是防 mapping 失控,调大之前先想想建模对不对。

文档那句"100 个 nested 字段实际索引出 101 个文档"是关键:nested 越多,Lucene 里的文档越多,索引体积和 segment 合并的开销随之增加。把业务对象里所有子结构都设成 nested,调大 index.mapping.nested_fields.limit 只是让这个问题更大。

判断标准是:nested 只用在需要查询子对象内部关系的地方,例如"找出同时满足 A 和 B 的商品规格"。只是存着一起展示的话,用 object 就够了,代价是失去内部对应关系;只是按子字段过滤的话,扁平化成多个普通字段更简单。

怎么查现在用了多少 nested

curl -s 'localhost:9200/_all/_mapping' > /tmp/m.json
python3 -c "
import json
m=json.load(open('/tmp/m.json'))
for idx,body in m.items():
    n=0
    def walk(p):
        global n
        for f,d in (p or {}).items():
            if d.get('type')=='nested': n+=1
            walk(d.get('properties'))
    for t,td in (body.get('mappings') or {}).items(): walk(td.get('properties'))
    if n: print(idx, 'nested 字段数 =', n)
"

跑完对照 50 这个上限,超了或接近的按上面三条路处理。这个检查要在改 mapping 之前做:限制只在新建或更新 mapping 时触发,等报错再查,变更就已经卡住了。

nested 的替代方案:object 与扁平化

既然限制的本质是"nested 太贵",那把替代方案写清楚更有用。三种建模对比:

// 1) nested:子对象独立成文档,可查询内部关系,索引开销最大
{
    "specs": {
    "type": "nested", "properties": {
    "k": {
   "type":"keyword"}, "v": {
   "type":"keyword"} } } }

// 2) object:普通对象,ES 会扁平化存储,开销小,但失去内部对应关系
{
    "specs": {
    "type": "object", "properties": {
    "k": {
   "type":"keyword"}, "v": {
   "type":"keyword"} } } }

// 3) 扁平化成多字段:按用途直接展开,写法最简单
{
    "spec_color": {
   "type":"keyword"}, "spec_size": {
   "type":"keyword"} }

选择依据只有一条:要不要查询"同一个子对象内部的多个条件同时成立"。要(比如"颜色是红且尺寸是 L 的规格"),只能用 nested;不要,用 object 或扁平化。这个判断可以放到业务层面问:产品会不会按规格组合筛选,会就上 nested 并控制数量,不会就用 object。

object 的数组会失去内部对应,也就是"交叉匹配"。[{k:color,v:red},{k:size,v:L}] 用 object 存,查 "k=color 且 v=L" 会命中,即使它们属于不同的元素。这是 nested 存在的理由,选 object 之前要确认业务能接受这个误差。

调阈值前先算一笔账

如果确实要调 index.mapping.nested_fields.limit,先把增长算清楚:

实际索引文档数 ≈ 主文档数 × (1 + nested 子文档平均数)

假设平均每个文档有 30 个 nested 子文档,100 万条业务数据会变成约 3100 万个 Lucene 文档。这是按文档那句话推出来的数量级,没有实测。拿这个数字去估算磁盘、段数量和查询延迟,再决定调不调。建议是把它当成一次容量评估,而不是改个配置:算完通常会发现该改的是建模,不是把阈值从 50 调到 200。

写在最后

  • nested 对象是独立的文档,用之前先算增长;
  • 50 是对 mapping 设计的提醒,调大之前先想建模对不对;
  • 限制只在改 mapping 时检查,老索引可能平时没事,某天加字段就报错,主动查一遍比等报错强。

Groovy 瘦身和 Analyze 参数改名两条不影响多数业务代码:前者减依赖和攻击面,后者是 5.0 移除旧名字的前置。CORS 那条:不要停在 2.3.0 和 2.3.1,直接上 2.3.2。

参考

本文对照 Elasticsearch v2.4.6 标签的官方文档核对,引文逐字取自原文。撰写时未启动集群、未运行测试。

目录
相关文章
|
4天前
|
固态存储 Java 测试技术
Elasticsearch 6.0.0本地单机16核32G压测报告
Elasticsearch 6.0.0本地单机16核32G压测报告
82 28
|
开发者
乘风者之星来啦!发文享阿里内推机会和50W流量曝光!
乘风者计划特推出乘风者每周之星活动,期待各位博主的参与,成为“每周之星”,上榜者可获得官方流量扶持
19070 3
乘风者之星来啦!发文享阿里内推机会和50W流量曝光!
|
开发工具
新人乘风者礼品兑换指南
仅限2023年11月15日(含11月15日)后入驻博主用于兑换礼品,此前完成入驻的博主按原邮寄方式进行。
5027 9
|
开发者
2024 乘风者计划全新启航!快来加入吧!
 2021年,阿里云开发者社区焕新升级,重磅推出“乘风者计划”!诚邀四海技术博主入驻社区,泼墨云间,书写天地。入驻社区,即可享丰厚权益! 新的一年,乘风者计划重磅升级!
252670 81
|
开发者 黑灰产治理
阿里云开发者社区积分细则
阿里云开发者社区,积分规则、领取、过期等相关说明
4224 20
|
开发者 程序员
阿里云开发者档案能力鉴定说明
开发者能力鉴定是通过“技能测试”(通用技术能力)以及“阿里云认证”(阿里云解决方案能力)两个模块对您的技术能力进行一个较为全面的评估。
99423 8
|
9月前
|
Kubernetes 应用服务中间件 API
应对 Nginx Ingress 退役,是时候理清这些易混淆的概念了
本文希望提供一种更简单的方式,来理解这些容易混淆的技术概念:Nginx、Ingress、Ingress Controller、Ingress API、Nginx Ingress、Higress、Gateway API。
4371 191
|
20小时前
|
存储 安全 Java
考古|ES 2.2 升级要查三处,geo_point 格式、脚本沙箱和 field stats
考古|ES 2.2 升级要查三处,geo_point 格式、脚本沙箱和 field stats
25 0
|
20小时前
|
运维 安全 API
ES 的 Transform 通知索引兼容别名不再自动创建了
ES 的 Transform 通知索引兼容别名不再自动创建了
24 0
|
1天前
|
Java 测试技术 Linux
Elasticsearch 7.10 向量检索插件opendistro knn性能测试
Elasticsearch 7.10 向量检索插件opendistro knn性能测试
33 1