01
引言
在前面的文章中,eBay Touchstone团队深入研究了eBay实验平台的网站性能优化实践。本篇我们将进一步研究eBay A/B Testing 指标平台及其技术演进。
A/B 测试是 eBay 推动产品功能数据驱动决策的核心手段。Touchstone是eBay内部的实验(A/B 测试)平台,为各团队提供自助实验配置和指标分析支持。随着业务的增长和实验需求的增加,Touchstone 原有架构逐渐无法满足日益提高的性能和功能要求。因此,我们对 Touchstone 指标和报告平台进行了架构上的技术演进,以适应不断变化的发展需求。
02
现状与挑战
现状
我们先简短介绍一下Touchstone的指标以及报告系统。Touchstone实验报表平台是一个面向用户自助服务的系统,专为实验结果分析和决策而设计。平台支持用户自主注册数据源、指标和维度,同时提供超过8000项的公用指标供快速调用。在实验启动后,用户可自行创建多个报表,通过丰富的功能选择对实验结果进行多角度分析。在指标计算方面,平台从eBay多个实验事件源中获取用户实验事件数据(Event Fact),并结合用户自定义数据源中的指标(Metric)和维度(Dimension),经过事件(Event)、样本(Sample)、实验组(Treatment)等多粒度处理,以及离群值剔除(Capping)、功效分析(Power Analysis)、方差削减(CUPED)和显著性分析(Stat-sig) 等AB实验方法,最终将精准的实验结果呈现在报告中。
挑战
规模与增长的挑战:Touchstone 平台的实验用例和指标规模近年快速增长,给现有系统带来巨大压力。目前Touchstone每天需从ebay全球40+站点和400+个指标数据源中接入约700亿条新记录,每天为400个以上的包含100到上千个指标的实验报告生成读数。随着更多团队接入A/B测试,新的数据源数量激增:仅2023年就净增加120个数据源,2024年又净增加接入130个。同时,实验数量也在以50%到80%的年增长速度增加。在如此规模下,Touchstone的原基于MapReduce管道频繁出现作业排队等待,系统吞吐已超过极限。同时,某些复杂统计计算(如Bootstrapping需要大量迭代)严重消耗资源,常导致批处理作业内存溢出等稳定性问题。运维上,批量任务失败需要人工介入恢复,每当上游数据或平台出问题时往往导致大量作业失败。复杂的代码和缺乏良好测试工具也令开发调试困难,影响了迭代速度。以上种种挑战表明,旧的架构已无法有效支撑Touchstone在规模和功能上的要求,亟需演进升级。
指标报告存在不足:原有 Touchstone 报告系统存在性能瓶颈和可用性问题,难以充分满足公司快速发展的需求。
一方面,指标生成延迟较高,某些实验的数据处理需要运行超过30小时以上才能完成,导致结果输出严重滞后。另一方面,数据存储模式过于复杂:为了配合Hadoop MapReduce,Touchstone 采用了嵌套的行式编码存储,字段经过编码处理,直接用 SQL 查询十分困难,分析人员必须使用特定UDF解码并展开字段,查询性能低下。
此外,一些派生指标仅在MapReduce作业的中间阶段计算,并未持久化到易于查询的表中,导致无法直接提供用户级别的汇总指标供深度分析。这些不足使得传统指标报告难以满足业务和分析人员不断增长的需求。
03
新架构设计
架构目标:为适应未来5年的业务和数据增长,Touchstone 需要从根本上优化架构,使其能够适应更大规模以及更高级的数据处理需求。
我们的目标是打造一个高可扩展、高性能、易维护的实验数据平台,确保其可以持续支持 A/B 测试在业务中的核心作用。
首先,新的架构应该显著降低指标生成的延迟——系统目标将原本需要30小时(P75)的指标计算缩短至8小时内完成。
其次,数据应该更容易被分析和使用,分析师会花大量时间对实验结果进行分析,易用的数据集可以节省人工分析的时间,并且也可以缩短Touchstone新功能开发的周期。
第三,为了应对不断扩大的业务规模,平台应能进行自动化运维和治理。
为此,我们进行了如下关键改进:
在技术栈上,采用 Spark 替代 MapReduce,通过动态资源调配,大幅提升计算效率。引入 Airflow 作为调度引擎,提供灵活的工作流编排。
采用分析友好的新数据模型,支持未来更高级的分析需求(如实时分析,多维度分析等)。
构建智能的调度机制,在有限资源下提升关键报表的计算优先级,使用户更快看到最重要的数据。
04
技术细节
1.数据模型优化
为提升数据的易用性和扩展性,新架构对 Touchstone 的数据模型进行了全新的设计。在过去,我们的数据模型主要存在两个核心问题:
第一,在多个维度的 schema 与计算逻辑上,尤其是实验分配(Assignment)和指标数据源定义方面,与 eBay 上一代用户在线行为数据模型存在强耦合。这导致在开发时支持更多不同类型的实验分配和指标数据源时,需要付出较大的兼容性成本,每一种实验分配数据和指标数据的 schema 组合都可能导致不同的计算逻辑。
第二,过去的数据模型是在 MapReduce 时代下设计的,采用紧凑但复杂的行式编码方式,已不适用于现代化的基于 SQL 或 DataFrame 的数据开发模式。分析人员和工程师在使用数据时需要调用特殊的 UDF 进行解码和解析,极大增加了使用门槛和开发难度。
针对上述问题,我们在新架构的数据模型设计中明确提出了去耦合与语义化的设计原则。具体来说,我们将 Touchstone的核心计算提炼成了一套通用且独立的计算逻辑和数据模型,并且更注重业务语义表达,而将不可避免的、对上游数据的兼容性开发独立为单独的数据标准化层。所有各类上游数据都会在数据处理的早期阶段被标准化为统一的数据格式,随后应用统一的计算逻辑。
简化的Touchstone报表计算逻辑,在Assignment/DataSource PreProcessing Processing完成去耦合工作,在Metric Processing使用语义化的数据模型和计算逻辑
具体实现上,我们引入了全新的 EP Schema(实验平台数据模型),重点增加了以下核心表:
实验分配表(Assignment Table):记录用户样本在实验中的分配情况(对照组或试验组及分配时间)。
事实级别聚合指标表(Fact Level Metrics Table):记录细粒度的实验数据,支持更灵活的多维度分析及高级统计方法。
样本级别聚合指标表(Sample Level Metrics Table):汇总每个用户在实验中的指标,支持深入的用户级分析,例如 Cohort 分析和基于 Jupyter notebook 的自主离线分析。
一条简化的Assignment数据,表明了用户98765是在1742121201000第一次进入了实验分组12345,其中提供的DimXXX是为Corhort分析准备的
两条简化的Fact Level数据,表明刚才进入实验分组12345用户98765在174233333000产生了5美元GMB,在174233333300产生了3美元GMB
一条简化的Sample Level数据,表明用户98765在进入实验分组12345产生了8美元GMB,可以注意到指标和维度已根据某种统计口径被聚合
上述表的设计为分析师提供了用户级别的细粒度数据,分析师可便捷地进行深度数据探索和灵活的统计建模,例如重新调整离群值剔除算法或尝试不同的统计方法。
同时,新数据模型全面采用列式存储,并避免嵌套的复杂结构,Schema 设计更符合 SQL 查询习惯。这种设计极大提升了数据查询与提取的效率,分析师无需依赖特殊的UDF解码,即可通过标准工具直接访问和分析数据,有效增强了自助分析能力。
此外,这些优化措施也提升了新架构的数据查询与存储效率,使其更具扩展性,可灵活适应未来业务的增长和更高级别的数据分析需求。
2.调度系统优化
为了进一步提高Touchstone平台的扩展性和开放性,我们在调度系统优化过程中,延续并强化了原有命令流水线架构的优势。命令流水线的理念最初源于CPU领域,用于提升处理器吞吐量,其核心思想是将指令执行过程分解为多个阶段,使多个指令同时处于不同的执行阶段,实现并行处理,从而提高整体性能。Touchstone同样借鉴这一理念,将数据处理流程分解为若干独立阶段,并使用专门的调度组件(为与传统的Workflow Scheduler区分,我们称之为Driver/Coordinator)管理各阶段任务执行及子流程分发。
Touchstone报表处理的核心数据流(逻辑),每个黄块都是一个数据处理的独立阶段,黄块之间的逻辑实际由Coordinator负责调度和分发
虽然原有架构在平台的早期发展阶段很好地支撑了快速迭代和功能扩展,但随着功能复杂度和数据处理规模的不断提升,页面上的每条数据背后都涉及多个功能的交叉组合,目前组合方式已有数万种之多。而无论是实验性的功能还是通用功能,新增功能的实现都需要在已有的漫长处理链路中多处增加分支逻辑。这种持续叠加的方式逐渐使得处理流程变得臃肿复杂,形成难以维护的“面条代码”,新功能上线面临极大的测试和质量保证压力。一旦出现代码问题引发数据错误,代价巨大,严重情况下可能需要对所有数据进行全面重跑,且问题往往滞后发现,进一步放大了风险。
为解决上述问题,我们在保留命令流水线架构优势的前提下,引入了声明式的数据模型Compute Unit。Compute Unit记录了报表应执行的计算内容。借助Compute Unit,我们能够构建出报表的完整分析方案。而 Compute Unit Execution 记录了报表计算单元在时间维度上的 W.I.S.B.(即预期状态)。它定义了应为报表提供的数据视图,并记录了处理该视图时所用数据源的执行状态及版本。
在这个例子中,一个Touchstone报表拆解为了4个Compute Unit
Compute Unit Execution声明示例:报告xxx的搜索转化指标应展示2025-03-10的数据
通过上述声明式的建模,每个声明直接对应用户在Touchstone报表中所需的一部分数据,多个声明组合即形成完整的用户实验报告。借助这一设计,我们将原本粗放的数据管道拆解为更细粒度、可动态调度的任务单元,并按实际需求触发执行。
Before:用户看到报告所有数据的时间取决于任务中最耗时的一部分计算
After:只要部分报告的数据已经完成计算,用户就可以看到。且更重要的数据可以使用优先级策略优先计算。
例如,不再对所有实验数据进行统一全量计算,而是仅针对被实际查询的数据源或指标触发处理,或者报表着陆页上的数据优先计算,暂未查询的数据则可延迟或跳过计算。这种细粒度的动态调度机制有效减少了不必要的资源开销,并提升了整体资源利用率:系统能更加灵活地并行执行不同的任务,避免过去长尾任务阻塞队列、影响其他任务的情况。
我们使用了数据驱动的模式来制定调度系统的优化策略,在我们之前分析中得出,用户常看的数据仅占Touchstone计算的数据的10%左右
同时,这种声明式设计极大提高了开发效率。当需要新增功能时,只需定义一组新的数据声明即可,自动与现有的Touchstone数据集合并生成对应报告,从而实现真正的模块化开发和集成。此外,新调度系统还显著增强了容错性。一旦出现任务失败或数据问题,我们不再需要全量重跑数据,而可以精确地只重跑受影响的小范围数据单元,大幅降低数据错误的修复成本和周期。
最后,这种精准处理的能力也打开了我们在精细化管理平台的窗口:除了精细化调度和重跑外,我们还可以精细化管理数据生命周期,无需预先配置的实验报告配置更改等等。
3.性能解决方案
上文中提到,实验平台目前所面临的一个关键问题是数据处理时间过长(P75 30小时),要解决这个问题,重点之一便是我们要基于新的Spark技术栈,相应地提出性能解决方案。
但在实践过程中,我们发现实验平台所面临的数据处理性能问题十分特殊且复杂,并不是简单的将MR转为Spark就能达到性能要求。在经过了反复的尝试和调整后,我们最终得到了一个达到目标的方案,并且成功将数据处理时间从30小时降至8小时(P75)。
问题分析
在Spark的实际应用中,最常见也最多的是ETL系统中的数据处理任务,这类任务用最简洁的语言来概括那就是“针对特定场景下的业务需求对特定数据源进行处理”。
在这种场景下去进行性能调优是相对容易的,因为足够“特定”。通常我们会从执行计划、参数两个方面进行调优。例如,通过一些常见的方案去调整执行计划,来消除数据倾斜、改变数据集join类型等;参数层面,我们也会根据任务实际运行情况去微调Spark的资源参数,也就是执行器的CPU核数,内存资源,shuffle过程的分区数目等。
通过这些传统的调优手段,我们可以相对容易地让绝大多数任务都拥有足够优秀的性能。
ETL Pipeline中性能调优的常规思路
但实验平台不同,它实际上是在“针对不特定数据源进行通用的逻辑处理”,即用户只需要提供满足标准的数据源(包含指定的拥有特定语义的列,数据归因属性等),实验平台便会根据实验配置使用标准流程进行数据处理。
标准数据处理流程简图
实验平台的整个数据处理链路主要由4个job flow,共计约20个Spark任务组成。其中, 负责最终交付实验指标数据的report data processing flow中的Spark任务每天共计要被调度超过100,000次,来服务于不同的数据源、不同的实验。
这些Spark任务,每一个都要面临极其多变的场景,这种多变主要体现在两个方面:
一方面,来自不同团队的用户所提供的数据源差异巨大。从数据量层面看,有的数据源单日数据行数可以达到百亿级,而有的却仅有千级;
另一方面,虽然实验平台的数据处理逻辑是通用的,即无论数据源如何,都会有相应的标准处理流程,但这样的标准流程会根据平台功能和实验配置的不同,派生出超过5万种不同的处理链。
这样的多变性和难以预测性导致了常规的调优思路无法很好地产生效果。
解决方案
通过分析,我们所遇到的关于性能的挑战,可以归纳总结为两个核心问题:
如何在Spark任务提交前设置合理的资源配置?
如何在Spark任务运行时使用合理并行度处理数据?
问题一是指,我们需要确定提交Spark任务时所使用的Driver、Executor的CPU核数、内存,以及Executor动态分配的数量限制等在提交后不可更改的参数。
这些配置,如果我们为一个负载较大的任务分配的资源不足,则任务会更容易失败。同样,如果分配过多则会造成资源浪费——在集群计算资源紧张的情况下这个问题更为重要。
如果基于历史数据统计来预测工作负载,则有可能无法应对震荡;如果在任务指派时进行即时分析,例如即时统计超过十万个文件所组成的数据集大小,则可能会十分复杂耗时。
此外,无论哪种选择,都无法逃避数据在运行时产生剧烈变化的问题。
在这种矛盾的情况下,我们选择换一种思路:将静态资源配置标准化,通过Spark的动态能力来应对任务的多变性和难预测性。
所谓动态能力就是指那些可以在Spark运行时更改的设置,如shuffle partitions。
例如我们使用一个常见的配置作为标准:4C4G + 2G Overhead,以及100 MaxExecutors的DynamicAllocation,则我们可以简单认为可供我们动态规划的计算单元(executor task)是1 GB数据,同一时间最多存在400个。在这种配置下,假如我们需要进行shuffle的数据总量有300 GB,则我们可以在shuffle前将shuffle partitions改为300。这样,Spark会使用最多75 (300 GB / 4 Cores) 个executor来进行shuffle,不仅每个executor task会被分配到合适的数据量,基于DynamicAllocation的特性,未达到最大值100的25个executor资源也不会被浪费,而是被集群利用在其他Spark任务中。
而假如需要shuffle的数据量较大,例如4 TB,我们同样可以将shuffle partitions设置为4000,虽然同一时间只有400个executor task在工作,但在工作负载分配合理的情况下,每一个task都可以快速完成,则整个shuffle过程也不易失败。
基于这种思路,我们得到了解决方案的第一片拼图,即标准化的资源分配,让这种分配变成运行时的已知条件,供动态规划做出决定。
Spark参数样例
在这种思路下,解决方案的第二片拼图就相对明确了,同时它也对应着上面提到的第二个核心问题,即在Spark任务运行时动态调整数据处理的并行度。
这里的并行度存在两种类型,分别为数据在shuffle(宽依赖)、mapping(窄依赖)过程中使用的并行度。
Shuffle过程并行度的重要性自不必说,作为Spark任务中的性能杀手,一个合理的规划可以让shuffle过程更加高效可靠。
Mapping过程使用的并行度通常等同于读入的数据源文件数或上一次shuffle所使用的分区数。这一并行度我们一般很少会介入,因为mapping过程在大多数情况下是非常高效的。
而在实验平台中,由于一些业务逻辑,导致在数据转换过程中存在explode和stack操作,这类操作会使数据在内存中产生膨胀,如果规划不当,则会产生大量的Spill,甚至会导致OOM问题,因此我们也应妥善处理之。
在实践此思路的早期,我们尝试了基于输入数据源在HDFS上的文件大小来预估应当使用的并行度,然而实际效果并不理想,Spill以及OOM的问题仍然时有出现。
通过分析,其原因在于从HDFS中得到的数据量实际是磁盘占用,而Spark处理数据却是在内存中进行的,这两者之间的差异超出了我们的预计,从而导致我们基于此预计进行的并行度调整效果很差。
造成这种差异的因素主要有:
数据序列化
数据通常以Parquet格式被序列化于HDFS中,而在内存中,数据是被充分结构化后进行处理的,这种差异会导致磁盘与内存中的数据量差异巨大。
数据压缩率
在文件序列化的基础上,数据压缩算法的存在使数据量的差异进一步增大了。根据测试,由数据压缩导致的数据量差异可以超过20倍。
面临这种问题,我们进一步提出了能够更加精确预计运行时数据量的方案,也是我们整个解决方案的最后一片拼图:基于内存数据量的工作负载预测。
办法实际很简单,就是在资源密集型的计算步骤开始前,基于数据集的类型结构和行数来估算内存中的数据大小。唯一需要注意的点就是由于需要计算数据集的行数,会额外引入Action操作,所以需要尽量利用Spark的谓词下推优化以及缓存来避免造成过多的负面作用。
通过这种办法,我们得以能够更加精准地得到运行时的数据集大小,从而更准确地进行repartition或调整shuffle partition。
在应用了此方案后,资源密集型计算过程中的Spill情况大大改善,OOM的问题也得以消除。
完整的解决方案可以用下图来概括:
Dynamic Processing Parallelism
下图揭示了同一个Spark任务在不同工作负载的情况下的两次执行情况:
左:高负载;右:低负载
可以看出,在基本相同的执行逻辑下,不同阶段的Task数目有很大差异,这正是通过动态调整后的结果。
经过实际测试,在应用了上述方案后,Spark任务在各种场景下都表现出了优秀的适应能力和性能。同时,整个集群对于任务的吞吐量也得到了显著提升。
05
总结与未来展望
在本次平台架构升级中,我们成功实现了无缝的架构迁移,显著降低了指标计算的延迟。当前,新架构已支撑超过5600余个实验报告的生成,并在实验报告数量以 YoY 70% 的高速增长背景下,依然保持了出色的性能优势——指标计算时间大幅缩短,确保各团队能够在更短的时间内获得高精度的数据分析结果。
展望未来,我们将继续围绕新架构进行深入优化,主要包括以下几个方向:
性能与调度优化:进一步提升计算与调度性能,增强平台在大规模数据处理下的资源管理能力。计划引入用户侧的 quota 管理以及细粒度的数据生命周期管理机制,从而实现更精准的资源分配与监控。
实验方法扩展:在现有的实验方法基础上,探索更多指标与事件归因模型,开发针对小数据集的实验方案(如贝叶斯非参数模型),满足多样化业务需求。
在海量数据上的即时分析:正在积极探索在海量样本粒度数据进行即时分析,旨在实现跨多维度的灵活分析和近实时报告数据展示,为用户提供更强大的自助数据分析能力。
我们期待在下一篇 Touchstone平台系列文章中,与大家分享更多技术进展和实践经验,继续推动平台向更高效、更智能的方向发展。
END