Что такое приближённые агрегирующие функции в ClickHouse (uniq, quantile и т.д.)?
uniq, quantile и подобные функции используют вероятностные алгоритмы (HyperLogLog, t-digest, reservoir sampling), жертвуя небольшой погрешностью ради многократного ускорения и экономии памяти.
Приближённые агрегирующие функции в ClickHouse
ClickHouse предоставляет два семейства агрегирующих функций: точные и приближённые. Точные функции (например, count(DISTINCT x) или quantileExact) дают верный результат, но при больших объёмах данных потребляют много памяти и CPU. Приближённые функции обменивают небольшую погрешность на радикальное снижение стоимости запроса.
uniq и семейство uniqXXX
uniq(x) реализует алгоритм HyperLogLog с относительной погрешностью около 2,2 %. Семейство включает несколько вариантов:
uniq— HyperLogLog, ~2 % погрешности, минимальная память.uniqCombined— гибрид: сначала точный hash set, при росте переключается на HyperLogLog; погрешность ~0,5 %.uniqCombined64— то же, но 64-битные хэши, меньше коллизий.uniqExact— точный результат, большая память (не приближённый).uniqHLL12— HyperLogLog с 12 битами, похож на базовыйuniq.uniqTheta— алгоритм Theta sketch из Apache DataSketches, поддерживает set-операции.
quantile и семейство
quantile(0.5)(x) вычисляет медиану приближённо через reservoir sampling (8192 случайных элемента). Погрешность зависит от распределения данных. Варианты:
quantileExact— точно, полная сортировка.quantileTDigest— алгоритм t-digest, точнее на хвостах (p99, p999).quantileTiming— оптимизирован для значений в миллисекундах (0–30 000 мс).quantileBFloat16— компактный, использует BFloat16 числа.quantiles(0.5, 0.9, 0.99)(x)— вычисляет сразу несколько квантилей за один проход.
topK
topK(5)(x) возвращает 5 наиболее частых значений через алгоритм Space-Saving, не строя полную частотную таблицу.
Пример запроса
SELECT
toStartOfHour(event_time) AS hour,
uniq(user_id) AS approx_dau,
uniqExact(user_id) AS exact_dau,
quantileTDigest(0.95)(response_ms) AS p95_ms,
quantiles(0.5, 0.9, 0.99)(response_ms) AS percentiles,
topK(10)(page_path) AS top_pages
FROM events
WHERE event_time >= now() - INTERVAL 1 DAY
GROUP BY hour
ORDER BY hour;
Комбинация с -State/-Merge для инкрементального обновления
Приближённые функции поддерживают суффиксы -State и -Merge, что позволяет хранить промежуточные состояния в materialized view и дообновлять их без пересчёта:
-- Создание materialized view с промежуточным состоянием
CREATE MATERIALIZED VIEW mv_daily_uniq
ENGINE = AggregatingMergeTree()
ORDER BY (date, country)
AS
SELECT
toDate(event_time) AS date,
country,
uniqState(user_id) AS user_id_state,
quantileTDigestState(0.95)(ms) AS p95_state
FROM events
GROUP BY date, country;
-- Чтение с финализацией
SELECT
date,
country,
uniqMerge(user_id_state) AS dau,
quantileTDigestMerge(0.95)(p95_state) AS p95
FROM mv_daily_uniq
GROUP BY date, country;
Подводные камни
- uniq vs uniqExact: при кардинальности до ~1 000 элементов разница в скорости незначительна —
uniqExactможет быть уместнее, чтобы не вводить погрешность. - quantile без t-digest на хвостах: базовый
quantile(reservoir sampling) заметно врёт на p99+ при скошенных распределениях; используйтеquantileTDigest. - -State тип не читается напрямую: колонки типа
AggregateFunction(uniq, UInt64)нельзяSELECT-нуть как число безuniqMerge. - topK не гарантирует порядок по точному счёту: алгоритм Space-Saving может пропустить элемент, если буфер мал; увеличьте параметр (например,
topK(20)(x)) для повышения точности. - quantileTiming ограничен диапазоном: работает корректно только для значений 0–30 000 мс; за пределами этого диапазона результат некорректен.
- uniqTheta и set-операции: объединение Theta sketches через
uniqThetaMerge+uniqThetaIntersectкорректно только для sketches с одинаковым параметром точности. - Потеря точности при малой кардинальности: HyperLogLog возвращает не 0, а маленькое ненулевое значение для пустой выборки — фильтруйте явно.
- Несовместимость версий -State: бинарный формат промежуточного состояния может измениться между мажорными версиями ClickHouse — не полагайтесь на его переносимость между кластерами разных версий.
Common mistakes
- Объяснять приближённые агрегаты как OLTP-механику row-store базы вместо аналитической колоночной модели ClickHouse.
- Путать primary key ClickHouse с уникальным constraint из PostgreSQL или MySQL.
- Игнорировать parts, merges, ORDER BY, sparse index и стоимость маленьких вставок.
- Предлагать синтаксис или транзакционное поведение, которого в ClickHouse нет.
What the interviewer is testing
- Кандидат объясняет приближённые агрегаты через реальный механизм ClickHouse, а не общими словами.
- Приводит корректный SQL или диагностический запрос для этой СУБД.
- Называет ограничения, версионные отличия или эксплуатационные последствия.
- Связывает ответ с проектированием приложения, производительностью, надежностью или безопасностью.