突破 ES 引擎局限性在用户体验场景中的优化实践

回顾:ES 慢上游响应问题优化在用户体验场景中的实践-CSDN博客

上文介绍了用户体验管理平台(简称 VoC)在针对 ES 慢上游响应场景下的优化实践,本文继续介绍针对第二个痛点问题——ES 引擎局限性的性能优化实践进行介绍。

下文以搜索引擎无法替换(存量数据太大,数据迁移成本高)、ES 查询性能配置无法修改(ES 集群配置有限且公司对部分参数配置有限制)、SkyNet 性能无法优化(后续逐步优化,不在本次实践进行)为前提设计方案进行性能优化,即在最坏的场景下寻找到最优解,降低后续进一步优化的压力。

ES 引擎局限性

上文有提到,ES 的局限性本质上是 Bucket 限制的问题,因此突破 ES 引擎局限性的关键在于如何在满足 ES Bucket 限制的前提下更高效地进行 ES 查询。

优化措施一:查询拆分

既然一次查询产生的桶数会超出 ES 聚合能力的上限,那最简单的方案就把请求拆分,拆分后的请求每一个都能够满足 ES 的性能限制,多个请求得到的数据在进行二次整合即可。但这就引入了另一个问题,请求该以什么标准进行拆分?

对于对比维度枚举已知且数量不大的场景,我们可以按照对比维度进行请求拆分,每次只进行单个枚举值下的是时间直方图聚合,Query DSL 如下所示:

请求1:
GET /***/_search
{"query": {"term": {"FIELD": {"value": "VALUE_1"}}},"aggs": {"eredar_create_timestamp_ms": {"date_histogram": {"field": "TIME_FIELD","interval": "day","time_zone": "+08:00","order": {"_key": "asc"},"min_doc_count": 0}}}
}请求2:
GET /***/_search
{"query": {"term": {"FIELD": {"value": "VALUE_2"}}},"aggs": {"eredar_create_timestamp_ms": {"date_histogram": {"field": "TIME_FIELD","interval": "day","time_zone": "+08:00","order": {"_key": "asc"},"min_doc_count": 0}}}
}

对于对比维度为复合条件的,例如一个映射标签对应了不用渠道下的多个原始标签的场景,可以按照映射标签为进行拆分,Query 中定义映射标签的符合条件即可,Query DSL 如下所示:

请求1:
GET /***/_search
{"query": {**复合条件1**},"aggs": {"eredar_create_timestamp_ms": {"date_histogram": {"field": "TIME_FIELD","interval": "day","time_zone": "+08:00","order": {"_key": "asc"},"min_doc_count": 0}}}
}请求2:
GET /***/_search
{"query": {*复合条件2**},"aggs": {"eredar_create_timestamp_ms": {"date_histogram": {"field": "TIME_FIELD","interval": "day","time_zone": "+08:00","order": {"_key": "asc"},"min_doc_count": 0}}}
}

显然方案一的实现十分简单,是针对 Bucket 聚合限制最直接粗暴的解决方案,可以保证每次请求都满足 ES 性能限制要求。但是,这个方案也存在以下几个明显问题需要解决:

  • 将 ES 的查询压力转换成了 Http 请求压力,虽然这里也采用了多线程处理,但是多次请求产生的时延仍导致最终接口的响应时间没有明显的下降,部分场景(对比维度数目过大)甚至有恶化;

  • 需要排序的场景,查分后的查询无法利用 ES 提供的排序能力,产生额外逻辑负担;

  • 对于无法预先掌握对比维度枚举的场景,无法直接做请求拆分。

优化措施二:m_search 引入

为了应对查询拆分带来的大量请求压力,我们采用 ES 中的 m_search 代替 Search,将多个 Search 请求合并为一个 m_search 请求,极大地减少了拆分后的请求数量。Query DSL 如下所示:

