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

news2024/9/23 3:25:09

回顾: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.coloradmin.cn/o/1996880.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MaxKB:基于 LLM大语言模型的知识库问答系统实操

1.MaxKB介绍 MaxKB 是一款基于 LLM(Large Language Model)大语言模型的知识库问答系统。MaxKB 的产品命名内涵为 “Max Knowledge Base”,为用户提供强大的学习能力和问答响应速度,致力于成为企业的最强大脑。与同类基于 LLM 的知…

数值分析【4】

目录 ​编辑第六章 数值积分微分 龙贝格 高斯求积 查表? 插值求导 两点 ​编辑 三点​编辑 第七章 ode 龙哥库塔 线性多步法 第八章 eig 幂法:v-》Av-》AAv-》……​编辑 反幂法 每次成得是A逆,这样得到摸最小的特征值​编辑 Q…

【IEEE独立出版】第四届计算机科学与区块链国际学术会议 (CCSB 2024)

第四届计算机科学与区块链国际学术会议 (CCSB 2024) 2024 4th International Conference on Computer Science and Blockchain 2024年9月6-8日 中国-深圳 老牌会议 | 涵盖计算机学科 | 往届均完成见刊、稳定检索 | 论文录用速度快 | 有ISBN号! *关于IEEE出版社 电气电子工…

使用ant design的modal时,发现自定义组件的样式(组件高度)被改变了!

一 问题描述 在项目中,自定义了一个组件,分别在界面和 antd的modal中都有使用到。但是突然发现,界面中的组件样式跟modal中的组件样式高度不一样。modal中的组件整体要比页面中的组件要高一点。 项目中的自定义组件比较复杂,因此&…

C#使用Puppeteer

Puppeteer Puppeteer是一个Node.js库,它提供了高级API来通过DevTools协议(Chrome DevTools Protocol https://devtools.chrome.com)控制Chrome或Chromium。 Puppeteer默认情况下无头运行(headless)。 可以配置为运行完整的Chrome或Chromium,运行效果如…

【中项】系统集成项目管理工程师-第11章 项目范围管理-11.2收集需求

前言:系统集成项目管理工程师专业,现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试,全称为“全国计算机与软件专业技术资格(水平)考试”&…

开源AI搜索平台Search4All

什么是 Search4All ? Search4All 是个人 AI 搜索协助工具,是 Perplexity 的开源替代品。能让你的 LLM API 支持联网,搜索、新闻、网页总结, 软件特点: 集成对 LLM 的支持,例如 OpenAI、Groq 和 Claude。本…

【学习笔记】Day 8

写在开头: 最近老板突然提出一个全新的组会主题,是关于 “最近我犯的傻”,其目的在于提供乐子的同时引以为戒。本来我还在愁到底去哪里找干的啥事儿,结果今天直接拉了个大的。什么叫无心插柳柳成荫啊,悲。 一…

亿达科创亮相智造数字科技大会

8月8日,IMC2024第七届智造数字科技大会在京启幕。大会以“乘‘数’而上”为题,邀请300智能制造行业数字化转型技术大咖、领军者及实践者共聚一堂,解读智造行业转型进程。亿达科创受邀参会,分享企业前沿数字技术、解决方案与创新实…

Java面试篇(线程池相关专题)

文章目录 1. 为什么要使用线程池2. 线程池的核心参数和线程池的执行原理2.1 线程池的核心参数2.2 线程池的执行原理 3. 线程池中常见的阻塞队列3.1 常见的阻塞队列3.2 ArrayBlockingQueue 和 LinkedBlockingQueue 的区别 4. 如何确定线程池的核心线程数4.1 应用程序中任务的类型…

开源AI智能名片小程序在私域流量运营中的“及时法则”深度应用与策略探讨

摘要:在数字化浪潮的推动下,私域流量已成为企业构建长期竞争优势的关键要素。开源AI智能名片小程序,凭借其智能化、个性化及高度可定制化的特性,正逐步成为私域流量运营的重要工具。本文深入探讨了“及时法则”在开源AI智能名片小…

模型量化——NVIDIA——QAT

概述 QAT 截止目前(20230418)的CUDA 实现并不在pytorch 原生包中(不等同于pytorch 的QAT,它主要支持CPU),需要引入NVIDIA 的第三方包“pytorch-quantization”。需要TRT8+ 、 pytorch 1.8 +。主要流程如下: 工具流转方向如下: 所以目前我的理解+咨询了NVIDIA官…

【代码随想录】螺旋矩阵II

本博文为代码随想录的学习笔记,原文链接:代码随想录 题目 原题链接:59. 螺旋矩阵 II 给你一个正整数 n ,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xf…

【Linux】lvm被删除或者lvm丢失了怎么办

模拟案例 接下来模拟lvm误删除如何恢复的案例: 模拟删除: 查看vg名: vgdisplayvgcfgrestore --list uniontechos #查看之前的操作 例如我删除的,现场没有删除就用最近的操作文件: 还原: vgcfgrestore…

1Panel应用推荐:KubePi开源Kubernetes管理面板

1Panel(github.com/1Panel-dev/1Panel)是一款现代化、开源的Linux服务器运维管理面板,它致力于通过开源的方式,帮助用户简化建站与运维管理流程。为了方便广大用户快捷安装部署相关软件应用,1Panel特别开通应用商店&am…

扩展02:Haporxy+Keepalived+Mysql高可用集群实战

由于这个架构和扩展01的大致步骤都相同,就不讲解了。看如下图再参考扩展01即可。

用C语言实现链式存储结构 万字

各位同学,大家好,我叫小敖。今天给大家分享数据结构之一链式存储结构,下面是对链表简单介绍,希望大家能理解。 链表介绍 链表是一种物理存储单元上非连续、非顺序的存储结构**,数据元素的逻辑顺序是通过链表中的指针链…

大模型快速部署,以浪潮源2.0为例

step1: 申请PAI-DSW试用 step2:魔塔社区授权 由于本地授权一直失败,于是采用了魔塔免费平台实例进行学习。 搭建好之后,打开就有相关页面了: demo搭建: 按照官方提示的步骤进行搭建,内容如下:…

第二十一节、敌人追击状态的转换

一、物理检测中的Boxcast 1、检测敌人Bool 当不知道一个函数的返回值是什么的时候 定义一个var变量 就知道了 二、状态切换 1、switch用法 2、新的语法糖写法

ubuntu2004上的glib编译教程

最近因为工作需要编译glib,写文章记录一下编译流程。 从launchpad上下载源码:链接 根据control文件的提示安装对应的依赖 然后尝试debuild,这里会编译不过出去,不过debuild会自动生成一些编译文件,不要删除。 接下来…