Использование hll() и tdigest()
Область применения: ✅Microsoft Fabric✅Azure Data Explorer✅Azure Monitor✅Microsoft Sentinel
Предположим, вы хотите вычислить количество отдельных пользователей каждый день за последние семь дней. Вы можете выполнять один summarize dcount(user)
раз в день с отфильтрованным диапазоном до последних семи дней. Этот метод неэффективн, так как при каждом выполнении вычисления выполняется шесть дней, перекрывается предыдущий расчет. Вы также можете вычислить агрегат для каждого дня, а затем объединить эти агрегаты. Этот метод требует от вас "запомнить" последние шесть результатов, но это гораздо эффективнее.
Запросы секционирования, как описано, просты для простых агрегатов, таких как count()
и sum()
. Это также может быть полезно для сложных агрегатов, таких как dcount()
и percentiles()
. В этой статье объясняется, как Kusto поддерживает такие вычисления.
В следующих примерах показано, как использовать hll
/tdigest
и продемонстрировать, что использование этих команд является высокопроизводительным в некоторых сценариях:
Внимание
Результаты hll
, hll_if
, tdigest
hll_merge
и tdigest_merge
являются объектами типаdynamic
, которые затем могут обрабатываться другими функциями (dcount_hll
, , percentile_tdigest
и percentiles_array_tdigest
percentrank_tdigest
). Кодировка этого объекта может измениться со временем (например, из-за обновления программного обеспечения); однако такие изменения будут выполняться в обратном режиме, поэтому можно постоянно хранить такие значения и ссылаться на них в запросах надежно.
Примечание.
В некоторых случаях динамические объекты, созданные hll
функциями статистической обработки, tdigest
могут быть большими и превышать свойство MaxValueSize по умолчанию в политике кодирования. Если да, объект будет приниматься как null.
Например, при сохранении выходных hll
данных функции с уровнем точности 4 размер объекта превышает значение hll
maxValueSize по умолчанию, равное 1 МБ.
Чтобы избежать этой проблемы, измените политику кодирования столбца, как показано в следующих примерах.
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, используйте специальный тип bigobject
политики кодирования, который переопределяет MaxValueSize
значение 2 МБ, как показано ниже:
.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)
Выходные данные
Столбец1 |
---|
1 |
0 |
Пример. Подсчет с забинированной меткой времени
Существует таблица, PageViewsHllTDigest
содержащая hll
значения страниц, просматриваемых в каждом часе. Вы хотите, чтобы эти значения были вложены 12h
в . Слияние hll
значений с помощью агрегатной hll_merge()
функции с меткой времени, вложенной 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 |
Один и тот же запрос может выполняться по значениям tdigest
, которые представляют BytesDelivered
собой каждый час:
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()
над большими наборами данных.
Чтобы решить эту проблему, новые добавленные данные могут быть добавлены в временную таблицу как hll
или tdigest
значения, используя, hll()
когда требуется операция dcount
или tdigest()
когда требуется операция использует процентиль или update policy
set/append
. В этом случае промежуточные результаты dcount
или tdigest
сохраняются в другом наборе данных, который должен быть меньше целевого большого.
Чтобы решить эту проблему, новые добавленные данные могут быть добавлены в временную таблицу как hll
или tdigest
значения, использующиеся hll()
при выполнении требуемой операции dcount
. В этом случае промежуточные результаты dcount
сохраняются в другом наборе данных, который должен быть меньше целевого большого.
Если необходимо получить окончательные результаты этих значений, запросы могут использовать hll
/tdigest
слияния: hll-merge()
/tdigest_merge()
Затем после получения объединенных значений можно вызвать для этих объединенных значений percentile_tdigest()
/ dcount_hll()
dcount
, чтобы получить окончательный результат или процентиль.
Предположим, что есть таблица PageViews, в которой данные ежедневно приемируются, каждый день, в течение которого требуется вычислить количество страниц, просматриваемых в минуту позже даты = datetime(2016-05-01 18:00:000,000000).
Выполните приведенный ниже запрос:
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 |
Этот запрос объединяет все значения каждый раз при выполнении этого запроса (например, если требуется выполнить его несколько раз в день).
При сохранении hll
и значений (которые являются промежуточными результатами dcount
и процентиль) в временной таблице, PageViewsHllTDigest
используя политику обновления или команды set/append, можно объединить только значения, а затем использовать dcount_hll
/percentile_tdigest
следующий запрос: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 | 84224 | 20056275 |
2016-05-02 00:00:00.0000000 | 83486 | 64135183 |
2016-05-03 00:00:00.0000000 | 72247 | 13685621 |
Этот запрос должен быть более производительным, так как он выполняется по меньшей таблице. В этом примере первый запрос выполняется над записями ~215M, а второй выполняется всего через 32 записи:
Пример: промежуточные результаты
Запрос на хранение. Предположим, что у вас есть таблица, которая содержит сводку по просмотру каждой страницы Википедии (размер выборки составляет 10 млн), и вы хотите найти для каждой даты12 процент страниц, проверяемых как в дате 1, так и в дате 2 относительно страниц, просматриваемых по дате1 (дата12 < ).
Тривиальный способ использует операторы соединения и суммирование:
// 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
функции ускоряют запрос выше примерно в 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 |
Примечание.
Результаты запросов не являются точными из-за ошибки hll
функций. Дополнительные сведения об ошибках см. в статье dcount()
.