GET /***/_msearch
{}
{"query": {"term": {"FIELD": {"value": "VALUE_1"}}},"aggs": {"eredar_create_timestamp_ms": {"date_histogram": {"field": "TIME_FIELD","interval": "day","time_zone": "+08:00","order": {"_key": "asc"},"min_doc_count": 0}}}
}
{}
{"query": {"term": {"FIELD": {"value": "VALUE_2"}}},"aggs": {"eredar_create_timestamp_ms": {"date_histogram": {"field": "TIME_FIELD","interval": "day","time_zone": "+08:00","order": {"_key": "asc"},"min_doc_count": 0}}}
}

通过引入 m_search 方法,可以有效地降低拆分后请求数量激增带来的时延,整个流程仍能保证一次请求即可完成全部所需数据的查询,接口响应速度得到有效提升。但是 m_search 一次性处理多个查询的底层运行逻辑实际上是每个查询独立运行,这就会导致全量反馈数据会被遍历多次,这个环节会严重浪费 ES 资源,仍存在较大的优化空间。

优化措施三:Filters 聚合

为了解决 m_search 中多次查询中数据实际上是进行了多次遍历这个痛点,我们试图寻找到一种能够一次遍历就能完成全部聚合的方法,这种想法看起来兜兜转转又回到了问题的原点,我们怎么能在一次请求中既满足ES的性能瓶颈限制又能够尽可能多的聚合出数据呢?

回过头来再分析查询拆分后的每一个查询的特点,目标数据的过滤是依赖 Query 中的 Terms 完成的,Terms 语句限制了待聚合的数据只有该对比维度枚举下的数据,如果在 Terms 中将数据范围限制为多个对比维度枚举下的数据范围,那么在聚合逻辑中就没办法去分辨数据到底属于哪一枚举。因此,基于 Query 中的 Terms 来约束数据并想要一次查询完成多个对比维度枚举的聚合通过这种方式看起来是不可能的。

调研后发现,Filters 聚合可以很好地解决这个问题:Filters 聚合是一种多过滤聚合,将过滤条件下移到聚合过程中,基于多个过滤条件来对当前文档进行过滤聚合,Query DSL 如下所示:

GET /***/_search
{"query": {**其他过滤条件**},"aggs": {"FIELD": {"filters": {"filters": {"FIELD_1": {**对于FIELD_1的过滤条件**},"FIELD_2": {**对于FIELD_2的过滤条件**}}},"aggs": {"eredar_create_timestamp_ms": {"date_histogram": {"field": "TIME_FIELD","interval": "day","time_zone": "+08:00","order": {"_key": "asc"},"min_doc_count": 0}}}}}
}

通过将对比维度枚举间不同的过滤条件从 Query 下沉到 aggs 中,可以让 ES 在聚合过程中也能清楚地知道一条数据属于哪一个对比维度枚举,同时由于 Filters 聚合支持 Bool 类的复合查询结构,这种聚合的扩展性与适用性和 Query 中的 Terms 相比是不分伯仲的。

那么基于 FIlters 聚合再考虑到 ES 性能瓶颈的问题,我仍然使用到了查询拆分和 m_search 的方案:对于一次复杂场景的指标趋势图计算,首先计算时间维度上的桶数目,然后根据这个桶数目动态地调整每次查询要聚合出的对比维度数目,例如此时统计周期为 6 个月、小时时间粒度下,那么每个请求中的 Filter 聚合就不超过 4 个,按照这种拆分方式进行请求拆分,再通过 m_search 一次性请求即可。这里为了避免上述流程对简易聚合场景反而产生性能劣化,对不触发性能瓶颈问题的接口请求仍然走原有的 Search 逻辑。

动态调整 Filters 聚合的使用,在保证不触发 ES 性能限制的前提下,最大化了 m_search 中每一个请求能够提供的对比维度聚合能力,充分利用每一块资源,做到了小请求保速度、大请求保稳定,将原有一些极端场景下无法完成的聚合变为可能,成功拓展了平台的极端场景分析能力。至此,对比维度枚举已知场景的痛点问题已解决。

