StarRocks PERCENTILE_APPROX_WEIGHTED 函数详解:带权重的固定内存近似分位数计算
2026/9/17 20:41:58 网站建设 项目流程

StarRocks PERCENTILE_APPROX_WEIGHTED 函数详解:带权重的固定内存近似分位数计算

【免费下载链接】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

本文围绕 StarRocks 聚合函数PERCENTILE_APPROX_WEIGHTED展开,先完整讲解该函数的语法、参数约束与典型用法(常量权重、列权重、压缩系数、多分位数数组),再结合后端聚合函数实现源码(TDigest 有符号加权累加、压缩系数惰性初始化、短路径短路格式)说明其"固定大小内存 + 权重加权"的底层原理,帮助你在 tp99/tp95 等高基数统计场景中正确、高效地使用这个函数。

函数定位:为什么需要"带权重"的近似分位数

PERCENTILE_APPROX_WEIGHTEDPERCENTILE_APPROX的加权版本:对每个输入值,用户可以指定一个权重(常量或数值列),再返回指定分位点 p 的近似值。p 的取值范围是 0 到 1,可以是单个 DOUBLE 值,也可以是ARRAY<DOUBLE>分位数数组。

官方文档对它的两个关键特性给出了明确说明(见 percentile_approx_weight.md):

  • 固定大小内存:该函数基于固定内存的摘要结构实现,因此在高基数列上计算分位数时内存占用可控;
  • 典型用途:计算 tp99、tp95 等尾延迟/尾分位统计量。

需要留意文档标题与函数名的差异:文档页标题为percentile_approx_weight,但 SQL 中实际可用的函数名是percentile_approx_weighted——这与前端函数注册表 FunctionSet.java 中的常量定义PERCENTILE_APPROX_WEIGHTED = "percentile_approx_weighted"一致。

语法与参数说明

DOUBLE PERCENTILE_APPROX_WEIGHT(expr, BIGINT weight, DOUBLE|ARRAY<DOUBLE> p[, DOUBLE compression])
参数说明
expr要计算分位数的列
p分位数值,必须是 [0, 1] 范围内的 DOUBLE,或是每个元素都在 [0, 1] 范围内的ARRAY<DOUBLE>。例如 0.99 表示第 99 分位
weight权重列,必须是正的常量数值或数值列
compression(可选)压缩系数,取值范围 [2048, 10000]。值越大,精度越高,但内存消耗越大、计算耗时越长;不指定或超出范围时,使用默认值10000

前端注册了 4 个内置签名(见 FunctionSet.java),覆盖了 p 为标量/数组、带/不带 compression 的组合:

  • (DOUBLE, BIGINT, DOUBLE) -> DOUBLE
  • (DOUBLE, BIGINT, DOUBLE, DOUBLE) -> DOUBLE
  • (DOUBLE, BIGINT, ARRAY<DOUBLE>) -> ARRAY<DOUBLE>
  • (DOUBLE, BIGINT, ARRAY<DOUBLE>, DOUBLE) -> ARRAY<DOUBLE>

对 p 的取值范围,前端在计划校验阶段就会报错。AggregateTest.java 中的testPercentileApproxWeightedValidation验证了这一点:select percentile_approx_weighted(v1, v2, 1.5) from t0会抛出percentile parameter must be between 0 and 1 in percentile_approx_weighted, but got: 1.5,负数同理;而weight传常量1、p 传边界值0.01.0都是合法用法。

使用示例

以下示例完整继承自官方文档,可直接复现。先建表并插入 5 万行测试数据:

CREATE TABLE t1 ( c1 int, c2 double, c3 tinyint, c4 int, c5 bigint, c6 largeint, c7 string, c8 double, c9 date, c10 datetime, c11 array<int>, c12 map<double, double>, c13 struct<a bigint, b double> ) DUPLICATE KEY(c1) DISTRIBUTED BY HASH(c1) BUCKETS 1 PROPERTIES ("replication_num" = "1"); insert into t1 select generate_series, generate_series, 11, 111, 1111, 11111, "111111", 1.1, "2024-09-01", "2024-09-01 18:00:00", [1, 2, 3], map(1, 5.5), row(100, 100) from table(generate_series(1, 50000, 3));

1. 使用常量值作为权重

mysql> select percentile_approx_weighted(c1, 1, 0.9) from t1; +----------------------------------------+ | percentile_approx_weighted(c1, 1, 0.9) | +----------------------------------------+ | 45000.3984375 | +----------------------------------------+ 1 row in set (0.07 sec)

权重为常量 1 时,结果等价于普通PERCENTILE_APPROX的 90 分位,可用来做结果对照。

2. 使用数值列作为权重

mysql> select percentile_approx_weighted(c2, c1, 0.5) from t1; +-----------------------------------------+ | percentile_approx_weighted(c2, c1, 0.5) | +-----------------------------------------+ | 35355.97265625 | +-----------------------------------------+ 1 row in set (0.07 sec)

3. 显式指定压缩系数

mysql> select percentile_approx_weighted(c2, c1, 0.5, 10000) from t1; +------------------------------------------------+ | percentile_approx_weighted(c2, c1, 0.5, 10000) | +------------------------------------------------+ | 35355.97265625 | +------------------------------------------------+ 1 row in set (0.09 sec)

4. 传入分位数数组,一次返回多个分位点

