一、引言
上一篇文章讲述了使用StarRocks建设实时数仓的原则与主题域/指标层构建思路,本文将介绍StarRocks 的分层设计与避坑实践。结合实时数仓实践,可以将 StarRocks 分层定义为:
diff
+-------+------------------+----------------------+----------------------+
| 层级 | 职责 | StarRocks 形态 | 典型表模型 |
+-------+------------------+----------------------+----------------------+
| ODS | 原始接入 | 原始表、外表 | 明细表、主键表 |
| DWD | 清洗事实 | 事实表、维度表、视图 | 明细表、主键表 |
| DIM | 公共维度 | 维表、拉链、当前快照 | 主键表 |
| DWS | 主题汇总 | 聚合表、异步物化视图 | 聚合表、物化视图 |
| ADS | 应用服务 | 宽表、结果表、MV | 主键表、明细表、MV |
+-------+------------------+----------------------+----------------------+
其中,ODS 和 DWD 强调"事实正确",DWS 强调"口径统一",ADS 强调"查询体验"。不要用 ADS 的查询便利性反推 DWD 的建模方式,也不要用 DWD 的规范性约束所有应用场景。
二、ODS 层
ODS 层的第一目标是接住数据并保留原貌。日志类数据通常使用明细表,CDC 类数据通常使用主键表。如果源端是订单、用户、商品这类会更新的数据,主键表更自然;如果源端是行为流水、点击日志、曝光日志,则明细表更合适。
diff
Kafka / Flink / CDC
|
v
+-------------------------+
| ods_event_log_rt | 明细表
| 一行一条事件 |
+-------------------------+
+-------------------------+
| ods_mysql_order_rt | 主键表
| 一行一个订单最新状态 |
+-------------------------+
主键表的一个关键优势是适配实时更新,主键表的PRIMARY KEY具有唯一约束和非空约束;当新数据主键与已有数据相同时,新数据会替代旧数据。主键表还支持导入时通过__op=0实现 INSERT,通过__op=1实现 DELETE。
三、DWD 层
DWD 层要把技术事件变成业务事实。例如,原始订单 Binlog 里可能有多次状态更新,但 DWD 层要明确:这是"订单当前状态事实",还是"订单状态流转事实"。前者适合主键表,后者适合明细表。
lua
ods_mysql_order_rt
|
+-----------------------------+
| |
v v
dwd_trade_order_current_rt dwd_trade_order_status_flow_rt
主键表:订单最新状态 明细表:订单状态变化流水
这种拆分比盲目宽表更重要。因为"当前状态"和"状态流转"回答的是不同问题:前者回答"现在是什么",后者回答"如何变化"。如果混在一张表中,后续指标会反复踩坑。
四、DIM 层
维度层在实时数仓里常被低估。实时链路中,事实表变化快,维度表也可能变化。用户等级、商品类目、店铺状态、渠道归属都可能影响指标口径。
在 StarRocks 中,当前快照维表适合用主键表:
sql
CREATE TABLE dim_user_current (
user_id BIGINT NOT NULL,
user_name STRING,
user_level STRING,
city_id BIGINT,
update_time DATETIME
)
PRIMARY KEY (user_id)
DISTRIBUTED BY HASH(user_id);
对于慢变维,如果只需要按当前维度分析,用当前快照即可;如果需要按历史口径还原,则要设计维度版本、有效期字段,或在 DWD 写入时固化当时维度属性。这里不是 StarRocks 独有问题,而是维度建模基本问题。
五、DWS 层
DWS 层适合承载主题域公共汇总。StarRocks 的聚合表和异步物化视图都可以用于这一层,但侧重点不同:聚合表更像写入时维护的聚合存储,异步物化视图更像声明式预计算和透明加速层。
异步物化视图支持自动刷新、定时刷新和手动刷新;也支持分区刷新,能通过PARTITION BY将基表分区与物化视图分区关联起来,从而按分区刷新。这对实时数仓非常关键,因为大多数实时指标都天然带时间分区。
DWS 层的设计重点是公共粒度:
markdown
交易主题 DWS:
dws_trade_shop_channel_1m
粒度:分钟 + 店铺 + 渠道
指标:下单数、支付数、支付金额、退款金额
商品主题 DWS:
dws_item_sku_1d
粒度:天 + SKU + 店铺
指标:曝光数、点击数、加购数、支付件数
用户主题 DWS:
dws_user_behavior_1d
粒度:天 + 用户
指标:访问次数、点击次数、支付次数、最近活跃时间
DWS 层不要直接服务所有应用细节,否则它会退化成 ADS。它的价值在于稳定、复用和统一口径。
六、ADS 层
ADS 层面向应用。它可以是宽表、结果表、视图,也可以是异步物化视图。与 DWS 不同,ADS 可以为具体看板、接口或分析场景做定制。
lua
DWS 公共指标
|
+--------------------------+
| |
v v
ads_realtime_trade_dashboard ads_shop_operation_board
实时交易大屏 店铺经营看板
ADS 层可以大胆使用宽表,因为它服务的是明确查询场景。例如实时交易大屏通常需要:
时间、店铺、渠道、类目、订单数、支付金额、支付用户数、退款金额、转化率
这类场景适合把指标预聚合到秒级、分钟级或小时级,再让应用直接查询。真正要避免的是把 ADS 宽表反向当成全局事实表使用。
七、物化视图放在哪
StarRocks 的异步物化视图是实时数仓建模的关键能力,但不能滥用。
sql
+-------------------+
| 查询改写 / 加速 |
+---------+---------+
|
v
+---------------------------------------------------+
| 异步物化视图 |
| 1. 多表 Join 预计算 |
| 2. 公共指标预聚合 |
| 3. 湖表 + 实时表统一建模 |
| 4. ADS 应用宽表 |
+-------------------+-------------------------------+
|
+-----------+------------+
| |
v v
DWS 指标层 ADS 应用层
同步物化视图更适合单表聚合加速,同步物化视图本质上是基表索引,不是像异步物化视图那样的物理表;它不支持 Join,也不支持 GROUP BY 的 HAVING 子句。因此,在数仓分层建模中,异步物化视图通常更重要。
八、分区分桶是模型的一部分
很多 StarRocks 建模问题不是 SQL 写错了,而是分区分桶设计错了。StarRocks 采用分区 + 分桶的两级数据分布策略,分区通常按时间或日期拆分,查询时可通过分区裁剪减少扫描量;分桶则把同一分区的数据划分到更小的数据管理单元,并分布到 BE 节点上。
实时数仓中可以遵循以下原则:
vbnet
分区列:
优先选择事件时间、业务日期、支付时间、分区日期
分桶列:
优先选择高频过滤或 Join Key
如 user_id、order_id、shop_id、sku_id
排序键:
优先选择高频过滤列
如 dt、event_time、shop_id、channel_id
一个交易明细表可以这样设计:
sql
CREATE TABLE dwd_trade_payment_rt (
pay_date DATE NOT NULL,
pay_time DATETIME NOT NULL,
payment_id BIGINT NOT NULL,
order_id BIGINT NOT NULL,
user_id BIGINT,
shop_id BIGINT,
channel_id BIGINT,
pay_amount DECIMAL(18, 2)
)
DUPLICATE KEY(pay_date, pay_time, payment_id)
PARTITION BY date_trunc('DAY', pay_date)
DISTRIBUTED BY HASH(order_id)
ORDER BY(pay_date, shop_id, channel_id, pay_time);
这只是示意,真实设计要结合查询模式。如果 80% 查询按店铺和日期过滤,把shop_id放进排序键会很有价值;如果核心查询按用户追踪行为,则user_id的位置要更靠前。
九、宽表和星型模型如何取舍
宽表和星型模型不矛盾。星型模型解决语义和复用问题,宽表解决查询性能和使用便利问题。
| 维度 | 星型模型 | 宽表 |
|---|---|---|
| 适合层级 | DWD、DWS | ADS、部分 DWS |
| 核心价值 | 语义清晰、事实维度分离 | 查询简单、性能稳定 |
| 维护成本 | 维度变化影响较小 | 列多、变更影响大 |
| 查询体验 | SQL 需要 Join | 查询门槛低 |
| 适用人群 | 数据工程师、分析师 | 应用、BI、运营看板 |
| StarRocks 实现 | 事实表 + 维表 + 视图/MV | 主键表、明细表、异步 MV |
一个实用判断是:
sql
如果模型要被多个业务复用,用星型模型。
如果模型只服务一个高频场景,用宽表。
如果 Join 成本高且查询模式稳定,用物化视图把星型模型物化成宽表。
在 StarRocks 中,宽表可以是"物理宽表",也可以是"物化视图宽表"。后者更适合从规范模型派生出来:
sql
事实表 + 维表
|
| CREATE MATERIALIZED VIEW
v
场景宽表 MV
|
v
BI / API 查询
这样既保留了底层模型的清晰性,也满足上层查询性能。
十、指标口径如何治理
实时数仓最怕的问题不是慢,而是快但错。指标层要治理三个东西:
markdown
1. 指标命名
2. 指标口径
3. 指标粒度
例如"支付金额"要明确:
ini
指标名:pay_amount
业务含义:用户实际支付成功金额
事实来源:dwd_trade_payment_flow_rt
过滤条件:pay_status = 'SUCCESS'
时间字段:pay_time
聚合方式:sum(pay_amount)
是否含退款:不扣退款
是否含运费:按业务口径定义
在 StarRocks 中,可以把稳定口径沉淀为视图或物化视图:
sql
CREATE VIEW vw_metric_trade_payment AS
SELECT
pay_time,
shop_id,
channel_id,
user_id,
order_id,
pay_amount
FROM dwd_trade_payment_flow_rt
WHERE pay_status = 'SUCCESS';
然后基于该视图构建 DWS 物化视图。StarRocks 支持基于视图创建物化视图,并且当视图引用的基表发生数据更改时,物化视图可以自动刷新;也支持基于其他物化视图创建物化视图,实现多层级联式刷新。
但这里要注意:层层嵌套虽然优雅,也会增加排障复杂度。核心公共指标可以嵌套,临时指标不要过度物化。
十一、实时性与一致性
实时数仓不是所有层都要"秒级"。不同层可以有不同的新鲜度目标:
ODS:秒级写入
DWD:秒级到分钟级清洗
DWS:分钟级聚合
ADS:秒级、分钟级或按应用要求
StarRocks 异步物化视图的刷新策略包括自动刷新、定时刷新和手动刷新;对于有时序属性的报表,可以通过分区刷新实现近实时计算。 因此,指标层不必总是外部调度计算,也可以让 StarRocks 管理部分依赖和刷新。
不过,物化视图刷新意味着可能存在数据延迟。在视图与物化视图选择上,物化视图结果为预计算,可能存在数据延迟;而视图在查询时返回最新数据,但没有预计算带来的性能收益。 这就是实时性和性能的基本取舍。
可以这样分配:
秒级强实时:
使用明细表 / 主键表 + 视图
牺牲部分查询性能,换最新结果
分钟级近实时:
使用异步物化视图
接受刷新延迟,换稳定性能
固定报表:
使用定时刷新物化视图或结果表
接受周期性刷新,换低成本查询
十二、建模中的常见坑
- 第一类坑是过早宽表化。刚接入数据就把所有维度打进一张大宽表,短期查询很爽,长期口径会散。正确做法是先在 DWD 明确事实粒度,再在 ADS 为高频场景派生宽表。
- 第二类坑是把指标写在应用 SQL 中。这样每个看板都有自己的GMV,每个接口都有自己的pay_amount,最后没有人知道哪个口径是对的。指标层应该用视图、物化视图或统一 SQL 模板固化口径。
- 第三类坑是忽视分区刷新代价。事实表按天分区、物化视图按天分区通常比较自然;如果维度表频繁变化,且 Join 型物化视图默认需要刷新大量结果,就要设计维度更新策略。当其他未做分区关联的基表发生变化时,默认情况下会刷新整个物化视图,但可以选择忽略某些未关联表的数据变化。
- 第四类坑是滥用同步物化视图。同步物化视图适合单表聚合加速,但它不支持 Join,也不是独立物理表;复杂主题建模和指标层通常应优先考虑异步物化视图。