考古|ES 5.4 和 5.5 的几条零散变更,delete-by-query 要显式写 query

举报
岳涛 发表于 2026/11/14 08:33:20 2026/11/14
【摘要】 5.4 只有一条:delete-by-query 不带显式 query 的请求被废弃(6.0 起非法)。

5.4 只有一条:delete-by-query 不带显式 query 的请求被废弃(6.0 起非法)。5.5 有三条:扫描插件目录时不再跳过隐藏文件;ES_SKIP_SET_KERNEL_PARAMTERS 环境变量移除,改用 mask systemd-sysctl 服务;has_child、has_parent、parent_id 查询和 children 聚合移到 parent-join 模块。四条都不难,麻烦在它们散在两个小版本的迁移文档里,容易被漏。本文按 v5.6.16 补上 vm.max_map_count 检查的实现细节与这四条的可执行排查。

说明

实测环境:本地起的 Elasticsearch 5.6.16 单节点实例(官方 tar 包直接跑,堆内存 512MB,机器学习关闭),curl 连本地 9200 端口。本文「实测」小节的命令和输出都出自这个实例。

来源和边界先交代:本文的依据出自 v5.6.16 标签下的官方文件(migrate_5_4.asciidoc、migrate_5_5.asciidoc、BootstrapChecks.java),文中引用的原文均来自这些文件。示例取自官方示例;哪些是文档写明、哪些是我的推断,正文里逐处标注。

01-migrate_5_4

