实时数仓怎么建:基于 StarRocks 的分层设计与实践

一、引言

上一篇文章讲述了使用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,也不是独立物理表;复杂主题建模和指标层通常应优先考虑异步物化视图。
相关推荐
roman_日积跬步-终至千里2 小时前
【计算资源节省与稳定性治理】自助查询系统请求幂等性设计
大数据
小玮看世界3 小时前
当“安全“变成“教训“:《拟人化暂行办法》时代,AI护栏的过度拒答之困与破局
大数据·人工智能
FserSuN4 小时前
回顾Spark的概念与应用
大数据·分布式·spark
一知半解仙5 小时前
大国博弈与机器人纪元:我们正站在怎样的历史拐点?
大数据·人工智能·机器人
亚古数据5 小时前
亚古数据:新西兰公司工商报告Company Extract全解析
大数据·人工智能
baopixiaoz6 小时前
AI量化策略师|Web3 量化交易研究员
大数据·人工智能·python·区块链
IT古董6 小时前
【WMS学习笔记系列】01-需求分析
大数据·数据库
成长之路5146 小时前
【数据集】MD&A管理层讨论与分析文本(2001-2025年)
大数据
浅念-8 小时前
MySQL 索引底层完整详解|磁盘Page|B+树推导|聚簇非聚簇索引|索引SQL操作
大数据·数据库·b树·sql·mysql·面试·职场和发展