mysql> select percentile_approx_weighted(c2, c1, [0.1, 0.5, 0.9], 10000) from t1; +------------------------------------------------------------+ | percentile_approx_weighted(c2, c1, [0.1, 0.5, 0.9], 10000) | +------------------------------------------------------------+ | [15811.6708984375,35355.97265625,47435.01171875] | +------------------------------------------------------------+ 1 row in set (0.03 sec)

数组模式返回ARRAY<DOUBLE>,元素与输入分位数一一对应(0.1、0.5、0.9 分位),适合在一条查询中同时拿到 tp50/tp90/tp99 等指标。

后端实现:权重如何进入 TDigest

后端聚合函数实现在 percentile_approx.h。PercentileApproxWeightedAggregateFunction(标量 p 版本)与PercentileApproxWeightedArrayAggregateFunction(数组 p 版本)共同继承自PercentileApproxAggregateFunctionBase,状态类型为PercentileApproxState,内部持有一个PercentileValue摘要对象。

1. 权重是 int64,逐行加权累加

update主路径中(percentile_approx.h),权重参数按"常量或 int64 列"两种形态处理:

// argument 1: weight can be const or int64 column size_t real_row_num = columns[1]->is_constant() ? 0 : row_num; int64_t weight = columns[1]->get(real_row_num).get_int64(); ... // add value with weight if (LIKELY(weight != 0)) { data(state).percentile->add(implicit_cast<float>(column_value), weight); }

注意两个细节:权重为 0 的行会被直接跳过,不进入摘要;数值在累加前会转换为 float 精度。调用链一路下沉到 TDigest:PercentileValue::add(value, weight)中执行_tdigest.add(value, static_cast<float>(weight))(见 percentile_value.h),最终由 tdigest.h 中声明的add(Value x, Weight w)将"值 + 权重"作为带权质心合并进摘要结构。这正是"加权分位数"的数学基础:分位数是按权重总和(而非行数)累积分布计算的。

2. 压缩系数的取值与边界

基类定义了三组常量(percentile_approx.h):

static constexpr double MIN_COMPRESSION = 2048.0; static constexpr double MAX_COMPRESSION = 10000.0; static constexpr double DEFAULT_COMPRESSION_FACTOR = 10000.0;

这与文档中"compression 范围 [2048, 10000],越界则回退到 10000"的说明完全对应。实现上还有一处值得注意的设计:get_compression_factor(percentile_approx.h)会从最后一个常量参数中解析压缩系数,注释明确说明这是为了适配优化器的SplitAggregateRule——该规则会把常量参数下传给 merge 阶段的聚合器,因此解析逻辑必须容忍权重为列、p 为常量的参数排布。压缩系数本身采用惰性初始化:首次update/merge时才通过reinit_with_compression重建内部的PercentileValue,并对超界值记录 WARNING 后回退默认值。

3. 函数签名注册

后端聚合工厂中注册了 BIGINT 与 DOUBLE 两种expr类型、标量 p 与数组 p 两种形态共 4 条映射(见 aggregate_resolver_others.cpp),与前端FunctionSet中的 4 个签名一一对应。数组 p 版本采用新的序列化格式[count(4 bytes), q1...qn(8*n bytes), TDigest_data]在分阶段聚合的中间结果中携带完整分位数列表(percentile_approx.h),保证merge阶段跨实例合并后仍能恢复出全部目标分位点。

与 PERCENTILE_APPROX 的对比与使用建议

可以结合 percentile_approx 文档 与 percentile_approx.h 中的非加权实现做对比:

维度PERCENTILE_APPROXPERCENTILE_APPROX_WEIGHTED
每行贡献计 1weight(int64,正数)加权,0 权重的行不参与
权重参数第二参数为 BIGINT 常量或列
底层摘要同为 TDigest 摘要(PercentileValue同左,走add(value, weight)带权路径
压缩系数默认 10000,范围 [2048, 10000]同左
内存特征固定大小内存固定大小内存

使用建议:

  • 权重列务必是非负数值列(BIGINT 语义),负权重虽然能通过类型检查,但从源码的加权累加逻辑看,其统计含义并不被设计支持;权重全为 0 时摘要为空,结果无意义;
  • 在数据量极大的高基数列上计算 tp99 时,compression 适当调小(如 2048)可以显著降低内存与耗时,代价是精度下降;对精度敏感的场景保持默认 10000 即可;
  • 需要多个分位点时优先使用ARRAY<DOUBLE>形式一次算完,避免多次扫描;
  • 近似分位数的结果是近似值(文档示例中 45000.3984375 这类非整数值即体现了摘要插值的特性),不要把它当作精确分位数做等值断言。

小结

PERCENTILE_APPROX_WEIGHTED(expr, weight, p[, compression])让 StarRocks 在固定大小内存的前提下支持带权重的近似分位数统计:权重可以是常量也可以是列,p 支持标量与ARRAY<DOUBLE>两种形态,compression 在 [2048, 10000] 内调节精度与资源的权衡。其实现链路为:前端在 FunctionSet.java 注册签名并校验 p 范围,后端在 percentile_approx.h 中逐行执行PercentileValue::add(value, weight),最终落到 tdigest.h 的带权质心合并。理解这条调用链后,你可以按需选择权重来源、分位点集合与压缩系数,在延迟指标等真实业务查询中安全地使用该函数。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询