图 1:官方文件 migrate_5_4.asciidoc(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 migrate_5_4.asciidoc

delete-by-query 必须带 query

5.4 迁移文档(migrate_5_4.asciidoc 第 4–8 行)只有一句:

Requests without an explicit query are deprecated and will be invalid in Elasticsearch 6.0.0.

不带 query 的 delete-by-query 等价于 match_all——删掉整个索引的全部文档。允许这种写法意味着少写一个字段就会清空数据,所以要求显式写出查询:

POST my_index/_delete_by_query
{
  "query": { "match_all": {} }
}

迁移时找一遍请求体为空的 _delete_by_query,补上 match_all。

02-BootstrapChecks

图 2:官方文件 BootstrapChecks.java(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 BootstrapChecks.java

插件目录不再忽略隐藏文件

Previous versions of Elasticsearch would skip hidden files and directories when scanning the plugins folder. This allows broken plugin installs to remain undetected so this leniency has been removed.

(migrate_5_5.asciidoc 第 4–10 行)

以前扫描插件目录会跳过多号开头的隐藏文件和目录(比如 .DS_Store、解压残留的 ._xxx、编辑器临时目录)。这让"插件装坏了"这件事不被发现——坏掉的插件目录可能被整段跳过。

官方对移除理由的说法就是"让坏掉的插件安装不被发现",没有展开到具体会报什么错。所以 5.5 起这类残留会被当成插件目录处理,可能出现启动失败。处理方式就是清理:

ls -la /usr/share/elasticsearch/plugins/

看到 .DS_Store、.tmp 之类删掉即可。macOS 上解压插件包最容易带出 ._ 开头的 AppleDouble 文件,这类文件在 Linux 上解压同样可能出现(包是 macOS 打的)。

03-migrate_5_5

图 3:官方文件 migrate_5_5.asciidoc(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 migrate_5_5.asciidoc

ES_SKIP_SET_KERNEL_PARAMTERS 移除

Elasticsearch attempts to set the kernel parameter vm.max_map_count during installation. In some environments this can fail. Previous versions of Elasticsearch accepted a flag ES_SKIP_SET_KERNEL_PARAMTERS to skip this writing. Starting in Elasticsearch 5.5.0, this flag has been removed and instead the same functionality can be achieved by masking the systemd-sysctl service.

(migrate_5_5.asciidoc 第 12–21 行)

5.5 起这个变量没了,官方给的替代是 mask systemd 的 sysctl 服务:

systemctl mask systemd-sysctl.service

但 mask 只是让"ES 不去改系统参数"这件事能实现,参数本身还得有人设。

vm.max_map_count 为什么绕不开

ES 用 mmap 读索引文件,每个 segment 的每个文件都占一个 mmap 区域,索引稍大就可能不够。5.6.16 里这是一项 bootstrap check(BootstrapChecks.java 第 198、401–416 行):

static class MaxMapCountCheck implements BootstrapCheck {
    private static final long LIMIT = 1 << 18;  // 262144
    ...
    return String.format(Locale.ROOT,
        "max virtual memory areas vm.max_map_count [%d] is too low, increase to at least [%d]", ...);

LIMIT 是 1 << 18,即 262144。官方文档的要求一致(bootstrap-checks.asciidoc 第 152–157 行):内核至少要允许 262144 个内存映射区,且只在 Linux 上强制;通过办法是用 sysctl 把 vm.max_map_count 设到至少 262144。

启动日志里常见的一条就是它(getting-started.asciidoc 第 243 行):

max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]

65530 是 Linux 上的默认值,不是 ES 设的值。这条在开发模式下是 WARN,生产模式下是启动失败。

所以更推荐的做法是直接在宿主机把这个参数设好,而不是想办法跳过:

# 在宿主机上设
echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
sysctl -p

# 核对
sysctl vm.max_map_count

绕开设置步骤不等于绕开这个要求。 mask systemd-sysctl 让 ES 不去改系统参数,但 bootstrap check 仍然会读 /proc/sys/vm/max_map_count 判断,参数没设好照样过不了启动检查。

父子查询移到 parent-join 模块

The has_child, has_parent and parent_id queries have moved to parent-join module. Also the children aggregation has been moved to the parent-join module. The parent-join module should be loaded when using the transport client. Alternatively use the PreBuiltTransportClient client, which load the module automatically.

(migrate_5_5.asciidoc 第 23–30 行)

对服务端没影响(模块照常加载),影响的是 transport client:如果自己拼客户端模块列表,就必须把 parent-join 模块加载进客户端,否则解析或传输这些查询时会报未知查询。用 PreBuiltTransportClient 则自动加载,不用管。

这也是 5.0 引入 PreBuiltTransportClient 的用意之一:客户端需要注册的 NamedWriteable(查询、聚合等)越来越多,手工维护不现实。它预装的插件里就包含 ParentJoinPlugin。

为什么要在意 delete-by-query 的空查询

这条看着只是"多写一行",实际防的是一类真实事故。不带 query 等价于 match_all,也就是删掉整个索引的全部文档。允许这种写法,意味着下面几种情况都能造成数据清空:

  • 客户端拼请求时漏了 query 字段(序列化时字段为 null 被省略);
  • 脚本里变量为空,请求体变成 {};
  • 复制粘贴一段清理代码,忘了改查询条件。

而且这类请求没有任何确认步骤——发出去就执行。要求显式写 match_all,本质是让你在写这行的时候意识到"我要删全部"。

清理脚本里建议加一道保护:先跑一次同条件的 _search 看命中数量,确认无误再执行删除。这一道检查比任何事后恢复都便宜。重要索引删除前留快照,这条规则跟版本无关。

这四条的共同点是把"静默容忍"改成"明确处理"

  • delete-by-query 空查询:以前容忍(等价于删全部),现在要求写明;
  • 插件目录隐藏文件:以前跳过(装坏的插件不被发现),现在不再忽略;
  • 内核参数:以前可以用环境变量跳过设置,现在要求真的设好(或者明确 mask 掉);
  • 父子查询:模块归属明确化,要用就显式加载。

这个方向在 5.x 期间反复出现——内存锁定、文件描述符、系统调用过滤器那批 bootstrap check 也是同一回事。理解这一点之后,再遇到"以前能用、现在报错"的条目,先想是不是"静默容忍被取消":多数是这个原因,而不是功能真的变了。

对应地,排查小版本变更的手段也要跟着变:既然它们不在同一份迁移文档里,靠逐条读文档成本高、收益低(一份文档可能上百条,你用到的只有几条,而且调用点可能埋在依赖库里)。deprecation 日志是"你真的触发了"才产生的,按 key 聚合、按次数排序,排在最前面的就是最该处理的,它天然做了过滤。

两个细节:聚合要在测试环境跑过业务流量之后做(光启动集群不产生足够的日志);出现次数少的也不要全忽略,有些改动一年只触发一次(比如某个季度任务),但触发时就是故障。

散点改动的排查命令

# 1) delete-by-query 是否带查询
grep -rn "_delete_by_query" --include=*.sh --include=*.py --include=*.java . | head -20

# 2) 插件目录里的隐藏文件
ls -la /usr/share/elasticsearch/plugins/

# 3) 内核参数当前值(低于 262144 就该在宿主机设好)
sysctl vm.max_map_count

# 4) 父子查询的使用点(涉及模块归属)
grep -rn "has_child\|has_parent\|parent_id\|"children"" --include=*.java . | head -10

第 3 条最容易被忽略:它直接给出当前值,不用等启动时被 bootstrap check 拦下来才发现。

升级检查清单

  1. _delete_by_query 请求体补上显式 query;
  2. 清理插件目录里的隐藏文件与解压残留;
  3. 去掉 ES_SKIP_SET_KERNEL_PARAMTERS,改为正确设置 vm.max_map_count(至少 262144)或 mask systemd-sysctl;
  4. 自拼模块的 transport client 加上 parent-join 模块,或换成 PreBuiltTransportClient;
  5. 重启后核对 deprecation log,确认这些点都清干净了。

在 5.6.16 上跑一遍

先写 3 篇文档,然后发两次 delete-by-query:

delete-by-query

图 4:本地 5.6.16 单节点实例上的实测输出。上:显式写 query;下:请求体留空

curl -s -X POST "" \
  -H 'Content-Type: application/json' -d '{"query":{"term":{"f":"v1"}}}' \
  | jq -c '{total, deleted}'

curl -s -X POST "" \
  -H 'Content-Type: application/json' -d '{}' | jq -c '{total, deleted}'

第一次 {"total":1,"deleted":1},只删了匹配的那条。第二次请求体是 {},{"total":2,"deleted":2}——剩下两条全被删掉了。

也就是说请求体留空被当成"不加过滤",默认删全部。这条最危险的地方在于它不报错:响应是 200,deleted 是多少就删了多少,只有回头数文档数才发现不对。

结论是:delete-by-query 的调用处必须强制要求请求体里有 query,空 body 直接拒绝在客户端那一侧。脚本里如果允许"不传过滤条件",等于留了一个删库的开关。

写在最后

5.4 / 5.5 这四条散在两个小版本里,都不难,麻烦在容易被漏:它们不在同一份迁移文档里,也没有集中的地方告诉你"这次变了什么"。

方向是一致的——把静默容忍改成明确处理。我的判断是其中最实际的一条是 vm.max_map_count:mask systemd-sysctl 只是让 ES 不去改系统参数,参数本身还得设到至少 262144,否则生产模式下照样起不来。

散点改动最适合的发现方式是 deprecation 日志:升级后在测试环境跑一遍业务流量,再按 key 聚合一次,按出现次数排优先级。每次升级都做,不会漏。

参考

本文对照 Elasticsearch v5.6.16 标签的官方文档核对,引文逐字取自原文。图 4 的两次 delete-by-query 来自本地 5.6.16 单节点实例。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。