简介:《火山引擎智能数据洞察平台v2.0》PPT面向数据分析师、产品运营及企业数据平台建设者,系统梳理智能数据洞察(eABI)的产品能力与落地路径,帮助读者理解从数据接入、建模、查询分析到可视化表达与AI洞察的完整链路。资源包仅含1个PDF文件,大小约21.54MB,由数据平台解决方案团队于2020年12月出品,内容以架构图、能力矩阵和功能拆解为主,适合方案汇报、产品选型与内部培训参考。目前已有789人学习下载。PPT重点展开报表制作、探索分析、数据洞察三大能力栈,并说明跨源融合、可视化建模、秒级查询与嵌入式分析等模块;同时呈现平台从2014年内部启用到2020年对外服务的成长路径,以及31K+数据模型、73.5PB数据量、每日150万+查询等规模数据,便于快速把握产品全貌与建设思路。
1. 从“快”到“对”:智能数据洞察平台的真正瓶颈
智能数据洞察这件事,最容易踩的误区是把“快”当成唯一目标。某内部 BI 平台在 2020 年底披露过一组数字:31K 个数据模型、73.5PB 存储体量、日均 150 万次查询、每天 10 万人次打开报表。在这个量级上,把一条 SQL 从 30 秒压到 2 秒,业务方的体感提升,远不如“同一份 GMV 在六个看板里给出六个数”带来的伤害大。所以这套 v2.0 架构真正砸力气的地方不在查询那一下,而在数据源接入之后那层看不见的语义约定:口径、维度关系、指标归属。下面按“数据怎么进来、怎么被定义、怎么被算出来、怎么被表达出去”这条链路,把三层能力栈拆开看。
2. 跨源融合:一张图表凭什么能同时读六种数据源
传统做法是先做数仓、再做 BI,数据和展示之间有明确边界。但这套平台面对的场景是:订单数据在关系型数据库里,用户收藏记录在离线数仓里,下单转化在列式分析引擎里,活动地推是运营发来的 CSV,还有一些第三方渠道只能走 RESTful 接口。如果每个源都先 ETL 搬一遍再分析,业务方提需求到拿到图表的周期是周级,早就凉了。
跨源融合的本质,是在“搬数据”和“搬计算”之间做了一次交换:能不搬的就不搬,把计算推下去,把结果收上来。
2.1 逻辑统一层:把异构源折叠成一个数据集
平台在数据接入层支持四类入口:应用系统与第三方接口、文件类(表格与文本)、离线大数据平台与传统关系库、流式管道。接入之后走同一套同步体系,包括编排与调度、流式同步、批量同步、API 集成、文件上传,配套数据依赖管理、数据回溯和性能调优。
关键设计在“快速数据集”这一层:它不是一个物理表,而是一份逻辑定义。用户在界面上把六张来源不同的表拖到一起,声明 join 关系,平台记下来的是一个逻辑数据集。业务方在看板上选指标和维度时,感知不到底下有几套存储。
-- 逻辑数据集:订单主题。它不是物理表,只在查询期被展开 CREATE LOGICAL DATASET order_topic AS SELECT t.order_id, t.user_id, t.pay_amount, t.channel, -- 来自接口渠道 h.region, -- 来自离线数仓的维表 c.conv_flag, -- 来自列式引擎的转化宽表 t.dt FROM api_src.channel_order AS t -- RESTful 分页接口 JOIN dw.region_dim AS h -- 离线数仓维表 ON t.user_id = h.user_id LEFT JOIN olap.order_conversion AS c -- 列式分析引擎 ON t.order_id = c.order_id WHERE t.dt BETWEEN ${start_dt} AND ${end_dt}; -- 由看板时间控件注入这段定义里有三个细节值得注意。第一,${start_dt}和${end_dt}是参数占位符,不是常量,意味着同一份逻辑定义能被无数张图表复用,每次查询把时间范围换掉即可。第二,api_src.channel_order是接口源,它没有“扫描全表”这个能力,只能分页拉取,所以执行期必须改写,这一点在下一节展开。第三,join 关系在逻辑层就固定了,业务方拖拽时选的是region、channel这些语义字段,而不是表名加列名,这是后面所有口径统一的地基。
逻辑数据集带来的直接好处是复用。31K 个数据模型的规模,如果每个看板都各写一份 SQL,口径漂移是必然的;而数据集是共享对象,改一处,所有引用它的图表同时生效。
2.2 跨源 JOIN 的执行路径与下推策略
逻辑定义只解决“怎么写”,真正干活的是查询分析系统。它拿到的是一棵树,需要判断哪部分推到源端、哪部分拉回融合层。常见的实现思路是三条规则加一个兜底。
def plan_cross_source_join(left, right, on): # 规则一:小维表优先广播,别把亿级事实表拉到融合层 if right.estimated_rows < BROADCAST_THRESHOLD: # 阈值通常取 50 万行量级 return BroadcastHashJoin(small=right, probe=left) # 规则二:没有谓词下推能力的源,只回查主键,绝不整表拉取 if right.source_type == "REST_API": keys = left.scan_keys(on.left_key) # 先从本地扫出主键集合 return KeyLookupJoin(keys=keys, batch_size=1000) # 按批回查,控制单次请求体积 # 规则三:两侧落在同一个引擎里,整段下推,融合层不参与 if left.engine == right.engine: return PushdownJoin(left, right) # 兜底:大表 × 大表且跨引擎,落到融合层做 shuffle return ShuffleJoin(left, right, on)参数和分支的含义要讲清楚。BROADCAST_THRESHOLD决定广播还是洗牌,取值本质是在“网络搬运小表”和“两侧各自 shuffle”之间权衡,维表小的时候广播几乎总是更优。batch_size控制回查接口的批大小,取 1000 是个经验值,太小会被网络往返次数拖死,太大又容易触发接口侧的单次请求上限。规则三是最舒服的情况,能整段下推意味着中间结果不外传,融合层只接收最终聚合结果。
这里的血泪经验是:跨源查询的性能问题,九成不是引擎慢,而是执行计划把不该拉的数据拉回来了。一个不带谓词下推的接口源,如果把全量订单拉进内存再做 join,几百 GB 直接就把节点打爆。
2.3 物化、缓存还是实时拉取
73.5PB 的体量下,任何“全都实时”的设想都不成立。平台实际采用的是分层策略:明细层保证秒级响应,汇总层做预计算,跨源部分用缓存兜住高频查询。
明细查询走列式引擎,依赖列裁剪、分区裁剪和向量化执行;汇总查询走预聚合表,看板打开时命中的往往是已经算好的 cube;跨源的长尾查询则依赖结果缓存,缓存键里必须带上权限范围,否则会出越权命中。同步体系里的数据回溯能力是这套分层的兜底:某个分区算错了,可以按依赖链重跑,而不是全量重刷。
取舍点很明确:物化换性能和一致性,实时换新鲜度。平台把选择权交给了场景:大屏要刷新的指标走实时路由,专题报告走预计算路由,用户在建数据集时就能指定刷新策略。
2.4 场景化建模与 AI 算子的下沉
数据准备环节不止有筛选、连接、聚合、合并、过滤、转置这些清洗算子,还内置了判别、预测、分类、聚类、回归等算法能力。这部分常被误解成“BI 里塞了个机器学习模块”,实际的设计意图不一样。
算法被包装成数据准备链路里的一个算子,输入是数据集,输出是新的一列(比如“流失概率”或“用户分群标签”),这一列随即成为可拖拽的维度或指标。业务方不需要知道训练集怎么切、特征怎么归一,他看到的只是“给这个数据集加一列预测值”。这种把算法降低为表达能力层级的做法,是“让非专业群体也能用上模型”的落地方式,而不是另起一个建模平台。
3. 语义层:FIXED 表达式为什么能让聚合“跳级”
如果只有跨源融合,这套平台和“能连很多库的图形化 SQL 工具”没有本质区别。真正拉开差距的是中间那层语义定义:它决定了业务方拖出来的每个数字,到底是怎么算的。
3.1 维度、指标与表达式字段的三层结构
语义层把字段分成三类。维度是分组依据,指标是聚合结果,表达式字段是前两者的组合产物。平台支持个人表达式字段,也支持在数据集级别定义公共指标,同时还配了逻辑字段、行列过滤、下载控制这些治理开关。
这个分层解决的是一个老问题:业务方想看的指标,往往不是表里现成的一列。比如“购买次数分布”,它既不是 user 表的列,也不是 order 表的列,而是“先按人聚合,再按次数分组”的两级结构。传统做法是让数据开发写一张中间表,业务方等两天;语义层的做法是让业务方自己表达出来,平台负责翻译成 SQL。
值得强调的是“认证数据集”这个概念。官方口径的指标只在认证数据集里定义一次,看板、大屏、订阅、嵌入页面、开放接口全部引用同一份定义。这是统一口径唯一靠谱的实现路径,靠流程约束是压不住的。
3.2 FIXED 表达式:先算人,再算人的人群分布
原料里给了一个很典型的例子:用{fixed[用户id]:uniq(订单id)}作为维度,查看“买过 N 次的用户有多少人”。FIXED 的含义是“固定按某个维度计算,不受当前视图维度影响”。
-- 业务方在界面上只做了一件事:把表达式字段拖成维度 -- {fixed[用户id] : uniq(订单id)} → 购买次数 SELECT times AS 购买次数, count(DISTINCT user_id) AS 用户数 FROM ( -- 第一层:固定按 user_id 聚合,算出每个人买了几次 SELECT user_id, count(DISTINCT order_id) AS times FROM order_fact WHERE status = 'succeed' GROUP BY user_id ) t GROUP BY times ORDER BY times DESC; -- 第二层:对"次数"这个结果再分组这段展开说明了 FIXED 的实现本质:它强制生成一个嵌套子查询,把“固定维度上的聚合”压到内层,外层再按普通的维度指标逻辑处理。如果没有 FIXED,直接写GROUP BY user_id, times会得到循环引用的错误,或者退化成按订单粒度计数,结果全错。
参数上有两个坑。uniq是近似去重还是精确去重,取决于引擎实现,数据量大时会用近似算法换速度,误差在千分位量级,做财务对账时不能接受。status = 'succeed'这类过滤条件写在内层还是外层,会直接改变分母,这是下一章排查里最常翻车的地方。
3.3 自动 DDL 与行列过滤:建模期的隐式编译
接入一个新数据源时,平台会自动推导表结构、生成 DDL、处理字段重名。看起来是个便利功能,实际上是语义层能工作的前提:只有字段元数据被规整成统一形态,后面的拖拽、表达式、权限注入才有落脚点。
行级和列级过滤则是治理能力的落点。行级过滤按用户所属范围注入谓词,列级过滤控制敏感字段是否可见,下载控制决定这批数据能不能导出。这些规则不是靠业务方自觉,而是在查询生成阶段自动拼进 SQL。
-- 语义层在编译期自动注入,业务方看不到也改不了 SELECT region, sum(pay_amount) AS gmv FROM order_topic WHERE dt >= '2020-12-01' AND dt < '2021-01-01' AND region IN (${user.allowed_regions}) -- 行级权限,来自项目中心下发 AND ${user.can_see_cost} = 1; -- 列级开关,控制成本字段是否参与${user.allowed_regions}这类变量由权限中心在会话建立时下发,SQL 文本里不出现具体值,所以同一条查询对不同用户走的是不同的执行计划,也因此不能共用同一份缓存结果。这一点在后面排查章节还会再提一次。
3.4 统一口径的落地:指标只写一次
数据问答这类能力,容易被当成一个独立的自然语言模块。但在这套架构里,问句解析出来的落点仍然是语义层:识别出维度、指标、时间范围,然后拼成对认证数据集的查询。这意味着用户问出来的数字,和看板上的数字来自同一份定义,不会因为“AI 自己造了个口径”而对不上。
同一份指标定义,被看板、报告、大屏、移动驾驶舱、第三方嵌入、开放接口同时消费。这个“一次定义、多处消费”的结构,是 3.4 万份报表还能保持口径一致的根本原因。反过来,一旦有人在非认证数据集里私自改了个口径,漂移就会从那里开始蔓延。
4. 秒级查询:自研引擎在大屏刷新时到底做了什么
原料里提到自研查询引擎“处理速度至少提高 100 倍”,同时强调高可用、实时性、数据安全、集群运维四个维度的改造。这个数字不必纠结绝对准确,值得拆的是它靠什么拿到这个量级。
4.1 从一次拖拽到执行计划
业务方在界面上拖一个维度、加一个指标、选个时间范围,这个动作最终会被编译成一份结构化的查询描述,而不是拼接 SQL 字符串。
{ "cube": "order_topic", "group_by": ["region"], "metrics": [ {"name": "gmv", "agg": "sum", "expr": "pay_amount"}, {"name": "buyers", "agg": "uniq", "expr": "user_id"} ], "filters": [ {"field": "dt", "op": "between", "value": ["2020-12-01", "2020-12-31"]} ], "route": "olap_realtime", "fallback": "olap_batch", "cache_key": "order_topic|region|gmv,buyers|dt:202012|acl:u_1024" }这份描述里,route决定走实时还是批处理路由,fallback是降级路径,cache_key里带上acl前缀是为了隔离权限。用结构化描述而不是裸 SQL 的好处是:引擎可以在中间做改写,比如把uniq换成更快的近似实现,或者把某个指标从明细表切到预聚合表,业务方的拖拽动作完全不用变。
4.2 预聚合、列裁剪与缓存命中
查询路由的判定顺序,基本决定了响应时间的天花板。
def resolve_route(query, user): key = build_key(query, acl_scope=user.scope) # 权限进 key,避免越权命中 if hit := cache.get(key): # 一级:结果缓存 return hit rollup = pick_rollup(query) # 二级:命中最细粒度的预聚合表 if rollup and rollup.covers(query): return execute(rollup, query) pruned = query.cube.prune_columns(query.metrics) # 三级:回明细,先做列裁剪 return execute(pruned, query)build_key里带acl_scope是必须的。曾经有平台把权限从缓存键里拿掉换命中率,结果两个不同区域的用户看到同一份结果,属于典型的因小失大。pick_rollup负责在预聚合表里找覆盖度最高的那张,覆盖度不够就退回明细表。prune_columns只读查询需要的列,在列式存储上这一操作能减少一个数量级的总读取量。
预聚合表的粒度设计是个权衡。粒度过粗,很多查询覆盖不到,白白维护;粒度过细,表的数量膨胀,维护成本反噬。常见的做法是按业务主题分别建设,而不是全量铺开。
4.3 大屏实时刷新与并发隔离
数字大屏和移动驾驶舱的数据刷新,基本会拉高平台的整体并发负载。假设有若干块大屏配置了高频刷新,并且刷新时间点相同,就会造成瞬时并发激增。
对应的处理通常有三层:刷新请求错峰调度,避免同一秒涌入;实时查询和交互式查询走不同的资源池,互不抢占;结果缓存做短周期兜底,让高频重复查询不必真的访问底层存储。监控体系里,这部分要有独立的排队时长和失败率指标,否则查询变慢时根本定位不到是大屏引发的。
4.4 把通用引擎改造成分析引擎的几个动作
原料提到这类引擎是基于社区版做了大量开发和优化。这类改造通常集中在几个方向:一是高可用,副本与故障切换策略要适配长连接的查询场景;二是实时性,写入即查需要处理好小文件合并和可见性延迟;三是数据安全,权限校验要支持到行级,而不是只在库表级别拦截;四是集群运维,扩缩容过程不能中断正在跑的查询。
说句实在的,这四项里最耗人力的往往不是性能优化,而是数据安全和集群运维。性能可以在压测里调参,权限和运维的坑是真实业务流量喂出来的。
5. 常见问题排查:跨源口径、LOD 与查询超时最容易翻车的五个地方
以下五条,来自一个日均百万级查询的平台上反复出现的真实类型,每一条都按“现象 → 原因 → 解决”走。
5.1 看板数和明细导出数差几万行
现象:某个订单主题的看板显示 87 万单,业务方从同一数据集导出明细,只有 84 万行,差了约 3%。
原因:跨源 join 时,接口源这一侧存在分页上限,超出部分静默截断;另外接口返回的时间戳是服务器本地时间,而离线数仓按 UTC 分区,跨日边界上会漏掉几个小时的数据。
解决:给接口源加“拉取完整性校验”,比对计数和主键最小值、最大值;时间字段在数据集定义时就统一转成 UTC 并显式标注时区;在数据同步的编排里加一步行数对账,对不上就告警而不是继续跑。
5.2 FIXED 表达式加进筛选器后结果变了
现象:单独看{fixed[用户id]:uniq(订单id)}分布是对的,一旦把它拖进筛选器(比如只看购买次数大于 3 的用户),总数就变了。
原因:筛选条件被下推到了内层子查询,变成“先过滤订单再加聚合”,语义等价于把一部分订单从统计中剔除了,和内层聚合的原子性冲突。
解决:FIXED 表达式产生的字段,筛选必须落在外层,平台在编译期要能识别“这个谓词依赖内层聚合结果”,禁止下推。排查时直接看生成的 SQL,谓词出现在GROUP BY user_id之前就是错的。
5.3 同环比数值突然变成空
现象:某张趋势图的同比计算正常,环比在某些日期区间整段为 null。
原因:日期维表缺失了那几天(节假日补数据没跑),导致自连接找不到对应的上期记录;还有一种是时间窗口末端不完整,用了不完整周期去比完整周期。
解决:日期维表单独做完整性巡检,缺失即阻断依赖它的报表刷新;同环比的计算口径里显式声明“不完整周期不参与比较”,而不是让它默默算出个错的数字。
5.4 大屏刷新时段查询集体超时
现象:每天早上固定时间,几十块大屏同时刷新,交互式查询排队时长从几百毫秒涨到十几秒。
原因:缓存集中失效加刷新时间点重叠。缓存键里带了日期参数,跨天时整批失效,所有大屏同时回源。
解决:刷新任务做抖动,把同一时刻的请求摊到几分钟内;缓存过期时间加随机偏移;大屏查询和交互查询分资源池,前者失败可以降级到上一次结果,后者不行。
5.5 有权限的人看不到数据
现象:两个同区域的用户,一个人能看到完整数据,另一个只能看到一小部分,权限配置看起来一模一样。
原因:缓存键里只带了角色,没带用户所属的项目范围,导致命中了别人会话写入的缓存结果;或者行级权限是在 SQL 生成之后才注入的,被缓存层绕过了。
解决:权限范围必须在查询描述生成阶段就固化进cache_key,越权命中的风险远大于缓存命中率收益。上线前用“同角色不同范围”的两组账号做对拍,比看权限配置界面可靠得多。
6. 设计取舍:宁可牺牲实时性,也要保住口径
把五层能力栈摆在一起看,会发现这套架构有大量“故意不做到极致”的地方。明细数据要求秒级,但汇总数据允许预计算;跨源查询支持即时联合,但接口源必须限制批量回查;AI 建模能力内嵌在数据准备里,但输出的只是数据集里的一列,不是一个独立的模型服务。这些选择的共同指向是:让业务方在自助化时,仍然踩在一条确定的语义轨道上。
最典型的取舍是实时性。73.5PB 的规模下,如果所有查询都回明细表,再强的引擎也扛不住百万级日查询。所以平台用预聚合换响应时间,代价是“刚发生的数据可能还没进汇总层”。这个代价被显式暴露给用户,而不是隐藏起来——用户可以选实时路由,但要接受更长的等待,这是一个可以被理解、被接受的降级,而并非一个黑匣子。
第二个取舍是自助化与管控的边界。全流程自助化是明确方向,从即席查询到可视化查询,再到自助建模,每一步都在降低门槛。但与此同时,数据治理和安全管控这条线必须同步收紧:认证数据集、行级权限、列级脱敏、下载控制,全都是在“让你能自己玩”和“别把不该看的看了”之间划的那条线。这条线画得松,平台就不敢开放给一线的业务人员;画得太死,自助化就成了摆设。
第三个取舍是 AI 的位置。把算法做成数据准备链路里的算子,而不是单独开一个建模产品,好处是使用成本极低,坏处是模型的可解释性和可调性受限。这是一个明确的取舍:面向广泛的业务人员,牺牲一部分专业能力是划算的。
如果要验证这套设计是否真的成立,方法其实很简单:挑一个跨三四个数据源的指标,让人分别从看板、订阅邮件、嵌入页面和开放接口取数,四个地方的数字必须完全一致。对不上的地方,就是语义层有洞的地方。我曾经在一个项目里因为觉得“这几个看板看起来差不多,应该没事”,跳过了这一步,结果上线两周后被财务发现同一份营收在三个页面有四个版本,回头查了两天才定位到是一个非认证数据集里手写了过滤条件。从那以后,我每次上多数据源报表前,都强制走一遍“同指标多出口对拍”,哪怕再赶时间也不跳过。希望帮到你。
本文还有配套的精品资源,点击获取