新闻资讯
洞察行业前沿,时刻保持探索精神,保持 HENGSHI SENSE 产品的优越性,
更多衡石精彩不容错过

免费试用

全部

精选案例

最新活动

衡石动态

衡石科技八周年|赖林华:新形势下数据分析框架的一般原理
作者:HENGSHI 时间:2024-12-14

导言:


创业公司起步或凭技术 idea ,或靠大客户资源。但打造卓越商业化软件,需修炼产品定义、客户成功、市场宣传、销售服务、人才培养、组织建设等多方面。


历经8年,我们从简陋小办公室成长为专业激情团队,满载挑战与收获。我们将推出八周年系列文章,分享我们在产品、技术、市场、团队、人才等方面的故事与内幕,涵盖从摸索定义首个产品到技术架构复杂演进,从签约首客到多行业覆盖,从技术方案到产品化运作的历程。


故事中有面对挑战的突破、创新、希望与团队凝聚力,希望在 SaaS 行业面临重重挑战的当下,和更多团队一起去探索成功之路。


分析型产品的开发难度


很长时间以来数据分析已经成为一门显学,规模稍大的企业都不止有一个数据分析团队。特别是大模型的这一波浪潮,让数据能以更加触手可及的方式提供给业务团队。


虽然若干年以后,我们回顾目前的数据服务方式,可能并非是一个有价值的投入,因为 AI 也许能够识别业务需求并完成数据存储规划,数据业务建模,数据查询优化和查询结果解读。但就目前而言,为了满足业务方在数据查询时效性,业务灵活性和易用性的需求,数据团队在当下所需要处理的任务是比以往更重和更频繁的。


不管是新手还是从事多年数据开发方向高级工程师,你可能都在寻找一个同时解决数据存储和数据查询、同时解决批处理和流式计算、同时解决预定义和 ad-hoc 查询的最佳方案。就一般而言你会得到一个可以接受的方案,但它是否是最终的答案?或者说当下纷繁的数据处理框架能把数据产品的能力边界扩展到哪里?


当然这是一个庞大的话题,正如本文的标题所说,我们将限定在 BI 产品或者说更大的一个品类——分析产品。分析产品和数据处理产品是有一个比较清晰的分界的:

 人们使用数据处理产品的目的是加工出新的数据供计算机进行存储。

 而使用数据分析产品的目的是得到数据的聚合结果供人类查看( headless-BI 看起来是一个例外)。


所以就一般或者在外在而言,分析产品是不会也不需要修改原始数据的。诚然在具体的实现上,由于性能和数据关联的限制,在很多时候分析产品也需要进行一些数据的缓存,但这对于上层应用来说应该是一个透明的实现手段,而非目的。对最终用户来说,在使用分析产品查询过程中触发的数据缓存是无感的,而数据处理产品的用户对于加工的结果却是有明确预期的。


相较于数据处理产品对具体数据处理方式的深度绑定,分析产品通常更容易实现脱离具体数据的绑定,从而形成一个更加通用的产品。因此,技术负责人往往对开发分析产品所需的资源投入持有过于乐观的态度,而后台代码开发人员则通常缺乏在数据仓库选型、抽象化以及 SQL 生成等具体工作上的丰富经验,从而对分析产品的开发难度和维护难度持有过于悲观的认识。


接下来,本文将就衡石团队这几年在 BI 分析领域对这个问题的的探索做一个阶段性的总结。把我们面临的典型问题和所采用的解法做一个简略的介绍,以期对从事数据分析、BI 项目、报表统计相关工作的同仁有一些启发。当然,如果您对这些方法有所了解,并希望避免重复繁杂的工作,以便将研发力量集中在核心业务上,您可以点击查看原文联系我们,了解一下 HENGSHI SENSE。




当代数据分析技术面临的挑战


大数据量


虽然大数据现在听起来已经是老生常谈,但不可否认的是,当前我们在分析场景下需要处理的数据量比十年前还是大了许多许多。否则我们需要的就不是市场上的这些 BI 产品或者分析产品,大家直接用 Excel 就可以了。并且传统上的 Java (或 Go/Rust/C++ 等等)程序中单机处理任务,从性能上特别是交付周期上,都逐渐变得不可接受。



