导读:DataTester是由火山引擎推出的A/B测试平台,笼罩举荐、广告、搜寻、UI、产品性能等业务利用场景,提供从A/B实验设计、试验创立、指标计算、统计分析到最终评估上线等贯通整个A/B试验生命周期的服务。

DataTester通过了字节跳动业务的多年打磨,在字节外部已累计实现150万次A/B试验,在内部也利用到了多个行业畛域。指标查问的产品高性能是DataTester的一大劣势。作为产品最简单的功能模块之一,DataTester的指标查问可能在无限资源的前提下,施展出最极致的A/B试验数据查问体验,而在这背地是屡次的技术计划的打磨与迭代。

本文将分享DataTester在查问性能晋升过程中的5个优化思路。

01 现状及问题

  1. 挑战 1:版本治理试验指标报告页是DataTester零碎最外围的性能之一,报告页的应用体验间接决定了DataTester作为数据增长和试验评估引擎在业界的竞争力。

该性能具备以下特点:
① 株连零碎多、链路长:报告页波及到控制台(Console)、科学计算模块、查问引擎、OLAP存储引擎。整个链路包含了:DSL到sql转化、后端查问后果缓存解决、查问后果的加工计算、前端查问接口的组装和数据渲染。
② 实现简单:试验指标有多种算子,在查问引擎侧中都有一套定制SQL,通过DSL将算子转换成SQL。这是DataTester中最简单的功能模块之一。

02优化思路

从一条SQL说起。
举一个例子,在DataTester中一次AB测试的查问分三局部逻辑。
① 实时扫描事件表,做过滤
② 依据用户首次进组工夫过滤出用户
③ 做聚合运算须要查问具体的SQL代码,也能够点击开展查看详情。

printf("hello world!");SELECT event_date,       count(DISTINCT uc1) AS uv,       sum(value) AS sum_value,       sum(pow(value, 2)) AS sum_value_squareFROM  (SELECT uc1,          event_date,          count(s) AS value   FROM     (SELECT hash_uid AS uc1,             TIME,             server_time,             event,             event_date,             TIME AS s      FROM rangers.tob_apps_all et      WHERE tea_app_id = 249532        AND ((event = 'purchase'))        AND (event_date >= '2021-05-10'             AND event_date <= '2021-05-19'             AND multiIf(server_time < 1609948800, server_time, TIME > 2000000000, toUInt32(TIME / 1000), TIME) >= 1620576000             AND multiIf(server_time < 1609948800, server_time, TIME > 2000000000, toUInt32(TIME / 1000), TIME) <= 1621439999)        AND (event in ('rangers_push_send',                       'rangers_push_workflow')             OR ifNull(string_params{'$inactive'},'null')!='true') ) et GLOBAL ANY   INNER JOIN     (SELECT min(multiIf(server_time < 1609948800, server_time, TIME > 2000000000, toUInt32(TIME / 1000), TIME)) AS first_time,             hash_uid AS uc2      FROM rangers.tob_apps_all et      WHERE tea_app_id = 249532        AND arraySetCheck(ab_version, (29282))        AND event_date >= '2021-05-10'        AND event_date <= '2021-05-19'        AND multiIf(server_time < 1609948800, server_time, TIME > 2000000000, toUInt32(TIME / 1000), TIME) >= 1620651351        AND multiIf(server_time < 1609948800, server_time, TIME > 2000000000, toUInt32(TIME / 1000), TIME) <= 1621439999        AND (event in ('rangers_push_send',                       'rangers_push_workflow')             OR ifNull(string_params{'$inactive'},'null')!='true')      GROUP BY uc2) tab ON et.uc1=tab.uc2   WHERE multiIf(server_time < 1609948800, server_time, TIME > 2000000000, toUInt32(TIME / 1000), TIME)>=first_time     AND first_time>0   GROUP BY uc1,            event_date)GROUP BY event_date

DataTester底层OLAP引擎采纳的是clickhouse,依据clickhouse引擎的特点,次要有两个优化方向:
① 缩小clickhouse的join,因为clickhouse最善于的是单表查问和多维度剖析,如果做一些轻量级聚合把后果做到单表上,性能能够极大晋升。也就是把join提前到数据构建阶段,构建好的数据就是join好的数据。
② 须要join的场景,则通过减小右表大小来减速查问。因为join的时候会把右表拉到本地构建hash表,所以必然会占用大量内存,影响性能。

  1. 重点优化计划
  2. 计划一:预聚合,压缩查问事件量


尽管指标很灵便,然而大多数场景用户进入报告页只会查看进组信息,试验论断,指标天级统计数据等,很少实时带条件去查问。因而,天级查问是咱们次要应用场景。天级查问能够通过「预计算」减速。为了反对置信度的计算,「预计算」能够从人的粒度着手,即每天保留一条人的聚合后后果,记录下这个人在所有试验下进组之后各指标下的累积值。这样每天数据量与日活量相当,能够大大压缩总体查问量。

(1)计划详情
总体流程图:

分为如下几个关键步骤:Dump、Parse、Build、Query

  • Dump
    即把事件dump到离线存储中。私有化采纳flume来实现:
    a. 自定义timestamp interceptor避免数据漂移
    b. 应用file channel文件缓冲保证数据不失落
  • Parse
    从指标DSL中解析出聚合字段、聚合类型,事件名、过滤条件指标四因素,再依据这些信息生成md5作为clickhouse存储的key。思考到不同指标配置可能会配置雷同的聚合字段、聚合类型,事件名、过滤条件,生成md5的目标是保障惟一避免屡次聚合。

