3. 聚合模型(Aggregate Key Model)

聚合模型,说白了就是 StarRocks 里专门为「预聚合」场景设计的利器。我最早接触它的时候,第一反应是——这不就是物化视图的变种吗?后来用深了才发现,它比物化视图要底层得多,也高效得多。

它的核心思想很简单:在数据导入时就完成部分聚合计算,把多行明细数据合并成一行聚合结果。你想想看,如果一张表每天产生几亿条订单明细,但你的报表只需要看每天的总销售额,那存那么多明细干嘛?直接在入库时算好总和,查询时秒出结果,多痛快。

核心原理: 聚合模型通过定义一组 AGGREGATE KEY(聚合键),在数据导入时对相同 Key 的多行记录,按照指定的聚合函数进行合并。最终存储的是聚合后的结果,而非原始明细。

3.1 模型原理

聚合模型的工作机制,我习惯把它拆成三步来理解:

  1. 定义聚合键: 建表时指定哪些列是 AGGREGATE KEY,这些列就是分组依据。剩下的列都是指标列,需要声明聚合函数类型。
  2. 数据导入时合并: 当数据写入时,StarRocks 会按照 AGGREGATE KEY 对数据进行排序和分组。如果多条记录的 Key 完全相同,就会触发聚合函数计算,合并成一条。
  3. 查询时再聚合: 虽然存储层已经做了预聚合,但查询时如果涉及更高维度的聚合(比如按天聚合的数据再按月聚合),StarRocks 仍然会再做一次聚合。不过因为数据量已经大幅减少,性能依然很好。

我举个例子你就明白了。假设有一张订单表,按「日期 + 商品ID」做聚合键,指标是「销售额」用 SUM 聚合。导入两条记录:(2024-01-01, A001, 100) 和 (2024-01-01, A001, 200)。最终存储的只有一条:(2024-01-01, A001, 300)。数据量直接减半,查询时连 GROUP BY 都省了。

我的经验: 聚合模型最适合「写少读多」的场景。写入时多做一点计算,查询时就能快十倍甚至百倍。我在做电商 GMV 报表时,用聚合模型把原始 10 亿条订单数据压缩到 2000 万条,查询从 30 秒降到了 0.5 秒以内。

3.2 聚合函数类型

StarRocks 聚合模型支持多种聚合函数。我按使用频率排个序,你心里有个数:

聚合函数 说明 典型场景
SUM 求和,数值累加 销售额、流量、时长
MAX 取最大值 最高温度、最大并发
MIN 取最小值 最低价格、最早时间
REPLACE 新值覆盖旧值 用户最新状态、最后登录 IP
REPLACE_IF_NOT_NULL 非空时覆盖 增量更新字段,保留已有值
HLL_UNION HyperLogLog 近似去重 UV 统计、去重计数
BITMAP_UNION Bitmap 精确去重 精确 UV、留存分析
PERCENTILE_UNION 百分位近似计算 P50/P95 延迟

这里我要特别提醒一下 REPLACE 和 REPLACE_IF_NOT_NULL 的区别。我曾经在做一个用户画像表时,用了 REPLACE,结果导入了一条只有部分字段更新的数据,把之前已有的字段值全覆盖成了 NULL。后来改用 REPLACE_IF_NOT_NULL 才解决问题。嗯,这个坑我踩过,你记住就好。

3.3 适用场景

聚合模型不是万能的。我总结了三个最适合的场景:

  • 固定维度的汇总报表: 比如按天、按门店、按品类的销售汇总。维度组合是固定的,不需要随意下钻到明细。
  • 去重计数场景: 用 BITMAP_UNION 或 HLL_UNION 做 UV 统计。我做过一个日活报表,用 Bitmap 聚合后,查询 30 天去重 UV 只需要 200ms。
  • 状态快照场景: 比如用户的最新资料、订单的最新状态。用 REPLACE 聚合,每次导入新数据就覆盖旧数据,查询时直接拿最新值。

那什么时候不适合用?如果你需要频繁查询原始明细数据,或者聚合键维度组合太多导致几乎每条记录都唯一,那聚合模型就没什么优势了。数据压缩不了,反而多了一层聚合开销。这时候我建议你用明细模型。

注意: 聚合模型一旦建好,AGGREGATE KEY 和聚合函数类型就不能修改了。所以建表前一定要想清楚业务需要的聚合维度。我见过有人建完表才发现少了一个维度列,最后只能重建表重新导数据,非常痛苦。

3.4 建表语法

建表语法其实不复杂。我直接给你一个完整的例子:

CREATE TABLE IF NOT EXISTS agg_order_daily (
    event_date      DATE            NOT NULL COMMENT '日期',
    shop_id         BIGINT          NOT NULL COMMENT '门店ID',
    product_id      BIGINT          NOT NULL COMMENT '商品ID',
    order_count     BIGINT          SUM     DEFAULT '0' COMMENT '订单数',
    total_amount    DECIMAL(18,2)   SUM     DEFAULT '0.00' COMMENT '总金额',
    max_amount      DECIMAL(18,2)   MAX     DEFAULT '0.00' COMMENT '最大单笔金额',
    first_order_id  VARCHAR(64)     REPLACE DEFAULT '' COMMENT '首单ID',
    uv_hll          HLL             HLL_UNION COMMENT 'UV近似去重'
)
AGGREGATE KEY (event_date, shop_id, product_id)
DISTRIBUTED BY HASH(shop_id) BUCKETS 16
PROPERTIES (
    "replication_num" = "3",
    "storage_medium" = "SSD"
);

几个关键点我拎出来说一下:

  • AGGREGATE KEY 必须包含所有维度列,且定义顺序决定了数据的排序方式。我习惯把基数小的列放前面,比如日期在前,门店在后。
  • 指标列必须声明聚合函数类型,写在数据类型后面。
  • DEFAULT 值建议都写上,避免导入时出现 NULL 导致聚合异常。
  • HLL 和 BITMAP 类型需要特殊处理,建表时声明为 HLL_UNION 或 BITMAP_UNION。

3.5 数据写入与查询示例

写入数据时,你不需要做任何特殊处理。StarRocks 会自动完成聚合。我演示一下:

-- 写入三条明细数据
INSERT INTO agg_order_daily VALUES
('2024-01-01', 1001, 2001, 1, 50.00, 50.00, 'ORD001', hll_hash('user_001')),
('2024-01-01', 1001, 2001, 2, 120.00, 80.00, 'ORD002', hll_hash('user_002')),
('2024-01-01', 1001, 2001, 1, 30.00, 30.00, 'ORD003', hll_hash('user_001'));

-- 实际存储的只有一条聚合结果
-- (2024-01-01, 1001, 2001, 4, 200.00, 80.00, 'ORD003', HLL合并后的值)

查询时,你可以直接查,也可以再做更高维度的聚合:

-- 直接查询,结果已经是按天+门店+商品聚合好的
SELECT * FROM agg_order_daily
WHERE event_date = '2024-01-01';

-- 按门店汇总,查询时再做一次SUM聚合
SELECT shop_id,
       SUM(order_count) AS total_orders,
       SUM(total_amount) AS total_amount
FROM agg_order_daily
WHERE event_date = '2024-01-01'
GROUP BY shop_id;

-- 使用HLL计算UV
SELECT shop_id,
       hll_union_agg(uv_hll) AS uv
FROM agg_order_daily
WHERE event_date = '2024-01-01'
GROUP BY shop_id;

你看,查询语法和普通表几乎一样。唯一要注意的是,HLL 和 BITMAP 类型需要用对应的聚合函数来查询,比如 hll_union_agg、bitmap_union_count。

我的建议: 如果你用聚合模型做去重统计,优先选 BITMAP_UNION。虽然它比 HLL_UNION 占更多存储,但结果是精确的。我在做用户留存分析时,就因为用了 HLL 导致数据对不上,排查了半天才发现是近似误差。后来全改成 Bitmap 了。

最后,我画了一张聚合模型的工作流程图,帮你把整个流程串起来:

聚合模型(Aggregate Key Model)工作流程 原始明细数据 数据导入(INSERT/Stream Load) 按AGGREGATE KEY分组聚合 存储聚合结果(数据压缩) 查询请求(SQL) 返回聚合结果(秒级响应) 核心逻辑说明: • 相同 AGGREGATE KEY 的多条记录合并为一条 • 指标列按指定聚合函数计算(SUM/MAX/MIN/REPLACE等) • 存储空间大幅减少,查询性能显著提升 注意事项: • AGGREGATE KEY 和聚合函数建表后不可修改 • 不适合需要频繁查询明细的场景 • HLL/BITMAP 类型需用专用聚合函数查询

这张图把聚合模型的完整链路画出来了。从原始明细数据开始,经过导入时的自动聚合,到最终存储和查询。你重点关注中间那个「按 AGGREGATE KEY 分组聚合」的步骤,这是整个模型的核心。

好了,聚合模型的内容就这些。记住一句话:用空间换时间,用预聚合换查询速度。选对场景,它就是你的性能利器;选错场景,它就是你的维护负担。