优化措施四:DFS 转 BFS(字节跳动特征服务管理)

在不知道目标对比维度有哪些枚举值时,Filters 聚合完全没办法发挥作用,而通过 Terms 来进行聚合时,ES 中的 Terms 聚合会基于我们的数据动态构建桶,但是我们并不知道这次聚合到底生成了多少桶,一旦使用嵌套聚合,就很可能产生大量的分组,最终导致 Bucket 超限或是 OOM 情况的发生。

结合 VoC 平台功能分析,实际上我们提供的看板数据一般来说只有 TOP 5 或 TOP 10 的趋势,此外的聚合数据对于 VoC 来说其实是无效数据。再分析 ES 的聚合算法发现。ES 的聚合是默认 DFS 的,也就是说在某对比维度反馈量 TOP 10 趋势图接口下的 ES 聚合逻辑如下所示:

默认情况下,ES 会先根据嵌套聚合定义的聚合逻辑构建出完整的树,然后再去剪枝掉无关节点。在我们 TOP 10 的场景中,就意味着实际上除了 TOP 10 枚举及其子聚合,其余枚举及其子聚合桶的构建浪费了大量的内存与计算性能,所以针对这种不知道目标对比维度有哪些枚举值的场景,可以改用特征服务管理(BFS) 来进行性能优化。

BFS 下的查询主要分为两步:首先进行 TOP 10 问题的聚合,此时为单个字段聚合,不添加嵌套,目的是过滤出反馈量 TOP 10 的枚举值;此时就从不知道目标对比维度有哪些目标枚举转换为了上文中我们已经完成性能优化的场景,再进行 Filter 聚合&查询拆分即可。实际上 ES 有控制 DFS 或 BFS 的 Terms 参数 collect_mode,但经过实验该参数并没有按照预期效果生效,这一点还需要在后续的实践过程中进一步进行探索。从 DFS 到 BFS 的转变,解决了对比维度枚举不明确场景下的聚合查询,也保证了接口的稳定性与可用性。

最终方案总览

综上,针对 ES 引擎局限性的解决方案如下图所示:

最终方案是上述四种方案在不同场景下组合运用的结果:

  • 非对比场景:无需嵌套聚合,直接走最简单的 Search 逻辑,保证效率;

  • 对比维度有限且单次查询满足 ES 性能限制场景:Search 查询即可,减少不必要的复杂逻辑;

  • 对比维度有限且单次查询超出 ES 性能限制场景:Filter 聚合基础上的查询拆分与 m_search,将查询拆分成满足 ES 性能限制且聚合效率最大化的多个查询,最后二次计算整合查询结果即可,保证极端场景下接口的可用性与高效性;

  • 对比维度无限场景:单次聚合获取 TOP 枚举值,利用 DFS 转 BFS 思想,将场景转换为对比维度有限场景,降低场景复杂度,减少不必要的性能开销。

整体收益分析

  • 长统计周期&小时间粒度&多对比维度下反馈量趋势

优化前极端场景(例如 6 个月统计周期、小时时间粒度、六级业务标签对比)下处于不可用状态,必然触发 ES 的 OOM 或是 2min 接口超时。优化后极端场景也能稳定响应,平均耗时 14s(优化前即使不做任何对比维度聚合也需要 5s 左右)。普通场景优化前后耗时如下所示,响应速度提升 86.6%

  • 对比维度枚举未知场景下反馈量趋势

优化前极端场景下同样处于不可用状态。优化后极端场景也能稳定响应,平均耗时 5.8s。普通场景优化前后耗时如下所示,响应速度提升 7%。这里提升不明显的主要原因是基本场景下的优化前的聚合查询是一次性完成的,优化后因为 BFS 的原因是分两次进行查询的。

  • 关键指标

用户进入VoC 平台首先看到的就是关键指标中的两个指标:反馈总量和反馈变化趋势,这里的性能分析是屏蔽了请求参数缓存进行的,目的是在于测试非高频场景下的性能优化结果:

