ClickHouseMiddleTechnical

Что такое приближённые агрегирующие функции в 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 или диагностический запрос для этой СУБД.
  • Называет ограничения, версионные отличия или эксплуатационные последствия.
  • Связывает ответ с проектированием приложения, производительностью, надежностью или безопасностью.

Sources

Related topics