你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
使用 hll() 和 tdigest()
适用于:✅Microsoft Fabric✅Azure 数据资源管理器Azure Monitor✅Microsoft✅ Sentinel
假设要计算过去 7 天内每日不同用户的计数。 你可以每天运行一次 summarize dcount(user)
,并将时间跨度筛选为过去七天。 此方法效率较低,因为每次运行计算时与上一计算之间有六天的重叠。 你还可以计算每天的聚合,然后将这些聚合组合在一起。 此方法要求你“记住”最后六个结果,但效率要高得多。
如上所述的分区查询对于简单聚合(例如 count()
和 sum()
)很容易。 它还可用于复杂聚合,例如 dcount()
和 percentiles()
。 本文说明了 Kusto 如何支持此类计算。
下面的示例展示了如何使用 hll
/tdigest
,表明在某些情况下使用这些命令是高效的:
重要
hll
、hll_if
、hll_merge
、tdigest
和 tdigest_merge
的结果是类型为 dynamic
的对象,然后可由其他函数(dcount_hll
、percentile_tdigest
、percentiles_array_tdigest
和 percentrank_tdigest
)进行处理。 此对象的编码可能会随时间而变化(例如,由于软件升级);但是,此类更改将以向后兼容的方式完成,因此可以持久存储此类值,并可靠地在查询中引用这些值。
注意
在某些情况下,hll
或 tdigest
聚合函数生成的动态对象可能会很大,会超出编码策略中的默认 MaxValueSize 属性值。 如果是这样,则该对象将作为 null 引入。
例如,在准确度为 4 的情况下保存 hll
函数的输出时,hll
对象的大小超出了默认的 MaxValueSize (1MB)。
若要避免此问题,请修改列的编码策略,如下例所示。
range x from 1 to 1000000 step 1
| summarize hll(x,4)
| project sizeInMb = estimate_data_size(hll_x) / pow(1024,2)
输出
sizeInMb |
---|
1.0000524520874 |
在应用此类策略之前将此对象引入到表中会引入 null:
.set-or-append MyTable <| range x from 1 to 1000000 step 1
| summarize hll(x,4)
MyTable
| project isempty(hll_x)
输出
Column1 |
---|
1 |
若要避免引入 null,请使用特殊编码策略类型 bigobject
,它将 MaxValueSize
重写为 2 MB,如下所示:
.alter column MyTable.hll_x policy encoding type='bigobject'
现在将值引入到上面的同一个表中:
.set-or-append MyTable <| range x from 1 to 1000000 step 1
| summarize hll(x,4)
成功引入第二个值:
MyTable
| project isempty(hll_x)
输出
Column1 |
---|
1 |
0 |
示例:使用装箱时间戳计数
这里有一个表 (PageViewsHllTDigest
),其中包含每小时查看的页面的 hll
值。 你希望将这些值分段成 12h
。 使用 hll_merge()
聚合函数合并 hll
值,将时间戳分段成 12h
。 使用函数 dcount_hll
返回最终的 dcount
值:
PageViewsHllTDigest
| summarize merged_hll = hll_merge(hllPage) by bin(Timestamp, 12h)
| project Timestamp , dcount_hll(merged_hll)
输出
Timestamp | dcount_hll_merged_hll |
---|---|
2016-05-01 12:00:00.0000000 | 20056275 |
2016-05-02 00:00:00.0000000 | 38797623 |
2016-05-02 12:00:00.0000000 | 39316056 |
2016-05-03 00:00:00.0000000 | 13685621 |
将时间戳分段成 1d
:
PageViewsHllTDigest
| summarize merged_hll = hll_merge(hllPage) by bin(Timestamp, 1d)
| project Timestamp , dcount_hll(merged_hll)
输出
Timestamp | dcount_hll_merged_hll |
---|---|
2016-05-01 00:00:00.0000000 | 20056275 |
2016-05-02 00:00:00.0000000 | 64135183 |
2016-05-03 00:00:00.0000000 | 13685621 |
可以对 tdigest
的值执行同一查询,这些值表示每小时的 BytesDelivered
:
PageViewsHllTDigest
| summarize merged_tdigests = merge_tdigest(tdigestBytesDel) by bin(Timestamp, 12h)
| project Timestamp , percentile_tdigest(merged_tdigests, 95, typeof(long))
输出
Timestamp | percentile_tdigest_merged_tdigests |
---|---|
2016-05-01 12:00:00.0000000 | 170200 |
2016-05-02 00:00:00.0000000 | 152975 |
2016-05-02 12:00:00.0000000 | 181315 |
2016-05-03 00:00:00.0000000 | 146817 |
示例:临时表
如果数据集太大,则会达到 Kusto 限制。在这种情况下,你需要对数据集运行定期查询,但需要运行常规查询来针对大型数据集计算 percentile()
或 dcount()
。
若要解决此问题,可以将新增加的数据作为 hll
或 tdigest
值添加到一个临时表中,当所需的运算为 dcount
或 tdigest()
时,请使用 hll()
;当所需的运算为 percentile 时,请使用 set/append
或 update policy
。 在这种情况下,dcount
或 tdigest
的中间结果会保存到另一个数据集中,该数据集应小于大的目标数据集。
若要解决此问题,可以将新增加的数据作为 hll
或 tdigest
值添加到一个临时表中,当所需的运算为 dcount
时,请使用 hll()
。 在这种情况下,dcount
的中间结果保存到另一个数据集中,该数据集应小于大的目标数据集。
当需要获取这些值的最终结果时,可以让查询使用 hll
/tdigest
合并:hll-merge()
/tdigest_merge()
。 然后,在获取合并值之后,可以对这些合并值调用 percentile_tdigest()
/ dcount_hll()
,以获取 dcount
或 percentile 运算的最终结果。
假设有一个表 PageViews,每天向其中引入数据。在 date = datetime(2016-05-01 18:00:00.0000000) 过后,你每天都要计算每分钟查看的页面的非重复计数。
运行以下查询:
PageViews
| where Timestamp > datetime(2016-05-01 18:00:00.0000000)
| summarize percentile(BytesDelivered, 90), dcount(Page,2) by bin(Timestamp, 1d)
输出
Timestamp | percentile_BytesDelivered_90 | dcount_Page |
---|---|---|
2016-05-01 00:00:00.0000000 | 83634 | 20056275 |
2016-05-02 00:00:00.0000000 | 82770 | 64135183 |
2016-05-03 00:00:00.0000000 | 72920 | 13685621 |
你每次运行此查询时(例如,你希望一天运行多次此查询),此查询都会聚合所有值。
如果你使用更新策略或 set/append 命令将 hll
和 tdigest
值(这些值是 dcount
和 percentile 运算的中间结果)保存到临时表 PageViewsHllTDigest
中,则可以仅合并这些值,然后通过以下查询使用 dcount_hll
/percentile_tdigest
:
PageViewsHllTDigest
| summarize percentile_tdigest(merge_tdigest(tdigestBytesDel), 90), dcount_hll(hll_merge(hllPage)) by bin(Timestamp, 1d)
输出
Timestamp | percentile_tdigest_merge_tdigests_tdigestBytesDel |
dcount_hll_hll_merge_hllPage |
---|---|---|
2016-05-01 00:00:00.0000000 | 84224 | 20056275 |
2016-05-02 00:00:00.0000000 | 83486 | 64135183 |
2016-05-03 00:00:00.0000000 | 72247 | 13685621 |
此查询的性能应当更高,因为它针对更小的表运行。 在此示例中,第一个查询针对大约 215M 记录运行,而第二个查询仅针对 32 条记录运行:
示例:中间结果
保留查询。 假设你有一个表,其中汇总了用户查看每个维基百科页面的时间(样本大小为 10M),并且对于每个 date1 date2,你想要查明在 date1 和 date2 这两天查看的页面数相对于在 date1 (date1 < date2) 这天查看的页面数的比率。
琐细的方法是使用 join 和 summarize 运算符:
// Get the total pages viewed each day
let totalPagesPerDay = PageViewsSample
| summarize by Page, Day = startofday(Timestamp)
| summarize count() by Day;
// Join the table to itself to get a grid where
// each row shows foreach page1, in which two dates
// it was viewed.
// Then count the pages between each two dates to
// get how many pages were viewed between date1 and date2.
PageViewsSample
| summarize by Page, Day1 = startofday(Timestamp)
| join kind = inner
(
PageViewsSample
| summarize by Page, Day2 = startofday(Timestamp)
)
on Page
| where Day2 > Day1
| summarize count() by Day1, Day2
| join kind = inner
totalPagesPerDay
on $left.Day1 == $right.Day
| project Day1, Day2, Percentage = count_*100.0/count_1
输出
第 1 天 | 第 2 天 | 百分比 |
---|---|---|
2016-05-01 00:00:00.0000000 | 2016-05-02 00:00:00.0000000 | 34.0645725975255 |
2016-05-01 00:00:00.0000000 | 2016-05-03 00:00:00.0000000 | 16.618368960101 |
2016-05-02 00:00:00.0000000 | 2016-05-03 00:00:00.0000000 | 14.6291376489636 |
以上查询用了大约 18 秒的时间。
使用 hll()
、hll_merge()
和 dcount_hll()
函数时,等效的查询将在大约 1.3 秒后结束,这表明 hll
函数将上述查询的速度提高了大约 13 倍:
let Stats=PageViewsSample | summarize pagehll=hll(Page, 2) by day=startofday(Timestamp); // saving the hll values (intermediate results of the dcount values)
let day0=toscalar(Stats | summarize min(day)); // finding the min date over all dates.
let dayn=toscalar(Stats | summarize max(day)); // finding the max date over all dates.
let daycount=tolong((dayn-day0)/1d); // finding the range between max and min
Stats
| project idx=tolong((day-day0)/1d), day, pagehll
| mv-expand pidx=range(0, daycount) to typeof(long)
// Extend the column to get the dcount value from hll'ed values for each date (same as totalPagesPerDay from the above query)
| extend key1=iff(idx < pidx, idx, pidx), key2=iff(idx < pidx, pidx, idx), pages=dcount_hll(pagehll)
// For each two dates, merge the hll'ed values to get the total dcount over each two dates,
// This helps to get the pages viewed in both date1 and date2 (see the description below about the intersection_size)
| summarize (day1, pages1)=arg_min(day, pages), (day2, pages2)=arg_max(day, pages), union_size=dcount_hll(hll_merge(pagehll)) by key1, key2
| where day2 > day1
// To get pages viewed in date1 and also date2, look at the merged dcount of date1 and date2, subtract it from pages of date1 + pages on date2.
| project pages1, day1,day2, intersection_size=(pages1 + pages2 - union_size)
| project day1, day2, Percentage = intersection_size*100.0 / pages1
输出
day1 | day2 | 百分比 |
---|---|---|
2016-05-01 00:00:00.0000000 | 2016-05-02 00:00:00.0000000 | 33.2298494510578 |
2016-05-01 00:00:00.0000000 | 2016-05-03 00:00:00.0000000 | 16.9773830213667 |
2016-05-02 00:00:00.0000000 | 2016-05-03 00:00:00.0000000 | 14.5160020350006 |
注意
由于 hll
函数的误差,查询结果不是 100% 准确。 有关误差的详细信息,请参阅 dcount()
。