计算平台多样



衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图1)


Scala 的目标是作为一种可能的 Java 替代者而出现, Kotlin 的出现又被视为一种更优秀的 Scala 替代选项,程序员群体一直在不断探索,以期创造一种更为完美的编程语言,能够超越现有的不完美语言。但遗憾的是,每次尝试的结果只是导致了更多(有时更糟糕)的编程语言涌现。就像人类语言体系形成时分裂出不同的语言之后,巴别塔再无可能被搭建一样,数据分析领域每隔一段时间就会有一个企业宣称他们的计算平台可以解决目前大部分(如果不是说所有)的技术难题,而实际情况如何我们现在已经知道了:我们需要程序员小哥哥掌握的计算平台就有 Spark、Flink,Presto,ClickHouse,Doris,Greenplum,Redshift,Hologres,TiDB 等等,这个列表每年还在不断变多。



感谢计算机科学史上伟大的真神 Edgar F. Codd、Donald D. Chamberlin、Raymond F. Boyce 给我们带来的 SQL,让我们在数据领域有一个(尽管非常薄的)通用基础。实际上所有的计算平台,不管它开始的设计如何,如果它志在变得流行和实用,最后一定会加入 SQL 的支持,就像 Elastic,MongoDB,Spark 等等标新立异的产品的演变过程一样。



但是,我又要说“但是”了,SQL 在不同的实现下,其语法和函数功能也是千差万别,我们依然还是需要经过学习才能掌握他们之间表达和能力上的差异。你如果期望有一个“标准”通用 SQL 能通行数据平台,那你很可能就像遇到一个自称中文聊天毫无障碍的老外,对你说了半天“雷猴”你都不知道他在说什么一样错愕。正如中国方言种类之多,SQL 的方言也不遑多让。我不知道你有没有准备好学习这些 SQL 方言和行为上的差异。



多源异构数据关联需求


如果在这些数据库种类之间选择还不足以让你难堪,那让你做一个应用并支持在这些数据库之间“灵活”取数进行关联呢?例如,你的老板需要评估不同营销渠道的效果,分析广告投放与实际销售之间的关系,或评估社交媒体活动对品牌知名度的影响,以优化未来的营销策略。而只有你知道广告投放数据、销售数据、社交媒体互动数据和客户反馈数据,分布在不同的系统中,可能包括关系型数据库、NoSQL 数据库以及外部服务的 API 数据。如何完成老板的这一设想和保住我们不多的头发也许不能兼顾了。


03

分析型查询实现的基本思路

03

如何应对这些挑战,我们团队总结了一些思路供大家参考。


贴源计算

我劝你别没事找事。——《我是黑社会》郭德纲,于谦


黑社会当然不值得效仿,但是不妨碍我们领略这句话中的禅意。随着组件的增加,系统的复杂性不是线性增长而是指数增长,道理如此简单,以至于我们可能若干年都熟视无睹。就像一个班级 60 个人有生日相同的人的概率大于99%,而不是 365÷60 一样是如此显然和反直觉。


数据运算如何才能加快?其中一个思路,就是将计算在数据存储的地方完成。即使像 Amazon 的 S3 文件存储系统,不是一个典型的数据处理系统,它提供的 Glue 查询接口都支持谓词下推,我们有什么理由不在所有更加高级的数据处理平台上充分利用平台自身的能力,将计算下推到最源端呢?何况近来 Lakehouse(湖仓一体)架构的流行也基本上能支持直接在明细层搭建数据应用了。



衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图2)



编译引擎

一法度衡石丈尺,车同轨,书同文字。——《史记・秦始皇本纪》


作为一个数据分析应用,如果不或者少进行数据搬运,那么就需要同多种数据平台打交道。应用服务的开发人员不仅需要熟练掌握每一种数据库的语法和用法,并且每个业务逻辑的处理都需要编写不同的 ORM 获取数据,甚至干脆针对每次的需求编写不同数据库进行储存。这样的方式我们称之为原生 SQL 开发范式



