次の方法で共有


hll() と tdigest() の使用

適用対象: ✅Microsoft FabricAzure データ エクスプローラーAzure MonitorMicrosoft Sentinel

過去 7 日間の個別のユーザーのカウントを毎日計算する必要があるとします。 過去 7 日間にスパン フィルターを適用して summarize dcount(user) を 1 日 1 回実行することができます。 この方法は非効率です。計算を実行するたびに、前の計算と 6 日間の重複が発生するからです。 毎日の集計を計算してから、その集計を結合することもできます。 この方法では、最後の 6 つの結果を "記憶" する必要がありますが、はるかに効率は良くなります。

前述のパーティション分割クエリは、count()sum() などの単純な集計では簡単です。 また、dcount()percentiles() などの複雑な集計にも役立つ可能性があります。 この記事では、Kusto がこのような計算をサポートする方法について説明します。

次の例では、hll/tdigest を使用する方法を示し、これらのコマンドの使用が一部のシナリオで高い性能を示すことを実証します。

重要

hllhll_ifhll_mergetdigest、およびtdigest_mergeの結果は、他の関数 (dcount_hllpercentile_tdigestpercentiles_array_tdigest、およびpercentrank_tdigest) で処理できるdynamic型のオブジェクトです。 このオブジェクトのエンコードは、時間の経過と伴って変化する可能性があります (ソフトウェアのアップグレードなど)。ただし、このような変更は下位互換性のある方法で行われるので、そのような値を永続的に格納し、クエリで確実に参照することができます。

Note

場合によっては、hll または tdigest 集計関数によって生成される動的オブジェクトが、大きく、エンコード ポリシー内の既定の MaxValueSize プロパティを超える可能性があります。 その場合は、オブジェクトが null として取り込まれます。 たとえば、精度レベル 4 で hll 関数の出力を保持すると、hll オブジェクトのサイズが既定の MaxValueSize (1 MB) を超えます。 この問題を回避するには、次の例に示すように、列のエンコード ポリシーを変更します。

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)

出力

列 1
1

null の取り込みを回避するには、次のように、MaxValueSize を 2 MB にオーバーライドする特殊なエンコード ポリシーの種類 bigobject を使用します。

.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)

2 つ目の値を正常に取り込む:

MyTable
| project isempty(hll_x)

出力

列 1
1
0

例: ビン分割されたタイムスタンプを含むカウント

各時間に表示される Pages のhll値を含むテーブルPageViewsHllTDigestがあります。 これらの値を 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)

出力

タイムスタンプ 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)

出力

タイムスタンプ 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

1 時間ごとに BytesDelivered を表す tdigest の値に対して、同じクエリを実行できます。

PageViewsHllTDigest
| summarize merged_tdigests = merge_tdigest(tdigestBytesDel) by bin(Timestamp, 12h)
| project Timestamp , percentile_tdigest(merged_tdigests, 95, typeof(long))

出力

タイムスタンプ 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() を計算する必要があります。

この問題を解決するために、必要な操作が dcount の場合は hll() を使用し、必要な操作が set/append または update policy を使用したパーセンタイルの場合は tdigest() を使用して、新しく追加されたデータを hll または tdigest の値として一時テーブルに追加できます。 この場合は、dcount または tdigest の中間結果が別のデータセットに保存されます。このデータセットは、ターゲットの大きいデータセットより小さくする必要があります。

この問題を解決するために、必要な操作が dcount の場合に hll() を使用して、新しく追加されたデータを hll または tdigest の値として一時テーブルに追加できます。 この場合は、dcount の中間結果が別のデータセットに保存されます。このデータセットは、ターゲットの大きいデータセットより小さくする必要があります。

これらの値の最終結果を取得する必要がある場合は、クエリで hll/tdigest マージャーの hll-merge()/tdigest_merge() を使用できます。 その後で、マージされた値を取得してから、それらのマージされた値に対して percentile_tdigest() / dcount_hll() を呼び出して、dcount またはパーセンタイルの最終結果を取得できます。

毎日データが取り込まれるテーブル PageViews があり、datetime(2016-05-01 18:00:00.0000000) より後の日付に毎日 1 分ごとに表示されるページの個別のカウントを計算する必要があるとします。

次のクエリを実行します。

PageViews
| where Timestamp > datetime(2016-05-01 18:00:00.0000000)
| summarize percentile(BytesDelivered, 90), dcount(Page,2) by bin(Timestamp, 1d)

出力

タイムスタンプ 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

このクエリは、このクエリを実行するたびにすべての値を集計します (たとえば、1 日に何度も実行する場合)。

更新ポリシーまたは set/append コマンドを使用して、hlltdigest の値 (dcount とパーセンタイルの中間結果) を一時テーブル PageViewsHllTDigest に保存する場合は、値をマージしてから、次のクエリで dcount_hll/percentile_tdigest を使用するだけで済みます。

PageViewsHllTDigest
| summarize  percentile_tdigest(merge_tdigest(tdigestBytesDel), 90), dcount_hll(hll_merge(hllPage)) by bin(Timestamp, 1d)

出力

タイムスタンプ percentile_tdigest_merge_tdigests_tdigestBytesDel dcount_hll_hll_merge_hllPage
2016-05-01 00:00:00.0000000 84,224 20056275
2016-05-02 00:00:00.0000000 83486 64135183
2016-05-03 00:00:00.0000000 72247 13685621

このクエリは、小さめのテーブルに対して実行されるため、効率が良いはずです。 この例では、1 つ目のクエリが約 215M 件のレコードに対して実行され、2 つ目のクエリが 32 件のレコードに対して実行されます。

例: 中間結果

保持クエリ。 Wikipedia の各ページが表示された日時をまとめたテーブル (サンプル サイズは 10M) があり、date1 で表示されたページに対する date1 と date2 の両方でレビューされたページの割合を date1 と date2 ごとに検索する必要があるとします (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

出力

Day1 Day2 パーセント
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 関数が上記のクエリより約 14 倍速くなることが示されます。

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

Note

hll 関数のエラーが原因で、クエリの結果が 100% 正確ではありません。 エラーの詳細については、「dcount()」を参照してください。