Поделиться через


Использование hll() и tdigest()

Область применения: ✅Microsoft Fabric✅Azure Data ExplorerAzure MonitorMicrosoft Sentinel

Предположим, вы хотите вычислить количество отдельных пользователей каждый день за последние семь дней. Вы можете выполнять один summarize dcount(user) раз в день с отфильтрованным диапазоном до последних семи дней. Этот метод неэффективн, так как при каждом выполнении вычисления выполняется шесть дней, перекрывается предыдущий расчет. Вы также можете вычислить агрегат для каждого дня, а затем объединить эти агрегаты. Этот метод требует от вас "запомнить" последние шесть результатов, но это гораздо эффективнее.

Запросы секционирования, как описано, просты для простых агрегатов, таких как count() и sum(). Это также может быть полезно для сложных агрегатов, таких как dcount() и percentiles(). В этой статье объясняется, как Kusto поддерживает такие вычисления.

В следующих примерах показано, как использовать hll/tdigest и продемонстрировать, что использование этих команд является высокопроизводительным в некоторых сценариях:

Внимание

Результаты hll, hll_if, tdigesthll_mergeи tdigest_merge являются объектами типаdynamic, которые затем могут обрабатываться другими функциями (dcount_hll, , percentile_tdigestи percentiles_array_tdigestpercentrank_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 policyset/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().