与之相对的,衡石采用的是编译器 SQL 开发范式。相比于针对每个业务逻辑用不同的方言实现,将业务逻辑用同一个规则以统一的语言表示(即所谓的 HQL,Hengshi Query Language,SQL 的一种方言),再用业务无关的翻译器翻译为不同的 SQL 方言是更易扩展、有效率的范式。


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图3)




通常来说实现这样的翻译转化引擎,需要实现几个部分:

Core Broker,将 HQL 逻辑拆解为 Core 算子的语法树。业务层的所有高级算法(例如对比计算、留存计算、行为计算等)拆解为最基础的算子。

General Optimizer(通用优化器),这里针对 Core 算子进行初步的计算改写和通用优化,典型的有关联顺序交换,谓词下推,常量计算。

Language Backend(在图中简称为 LB 的部分),这里进行 Core 算子到具体数据库方言的转化。虽然这部分工作比较繁杂且需要不断积累,但设计得当的话只需要针对有限的 Core 算子在有限的数据库方言上进行实现就可以了。另外 LB 中也需要有一些改写的逻辑,和上面的优化器有部分工作是重合的,例如某些数据库不支持窗口运算,将其改写为关联运算即可。


这里有一个常见的误区值得提醒——试图使用 Apache Calcite 来实现这个 SQL 翻译引擎。Calcite 在这里是完全不合适的,它更合适的是作为单一的 SQL 计算引擎的解析器来使用。例如,你自己需要实现一个 Doris 或者 Spark 之类的计算平台,那么 Calcite(以及其内置的很多 Optimizer)会给你很多启发和帮助。


虽然坊间传言衡石的名称寓意是通过指标体系建立标准,统一指标口径的度量衡,个人觉得是后来的穿凿附会之说。不过通过 HQL 能够弥合诸多 SQL 的方言差异,给使用者统一的界面,在我们产品内部是起到了类似秦皇“书同文”的效果(虽然实际的情况是我们为这个世界又创造了一个新的、拙劣的编程语言!)。



多源异构数据的联动过滤

每一个数学公式都会使读者减半。——斯蒂芬·霍金


已故英国物理学家霍金在某一版的《时间简史》(A Brief History of Time) 中引述过一句出版商对他的警告: 每一个数学公式都会使读者减半。霍金表示完全认同,但他还是在这本科普书中用到了一个公式:E=mc²。


到目前为止,本文还没有出现代码,这显然不太科学。因为真正能看懂并批评或者认同本文的读者大概率是一个程序员,而我们程序员信奉的一条重要准则是“Talk is cheap. Show me the code”。那接下来我们就将用一些实际的代码来展示我们对 SQL 计算能力的一些思考和扩展。我会为这些代码配上一些插图让非程序员的群体也能有一个直观的了解。


还记得文章第二部分那个老板吗,老板的需求要在多个业务数据库中联合查询才能得到。比如其中一个诉求:查询广告投放效果最好的三个活动分别花费的多少预算?


平平无奇的一个问题。手快的朋友5秒钟就写完SQL实现了:


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图4)

这个宽表关联的方法很标准,虽然 ad_spend 和 marketing_performance 分别在2个不同系统中存储,正如下图左侧示意的过程,只需要将 ad_spend 这张预算消耗的表同步到 marketing_performance 相同的存储系统中就可以了。


在一个理想的世界中,本任务到此就做完了,不过在我们这个不那么完美的世界中,我们的麻烦才刚刚开始:

一个活动的的费用消耗在 ad_spend 表中有多个记录,上面的关联中 SUM({spend}) 计算结果包含了这些所有记录,这很好。不过副作用是 SUM({conversions}) 也被这个关联影响导致数据出现了不应该出现的重复。

ad_spend 表每天都在更新,需要有一个手段能定时甚至实时在这2个系统间同步这些变动。


宽表关联导致的重复,可以用子查询的方式来避免:


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图5)


但是表同步的任务还是必不可少的。而同时老板对我们的的任务完成情况很满意,接下来可能有一些你看起来不切实际的需求:

 能不能做一个自助 BI 系统,让业务自己决定哪一些表进行关联查询?

 甚至能不能做一个 AI 智能查询助手,让业务能问一些自然语言的问题,系统自动做关联查询?