反馈总量接口响应随着时间周期的增大性能提升更为明显,且基本稳定在 3.2s 左右;反馈趋势接口性能优化接近 80%,提升显著,但是当时间周期增加到 6 个月时性能会发生劣化,推测跟 Filters 聚合的过滤条件下沉有关,这一部分还需要在后面的实践中进一步分析确认。

  • 预缓存效果

以上收益分析均建立在 VoC 接口未添加预缓存的条件下进行,目的是为了验证低频查询条件下的页面性能,实际上添加预缓存后的接口响应均在 ms 数量级内,这里就不再详细分析。

总结&思考

  • 场景决定方案

ES 引擎局限性解决过程中,不同方案在不同场景下的收益是截然不同的,并非简单的就性能差、复杂的就性能好,要针对场景,具体问题具体分析,拒绝差不多思维,也拒绝过度设计。

  • 居安思危,保持思考

Filter 聚合的使用极大地提升了接口的性能,然而这种 aggs 中的过滤实际上是对每一个桶的单独过滤,原本的 Query 中的过滤对于一次查询是全局过滤,这种聚合过滤导致的过滤条件下沉可能会造成聚合上的性能下降。虽然目前经过实验对比,下沉前后的 ES 响应速度基本一致,查阅相关资料显示 ES 有对这种下沉场景做了性能优化,但是随着数据量进一步增大以及对比场景愈发复杂,能否保证 Filters 聚合性能不劣化还有待考证。

  • 追求极致

日常需求开发中总是说要保证敏捷也要保证质量,但实际上有关质量我们更多地是着重在稳定、可用而非性能。虽然经过这次优化,接口的性能得到了很大程度的提升,但是很多复杂场景的接口响应耗时仍无法突破 1s 大关。性能的劣化非一日之功,优化也非一蹴而就,只有在日常需求中保持性能上追求极致的意识,才不用一直背负性能优化的历史债务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395213.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SSM美美电影院选座订票微信小程序-计算机毕业设计源码15838

美美电影院选座订票微信小程序 摘 要 美美电影院选座订票微信小程序是一个集在线选座和购票于一体的平台,旨在为用户提供便捷的观影体验。该小程序以其实时更新的座位图和多样化的支付方式而受到用户的喜爱。 首先,美美电影院选座订票微信小程序提供了直…

使用CLIP模型进行零样本图像分类的分步指南

零样本学习允许AI系统对未明确训练过的类别进行图像分类,标志着计算机视觉和机器学习的重大进步。本文将介绍使用CLIP实现零样本图像分类的详细分步指南,从环境设置到最终的图像处理和分类。我们首先介绍零样本学习的概念及其在现代AI应用中的重要性。然后深入探讨CLIP模型的概…

PostgreSQL的学习心得和知识总结(一百五十)|[performance]更好地处理冗余 IS [NOT] NULL 限定符

目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库…

低代码之殇

低代码的浪潮已经持续几年了,很多声音冒出来,其中最刺耳就是:低代码就是个伪命题,根本不可能用低代码开发业务系统,尤其是复杂的业务系统; 更有甚者,直接给给低代码贴了标签:骗子 …

linux常见性能监控工具

常用命令top、free 、vmsata、iostat 、sar命令 具体更详细命令可以查看手册,这里只是简述方便找工具 整体性能top,内存看free,磁盘cpu内存历史数据可以vmsata、iostat 、sar、iotop top命令 交互:按P按照CPU排序,按M按照内存…

trie算法

1、定义 高效的存储和查找字符串集合的数据结构 它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高 2、构建 我们可以使用数组来模拟实现Trie树。 我们设计一个二维数组 son[N] [26] 来…

鸿蒙HarmonyOS开发:@Observed装饰器和@ObjectLink装饰器:监听嵌套类对象属性变化