聚合类型包含count,sum,max,min,latest,distinct(暂不反对),任何算子都能够用这几个根底聚合后果计算出来。如avg能够通过sum/count来计算。

  • Build
    离线构建最外围的局部在于自定义聚合函数(UDAF),自带的聚合函数无奈满足咱们的要求。
  • Query
    即数据如何查问,通过对查问引擎减少参数管制是否走预聚合逻辑,同时针对预聚合定制了查问实现。

    (2)资源应用限度
    私有化场景用户机器资源是十分贵重的,夜间也有很多定时工作在执行会争抢资源。为了保障不占用太多资源,提交工作时会对spark参数做管制。
    以如下参数为基准,对spark.dynamicAllocation.maxExecutors进行管制:
    driver-memory:4g
    executor-memory:2g
    executor-cores:2

配置梯度表:

(3)性能晋升体现4亿事件量,100w用户量,查问晋升超过4倍。

  1. 计划二:ab_log,减小join时右表的大小(1)背景

    (2)计划概览

① 从实时流中过滤出曝光事件,把用户和进组工夫写进实时clickhouse表。
② 从clickhouse实时表中构建出天粒度的离线用户进组信息表,每天每个用户仅有1条进组记录,记录了该用户该天最早的进组工夫。
③ 查问的时候,为了取得用户首次进组工夫,取min(「实时表中该用户当天的进组工夫」,「离线表试验开始到T-1天数据中该用户进组工夫」)。

(3)晋升成果

① 通过天级进组表大大减速服务端试验进组人群的圈选。
② 彻底解决私有化进组用户属性的隐患。
③ 在私有化环境能够肯定水平上缩小曝光事件量。在某些客户下,可缩小30%以上事件量。

  1. 计划三:GroupBy查问优化
    (1)背景
    DataTester的数据查问和其余数据利用产品不同,DataTester在数据查问时,所有的查问都会针对每一个试验版本都查一遍,而过程中中惟一的区别就在于试验版本ID,所以和SQL中GroupBy的利用场景特地符合,通过GroupBy查问不仅能够极大的缩小查问的数量,也能够升高屡次查问造成的反复扫表,进步查问效率。
    (2)优化计划
    DataTester对每个试验版本的查问语句都是相似的,只是版本id不同。对DataTester用到的所有查问类型和算子做GroupBy的革新,实现细节这里不做过多开展。
    (3)晋升成果
    测试数据规模为日均一亿,7天,3个试验版本
    查问引擎接口响应时长(取10次均匀):

    5. 计划四:au类指标优化,缩小反复查问次数
    (1)背景指标查问引擎对DataTester的au类型算子都做了定制,一个指标查问会产生两条sql,一条失常指标的查问sql,另一条是对any_event的au的查问,在最初后果解决的时候对两条sql的查问后果做了一个合并,一起返回到DataTester的科学计算模块。然而,每次关上报告页都必定会查进组人数,它和any_event的au是同一个值,au类型算子查问的时候无奈复用进组人数的后果,而au查问又能够算是最慢的查问之一,升高了报告页关上的速度。

对有进组指标的算子做了缓存优化,缩小反复查问。

(2)优化计划

  1. 计划五:异步查问优化,解决页面超时问题
    (1)背景
    DataTester报告页等一些查问数据的接口自身的确比拟耗时,须要实时计算,而大部分网关都有超时限度,这个问题在私有化中尤为显著,所以对报告页的整体交互做了优化革新。
    (2)计划介绍
  2. 前后端交互
  3. 服务端架构设计

    (3)用户体验改良成果
    ① 大幅缩短申请延时,避免出现页面申请失败的状况
    ② 通过减少redis缓存,同页面的屡次刷新响应工夫能够管制在100ms左右
  4. 其余优化计划
    ① 业务逻辑优化,报告概览外围指标显著性和进组共用查问后果,去除试验版本依照外围指标显著性的排序,14个SQL降至10个,升高28.5%⬇️
    ② 多维度并发管制,限度资源应用
    ③ 默认应用备查问,充分利用备节点的算力
    ④ 灵便开关多种报告的缓存,保障外围链路失常运行

03总结

作为一站式A/B测试平台,火山引擎DataTester最外围的性能之一就是指标查问局部,它关系到产品体验和资源占用状况。而作为TOB畛域的数据产品,DataTester能在无限的资源下施展最极致的产品数据体验,也是产品最为重要的竞争力之一。

本次分享了DataTester在报告页查问优化过程中的5个技术计划落地。预聚合和ablog是从数据构建角度缩小查问数据量的角度的优化,groupby和au类指标的优化是从缩小并发的角度,异步查问是从产品体验角度。

查问和数据构建密不可分,DataTester将来的产品优化也会依照“去肥”和“增瘦”两个方向进行,“去肥”是优化科学计算模块和查问引擎的整体架构,优化业务逻辑,使得报告页查问逻辑更加清晰和简洁;另一方面“增瘦”就是通过正当的数据构建和数据模型优化减速查问,同时定向对局部难点问题重点优化,比方留存、盒须快照、同期群等等。

点击跳转火山引擎A/B测试DataTester官网理解详情!

扫码进入「字节跳动数据平台」官网交换群,支付更多A/B测试学习材料。