这个阶段相比上一阶段的困难在于,需要同步哪些表是在业务问题提出的时候才确定的!数据部门应该在何时以什么样的频率同步哪些表?


这里有一个部分的解法:采用异构过滤的方法。如下图的右侧示意的过程,执行引擎在做完语法解析之后,可以按照数据源的边界,将一个复杂的查询拆分为多个步骤。这些步骤分为2类节点:

语句生成节点,此类节点将生成单个数据源的 SQL 语句,它可能需要其上游节点的结果作为它生成过程中的变量替换的来源。

语句执行节点,此类节点将 SQL 发往计算平台得到二维表结果。如果它是最后一个节点那么它的结果就是最终的结果,否则它的结果将作为其下游的语句生成节点的变量来源。



衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图6)




通过异构过滤的方法能满足很大比例异构关联的需求,大大减少异构系统之间来回传输大量数据的需求。不过它也不能包治百病。其中一个明显的局限是当异构节点之间的中间二维表结果太大时,它就显然不合适。例如,在用户行为分析的场景中,计算上个月有过广告点击行为的这些用户其最终客单价和全体客户的客单价差异是多少?这一问题如果广告数据和下单数据分属不同的计算平台,那么用户列表作为中间表的规模就太大了,会超过异构过滤方法能处理的极限。通常而言这一限制在千条以内为宜。


窗口函数及其一般形式:上下文函数

我不能创造的东西,我就不了解 。——理查德·费曼


需求叠加导致系统复杂性的失控往往源于我们对工具原理不够了解,而不能单纯归咎于业务方需求的天马行空。业务要做的只是描述清楚问题,而开发者要考虑的就多了。这里将介绍我们从聚合函数、窗口函数推广的一般形式——上下文函数。它在解决业务方丰富多样的需求上有非常强的潜力和扩展性。


对 SQL 的了解可以从比较基本的的聚合运算开始,比如一个按照不同类目统计数量之和的需求:



衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图7)




可以看到这里对数据的加工是有几个步骤的,首先是按照类目分组,然后将每个类目内的数量归拢到一起,最后将每个类目内的数量按照指定的 SUM 运算进行求和。


接下来就可以用窗口运算,进行更复杂的下一步计算,比如统计每个类目按年累计的数量:


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图8)




衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图9)




上面的图示,我们可以看到这里所谓的窗口函数,其实就是将聚合运算的结果作为上下文,以 OVER 子句中给定的关联方式将自身的部分内容附加到每一行上,使得每个分组可以拿到其他分组的信息,然后从关联的集合中用另一个聚合函数进行运算以得到一个更有价值的结果。


可惜的是,由于窗口函数语法设计的限制,我们很多业务需求都在实际实现的过程中,离答案咫尺之遥却始终差一口气,最后只能以丧失灵活性为代价把数据抓回应用程序代码中用硬编码的方式来实现。相信每个用窗口函数进行过重度业务开发的朋友都有过砸键盘的冲动。


对此,我们的答案也比较直接——改造 SQL 的语法突破窗口函数的限制。这里我们使用了一个和 OVER 子句类似的 CONTEXT 子句来取代窗口函数的作用,称之为“上下文函数”。上下文函数的能力是窗口函数的超集,其使用也比较简单。可以从一个熟悉的例子出发,比如上面统计每个类目按年累计的数量的需求,我们用上下文函数实现如下:

衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图10)

这里你会发现除了我们用显式的 JOIN ON 来指定和上下文关联的条件之外,其余的使用方式和窗口函数并无二致。


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图11)


能替代窗口函数只是一个开始,创造一个新的概念肯定是要能够解决新的问题才有它的价值,这里我们可以简单看两个例子,看看窗口函数难以支持的功能,如何在上下文函数中轻松破解。


例子 1:计算不同类目近两年的累计计算。乍一看用某些计算平台的窗口函数中的 ROWS BETWEEN 或者 RANGE BETWEEN 就行了。但是正如鬼佬所谓的魔鬼藏在细节中,当我们的业务数据中出现跨年的间断数据,比如 2023 跳到 2026 如何处理呢?而这一问题在上下文函数中解决起来就毫不令人吃惊:


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图12)


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图13)