文章目录 一、装饰器二、概述三、限制条件四、装饰器说明五、Toggle组件1、子组件2、接口3、ToggleType枚举4、事件 六、示例演示1、代码2、效果 一、装饰器 State装饰器:组件内状态Prop装饰器:父子单向同步Link装饰器:父子双向同步Provide装…

SpringBoot MybatisPlus selectOne的坑

目录 一、问题 二、问题解决 三、其他方法 一、问题 selectOne在查询多条数据时会报错,查询语句并不会加 limit 1。 One record is expected, but the query result is multiple records。 二、问题解决 在QueryWrapper上添加如下: QueryWrapper&…

支付宝开放平台竟出现一张神秘人脸!

前言 ​ 我因一个单子来到支付宝开放平台来。在将其加入书签的时候,我发现出现了个神秘的人脸 一张笑容明媚的脸,就是出现的时候不太对 正常的收藏网址 应该是显示对应log 就不继续找相关例子了 ​ 添加书签的页面,本该出现log的地方缺出现了…

VScode的环境编译器选择

按快捷键 Ctrl Shift P 选择即可

反向传播与梯度累积

反向传播算法:loss.backward()的实现细节 向前传播:输入数据得到预测结果。向后传播:计算梯度加更新参数。反向传播:计算梯度 计算图 计算图 有向无环图 基本运算 节点:变量节点 & 计算节点有向边&#xff1…

【LeetCode】48. 旋转图像

旋转图像 题目描述: 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1: 输入:matrix …

【维修经验分享】可调电源输出不稳定

一、前言 从今天这期开始,我将在“维修经验”这个专栏中分享一些简单的电路板维修经历,希望能帮助大家。 二、相关信息及问题 型号:迈胜MS-3050。 问题:输出电压无法稳定下来,一直在跳动。这款电源估计也比较老了&…

Linux系统之ls命令的基本使用

Linux系统之ls命令的基本使用 一、ls命令介绍二、ls命令的使用帮助2.1 命令格式2.2 命令选项2.3 使用帮助 三、ls命令的基本使用3.1 列出当前目录中的所有文件和目录3.2 列出指定目录中的所有文件和目录3.3 显示文件的详细信息3.4 列出所有文件和目录3.5 显示目录本身&#xff…

【单片机毕业设计选题24105】-基于单片机的自动配料机控制系统

系统功能: 系统分为自动状态和手动状态上电默认为手动状态,手动状态下可以通过按键和蓝牙手动控制步进电机正反转, 自动状态下根据采集到的两路接近传感器信号自动控制步进电机正反转。 系统上电后,OLED显示“欢迎使用请稍后”&#xff0c…

C++17常用新特性介绍

目录 1、结构化绑定 2、constexpr扩展 2.1、constexpr lambda 2.2、constexpr if 2.3、constexpr string 4、if with initializer 5、std::optional 6、使用inline定义内联变量 7、std::filesystem库 8、折叠表达式 9、模板的模板参数推导 9.1、从构造函数参数推导…

前端获取视频文件宽高信息和视频时长

安装 yarn add video-metadata-thumbnails | npm install video-metadata-thumbnails引入依赖包 import { getMetadata } from video-metadata-thumbnails使用 if (file.name.includes(mp4)) {if (file) {try {console.log(file)// 获取视频的元数据const metadata await …

【微信小程序开发】——奶茶点餐小程序的制作(一)

👨‍💻个人主页:开发者-曼亿点 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 曼亿点 原创 👨‍💻 收录于专栏&#xff1a…

51单片机—智能垃圾桶(定时器)

一. 定时器 1. 简介 C51中的定时器和计数器是同一个硬件电路支持的,通过寄存器配置不同,就可以将他当做定时器或者计数器使用。 确切的说,定时器和计数器区别是致使他们背后的计数存储器加1的信号不同。当配置为定时器使用时,每…

数据结构的基本概念

数据结构的基本概念 数据是什么? 数据 : 数据是信息的载体,是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别(二进制0|1)和处理的符号的集合。数据是计算机程序加工的原料。 早期计算机处理的…