StarRocks hll_empty 函数详解:为 HLL 列生成空值以补齐导入/插入默认值
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
hll_empty()是 StarRocks 内置的 HLL(HyperLogLog)标量函数,用于生成一个空的 HLL 值,在向包含 HLL 类型列的表中执行INSERT或数据导入(Stream Load 等)时,为无法直接从源数据生成的 HLL 列补齐默认值。本文将结合官方函数文档 hll_empty.md 与 BE 端源码实现,完整讲解其语法、语义、两种典型用法、底层实现原理与配套的 HLL 聚合函数生态,帮助你在 UV 类近似去重场景中正确、高效地使用这一函数。
一、为什么需要 hll_empty
StarRocks 的 HLL 类型列基于 HyperLogLog 算法存储中间结果,只能作为表的 value 列使用,常用于替代COUNT DISTINCT快速计算 UV。HLL 列本身不能由普通数据直接写入,而是通过hll_hash等函数由导入数据或表内其他列转换生成。
但在实际写入中会遇到一种情况:目标表的某列是 HLL 类型,而本次插入或导入的数据中并没有能够用于生成该 HLL 列的源字段。此时就需要用hll_empty()生成一个空的 HLL 值来"占位"补齐,保证写入语句能够完整覆盖表的所有列。这正是该函数在设计上的核心用途——文档将其描述为"Generates an empty HLL column to supplement the default values when inserting or loading data"(生成空的 HLL 列,用于在插入或加载数据时补充默认值)。
在 HLL 数据类型文档 中,hll_empty被列入 HLL 相关函数清单,并明确其用途是"generates empty HLL column and is used to fill in default values during inserts or imports"。
二、语法与返回值
hll_empty的语法非常简单,它是一个无参数的标量函数:
HLL_EMPTY()| 项目 | 说明 |
|---|---|
| 参数 | 无 |
| 返回值 | 一个空的 HLL 类型值(empty HLL) |
返回值可以赋给表中的 HLL 类型列,也可以出现在INSERT ... VALUES、INSERT INTO ... SELECT的投影列,以及 Stream Load / Broker Load 等导入任务的columns映射表达式中。
从函数注册信息看,gensrc/script/functions.py 第 829 行将其注册为:
[80030, 'hll_empty', True, False, 'HLL', [], 'HyperloglogFunctions::hll_empty'],其中参数列表为空[],返回类型为HLL,对应的 BE 端实现入口为HyperloglogFunctions::hll_empty,这与文档中"无参数、返回空 HLL"的描述完全一致。
三、使用场景与完整示例
3.1 场景一:INSERT 时补齐默认值
当执行INSERT语句且表结构包含 HLL 列时,如果源数据中没有可用于生成 HLL 的字段,可用hll_empty()补齐:
insert into hllDemo(k1,v1) values(10,hll_empty());其中v1为 HLL 类型列,hll_empty()为该列写入一个空 HLL 值。该写法可类比于普通类型的DEFAULT占位,是官方文档给出的标准示例。
3.2 场景二:Stream Load 导入时补齐默认值
数据导入场景更为常见。以下 Stream Load 示例中,源文件的列通过columns参数进行映射:temp1、temp2为源文件列,col1由hll_hash(temp1)生成,col2则直接用hll_empty()填一个空 HLL:
curl --location-trusted -u <username>:<password> \ -H "columns: temp1, temp2, col1=hll_hash(temp1), col2=hll_empty()" \ -T example7.csv -XPUT \ http://<fe_host>:<fe_http_port>/api/test_db/table7/_stream_load要点说明:
-H "columns: ..."指定源文件列与目标表列的映射关系,列名顺序与源文件 CSV 列一一对应;col1=hll_hash(temp1)表示目标表col1列由源数据temp1通过 HLL 哈希生成;col2=hll_empty()表示目标表col2列(HLL 类型)直接写入空 HLL,无需依赖源文件任何字段;-T example7.csv指定待导入的本地文件;<fe_host>:<fe_http_port>为 FE 的 HTTP 端口地址。
这种"一部分列由源字段哈希生成、一部分列用空 HLL 补齐"的写法,是处理源数据字段与目标表 HLL 列不对等时的标准姿势,可有效避免因 HLL 列无值而导致的导入失败或类型不匹配问题。
3.3 与 hll_hash 的分工
hll_empty与 hll_hash 是 HLL 导入链路中相互配合的两个标量函数:
hll_hash(column_name):将某列的值转换为 HLL 类型,映射源数据字段到 HLL 列(hll_hash.md);hll_empty():不依赖任何输入,直接生成一个空 HLL 用于占位。
简单记忆:有源数据可哈希时用hll_hash,没有源数据时用hll_empty。
四、底层实现:源码视角看 hll_empty
4.1 BE 端函数实现
hll_empty的向量化实现在 be/src/exprs/hyperloglog_functions.cpp 中,与hll_hash、hll_cardinality等函数同属HyperloglogFunctions类(声明见 hyperloglog_functions.h):
// hll_empty StatusOr<ColumnPtr> HyperloglogFunctions::hll_empty(FunctionContext* context, const Columns& columns) { auto p = HyperLogLogColumn::create(); p->append_default(); return ConstColumn::create(std::move(p), 1); }实现要点:
- 创建一个
HyperLogLogColumn(HLL 类型的对象列容器); - 调用
append_default()向列中追加一个默认构造的空HyperLogLog对象; - 将单元素列包装为
ConstColumn(常量列),size 为 1。由于结果是常量列,在向量化执行时可以被 StarRocks 高效广播到目标行的所有位置,几乎不产生额外计算开销。
对比同文件中的hll_hash实现可以看到,hll_hash需要对每行调用murmur_hash64A哈希并hll.update(hash),而hll_empty无任何哈希计算,这也是它作为"占位默认值"在性能上的天然优势。
4.2 空 HLL 在存储层的表示
空 HLL 在存储层对应HLL_DATA_EMPTY编码。在 be/src/types/hll.h 中,HyperLogLog的值类型枚举为:
enum HllDataType { HLL_DATA_EMPTY = 0, HLL_DATA_EXPLICIT = 1, HLL_DATA_SPARSE = 2, HLL_DATA_FULL = 3, };源码注释明确指出:一个 HLL 值会沿着empty -> explicit -> sparse -> full的顺序演进,且不允许回退;这些枚举值会被持久化到存储设备,因此不允许变更已有枚举值。也就是说,由hll_empty()产生的空 HLL 是"最初始、最省空间"的状态,后续只有通过hll_hash/merge等操作写入哈希值后才会升级到HLL_DATA_EXPLICIT等更高形态。
4.3 单测验证
BE 端单元测试 be/test/exprs/hyperloglog_functions_test.cpp 的hllEmptyTest对上述行为做了直接验证:
TEST_F(HyperLogLogFunctionsTest, hllEmptyTest) { { Columns c; auto column = HyperloglogFunctions::hll_empty(ctx, c).value(); ASSERT_TRUE(column->is_constant()); // 结果是常量列 auto* hll = ColumnHelper::get_const_value<TYPE_HLL>(column); ASSERT_EQ(1, hll->empty().size()); // 空 HLL 的序列化大小为 1 字节(类型标识) } }测试断言hll_empty返回常量列,且其中 HLL 值的empty()序列化大小为 1——这与 hll.h 中HLL_DATA_EMPTY仅需 1 字节记录类型标识的实现一致。
五、存储成本与使用建议
由 HLL 数据类型文档 可知,HLL 的存储空间由哈希值中的去重数量决定,分为三档:
| HLL 状态 | 存储成本 |
|---|---|
| 空 HLL(无任何值插入) | 最低,约 80 字节 |
| 去重哈希值 ≤ 160 | 最高 1360 字节(80 + 160 × 8) |
| 去重哈希值 > 160 | 固定 16,464 字节(80 + 16 × 1024) |
这也再次印证了hll_empty()生成的空 HLL 是所有形态中最省空间的,适合作为默认占位值。在业务使用 HLL 时还应关注以下因素(同样来自 HLL 数据类型文档):
- 数据量:HLL 返回近似值,数据量越大结果越准确,数据量小则偏差较大;
- 数据分布:数据量大且 GROUP BY 维列基数很高时,计算会消耗更多内存,此时不推荐使用 HLL;建议用于无 GROUP BY 的去重计数,或对低基数维列做 GROUP BY 的场景;
- 查询粒度:若查询粒度较大,建议使用聚合表(Aggregate table)或物化视图预聚合,以减小数据量、提升查询速度。
六、与 hll_empty 配套的 HLL 函数族
hll_empty通常不是孤立使用的,它与 HLL 函数族共同构成完整的"写入—聚合—查询"链路:
| 函数 | 类型 | 作用 |
|---|---|---|
| hll_hash(column) | 标量 | 将值转换为 HLL 类型,用于导入映射 |
| hll_empty() | 标量 | 生成空 HLL,用于插入/导入时补齐默认值 |
| hll_union_agg(hll) | 聚合 | 估算满足条件的所有数据的基数(总 UV) |
| hll_raw_agg(hll) | 聚合 | 聚合 hll 类型字段并返回 hll 类型 |
| hll_cardinality(hll) | 标量 | 估算单个 hll 列的基数 |
典型的完整链路是:导入时用hll_hash或hll_empty生成 HLL 列 → 通过 Using_HLL.md 中介绍的 Rollup、物化视图或聚合表预聚合 → 查询时用hll_union_agg/hll_cardinality获取近似 UV。在这一链路中,hll_empty保证了"写入端"的完整性,是确保整条链路顺畅运转的兜底组件。
七、小结
hll_empty()是一个体量极小但定位清晰的 HLL 辅助函数:语法上只有一个无参调用,语义上生成最省空间的空 HLL(HLL_DATA_EMPTY),用途上专为INSERT与各类数据导入场景补齐默认值。通过 BE 源码可以看到它在向量化执行中被实现为常量列,几乎零开销;通过单元测试可以确认其输出行为与存储编码完全符合预期。掌握它与hll_hash的分工,再配合hll_union_agg、hll_raw_agg、hll_cardinality等聚合与查询函数,即可在 StarRocks 中构建完整、健壮的近似去重(UV 计算)方案。
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考