例子 2:计算每个年份中,头部类目(也就是 Top 2 类目)的数量占比是多少。这个问题在传统的不管聚合函数还是窗口函数处理起来都是让人绝望。原因在于,我们最终的数据中,只有年份一个维度,这意味着使用窗口函数,其上下文中根本就没有类目的存在,那又如何根据一个不存在的东西来计算呢?在上下文函数中,我们支持 PARTITON ADD 功能,这样就在上下文里无中生有地变出了我们需要的类目维度,同时又不需要也不改变初始查询的维度。结合 RANK 函数过滤出 TOP 2,问题就迎刃而解了:

衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图14)


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图15)


希望以上两个例子对上下文函数是做什么的,以及怎么实现有较清晰的展示。不过上下文函数的更多用法这里也难以一一罗列,实际上衡石 BI-PaaS 产品底层实现的大部分高级计算都是以上下文函数为基础。书不尽言,言不尽意,至于这个上下文函数在分析领域能有多大的作用就完全取决于我们用户的创意了。



多源异构数据的聚合关联

人的难题不在于他想采取何种行动,而在于他想成为何种人。——威廉·詹姆斯


这里可以先下一个暴论,当前无论哪个产品或者技术宣传它能够解决你多源异构关联的所有需求,那必定是虚假宣传。好消息是,还是有一部分异构关联的问题可以被解决。如本节标题所限定的异构、聚合结果的关联,这是一个不那么野心勃勃企图,因此它在工程上相对容易实现,而且有很大业务应用价值的功能。


典型的一个使用案例是:“查询每个部门的当期业绩数字,业绩目标,以及目标完成率”。


通常来说,业绩目标可能是一个静态管理的系统(甚至是 Excel 文件)记录,而业绩数字则是一个动态的业务系统中实时地不断的进展。单独展示这两部分内容对于大部分开发来说不会是一个难题,但对于一个自助系统要完成这个需求,还是需要引入异构聚合关联的能力。


如果不考虑异构的情况,上面的案例可以用类似这样的 SQL 实现:


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图16)


确实,我们最终的解法和你想的一样,在业务端 HQL 的用法基本就是如此,只不过执行引擎要稍微做一些调整。具体来说,需要识别业务查询中的异构关联节点边界,把处于边界上的节点之结果缓存在内存引擎中,然后生成后续的 SQL。和异构过滤不同的是,将结果缓存到内存引擎中之后,只需要给下游节点传递临时表的名字,而不需要传递完整的二维表了。


衡石科技八周年|赖林华:新形势下数据分析框架的一般原理(图17)


至此,构建分析应用的一般手段我们都已经介绍完毕了。至于是否在应该在你的项目中使用上述的一种或者多种思路,取决于你是否有这样的需求,而不是你掌握它们。这个道理和心理学家 William James 揭示的一样,行动是为了服务理念,而不是反之。



   结语



本文介绍了通用数据分析应用构建中可以采用的一些常用范式。当然数据技术栈是一个很庞大的话题,没有办法在一篇文章讲得面面俱到,比如 SQL 适配过程中各个数据平台的特点和局限;数据缓存的分层管理和时效性取舍;国际化多语言的动态生成和排序规则实现;单点登录的灵活性如何从程序硬编码走向配置化再走向产品化等等。这里每个细节的处理都可以展开出失控的篇幅,毕竟 BI 产品的开发也是一个复杂的系统工程,本文也就只好到此就收手了。


如果你看到了这里,那也许这篇文章也是给你你一些微不足道的启发,何不给我们一个免费的赞呢?还没有关注的话,也可以关注起来,后续公号还会陆续推出一些更细节的分析和数据领域的有用干货。也欢迎大家联系我们进行技术探讨。





立即体验 HENGSHI SENSE
让商业分析触手可及

电话咨询:15810120570

公司邮箱:hi@hengshi.com

北京市海淀区西小口路66号中关村东升科技园B-2楼A301室

上海市黄浦区延安东路550号海洋大厦29楼2903室

广东省深圳市光明区光源五路宝新科技园4栋707号

扫码关注