ToB企服应用市场:ToB评测及商务社交产业平台

标题: 以字节跳动内部 Data Catalog 架构升级为例聊业务系统的性能优化 [打印本页]

作者: 钜形不锈钢水箱    时间: 2022-6-23 16:14
标题: 以字节跳动内部 Data Catalog 架构升级为例聊业务系统的性能优化
背景

字节跳动 Data Catalog 产品早期,是基于 LinkedIn Wherehows 进行二次改造,产品早期只支持 Hive 一种数据源。后续为了支持业务发展,做了很多修修补补的工作,系统的可维护性和扩展性变得不可忍受。比如为了支持数据血缘能力,引入了字节内部的图数据库 veGraph,写入时,需要业务层处理 MySQL、ElasticSearch 和 veGraph 三种存储,模型也需要同时理解关系型和图两种。更多的背景可以参照之前的文章。
新版本保留了原有版本全量的产品能力,将存储层替换成了 Apache Atlas。然而,当我们把存量数据导入到新系统时,许多接口的读写性能都有严重下降,服务器资源的使用也被拉伸到夸张的地步,比如:
业务系统优化的整体思路

在开始讨论更多细节之前,先概要介绍下我们做业务类系统优化的思路。本文中的业务系统,是相对于引擎系统的概念,特指解决某些业务场景,给用户直接暴露前端使用的 Web 类系统。
优化之前,首先应明确优化目标。
与引擎类系统不同,业务类系统不会追求极致的性能体验,更多是以解决实际的业务场景和问题出发,做针对性的调优,需要格外注意避免过早优化与过度优化。
准确定位到瓶颈,才能事半功倍。
一套业务系统中,可以优化的点通常有很多,从业务流程梳理到底层组件的性能提升,但是对瓶颈处优化,才是 ROI 最高的。
根据问题类型,挑性价比最高的解决方案。
解决一个问题,通常会有很多种不同的方案,就像条条大路通罗马,但在实际工作中,我们通常不会追求最完美的方案,而是选用性价比最高的。
优化的效果得能快速得到验证。
性能调优具有一定的不确定性,当我们做了某种优化策略后,通常不能上线观察效果,需要一种更敏捷的验证方式,才能确保及时发现策略的有效性,并及时做相应的调整。
业务系统优化的细节

优化目标的确定

在业务系统中做优化时,比较忌讳两件事情:
优化范围选择

对于一个业务类 Web 服务来说,特别是重构阶段,优化范围比较容易圈定,主要是找出与之前系统相比,明显变慢的那部分 API,比如可以通过以下方式收集需要优化的部分:
• 通过前端的慢查询捕捉工具或者后端的监控系统,筛选出 P90 大于 2s 的 API
• 页面测试过程中,研发和测试同学陆续反馈的 API
• 数据导入过程中,研发发现的写入慢的 API 等
优化目标确立

针对不同的业务功能和场景,定义尽可能细致的优化目标,以 Data Catalog 系统为例:

定位性能瓶颈手段

系统复杂到一定程度时,一次简单的接口调用,都可能牵扯出底层广泛的调用,在优化某个具体的 API 时,如何准确找出造成性能问题的瓶颈,是后续其他步骤的关键。下面的表格是我们总结的常用瓶颈排查手段。

优化策略

在找到某个接口的性能瓶颈后,下一步是着手处理。同一个问题,修复的手段可能有多种,实际工作中,我们优先考虑性价比高的,也就是实现简单且有明确效果。

快速验证

优化的过程通常需要不断的尝试,所以快速验证特别关键,直接影响优化的效率。

Data Catalog 系统优化举例

在我们升级字节 Data Catalog 系统的过程中,广泛使用了上文中介绍的各种技巧。本章节,我们挑选一些较典型的案例,详细介绍优化的过程。
调节 JanusGraph 配置

实践中,我们发现以下两个参数对于 JanusGraph 的查询性能有比较大的影响:
针对字节内部的应用场景,元数据间的关系较多,且元数据结构复杂,大部分查询都会触发较多的节点访问,我们将 query.batch 设置成 true 时,整体的效果更好。
调整 Gremlin 语句减少计算和 IO

一个比较典型的应用场景,是对通过关系拉取的其他节点,根据某种属性做 Count。在我们的系统中,有一个叫“BusinessDomain”的标签类型,产品上,需要获取与某个此类标签相关联的元数据类型,以及每种类型的数量,返回类似下面的结构体:
{ "guid": "XXXXXX",                 "typeName": "BusinessDomain",                 "attributes": {                     "nameCN": "直播",                     "nameEN": null,                     "creator": "XXXX",                     "department": "XXXX",                     "description": "直播业务标签"                 },                 "statistics": [                     {                         "typeName": "ClickhouseTable",                         "count": 68                     },                     {                         "typeName": "HiveTable",                         "count": 601                     }                 ]             }
我们的初始实现转化为 Gremlin 语句后,如下所示,耗时 2~3s:
g.V().has('__typeName', 'BusinessDomain')     .has('__qualifiedName', eq('XXXX'))     .out('r:DataStoreBusinessDomainRelationship')     .groupCount().by('__typeName')     .profile();
优化后的 Gremlin 如下,耗时~50ms:
g.V().has('__typeName', 'BusinessDomain')     .has('__qualifiedName', eq('XXXX'))     .out('r:DataStoreBusinessDomainRelationship')     .values('__typeName').groupCount().by()     .profile();
Atlas 中根据 Guid 拉取数据计算逻辑调整

对于详情展示等场景,会根据 Guid 拉取与实体相关的数据。我们优化了部分 EntityGraphRetriever 中的实现,比如:
对大量节点依次获取信息加并行处理

在血缘相关接口中,有个场景是需要根据血缘关系,拉取某个元数据的上下游 N 层元数据,新拉取出的元数据,需要额外再查询一次,做属性的扩充。
我们采用增加并行的方式优化,简单来说:
对于写入瓶颈的优化

字节的数仓中有部分大宽表,列数超过 3000。对于这类元数据,初始的版本几乎没法成功写入,耗时也经常超过 15 min,CPU 的利用率会飙升到 100%。
定位写入的瓶颈

我们将线上的一台机器从 LoadBalance 中移除,并构造了一个拥有超过 3000 个列的元数据写入请求,使用 Arthas 的 itemer 做 Profile,得到下图:

从上图可知,总体 70%左右的时间,花费在 createOrUpdate 中引用的 addProperty 函数。
耗时分析
优化思路
优化实现效果
总结

立即跳转火山引擎大数据研发治理套件 DataLeap 官网了解详情!
欢迎关注字节跳动数据平台同名公众号

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4