Приближенные функции: uniq и quantile семейства
ClickHouse предоставляет два семейства приближенных функций: uniq (оценка кардинальности — сколько уникальных значений) и quantile (оценка перцентилей — какое значение на позиции N%). Приближенные функции жертвуют абсолютной точностью ради радикального снижения потребления памяти и времени вычисления. На миллиардах строк разница между точным и приближенным результатом — секунды vs минуты, мегабайты vs гигабайты.
Семейство uniq: оценка кардинальности
Сравнительная таблица
| Функция | Алгоритм | Погрешность | Память | Когда использовать |
|---|---|---|---|---|
| uniq | Адаптивный (HLL) | ~1-2% | Малая | Дефолтный выбор для аналитических дашбордов |
| uniqCombined | Array+Hash+HLL12 | ~1% | Средняя | Нужен -State/-Merge для MV |
| uniqCombined64 | Array+Hash+HLL12 (64-bit) | ~1% | Средняя | Кардинальность более 100M |
| uniqHLL12 | HyperLogLog 12-bit | ~1.5% | 2.5 КБ | Предсказуемый размер состояния |
| uniqExact | Полный hash set | 0% | Неограниченная | Точный подсчёт обязателен |
| uniqTheta | Theta Sketch | ~1% | Малая | Set-операции (intersect/union) |
Примеры использования
-- Дефолтный выбор: приближенный подсчёт уникальных пользователей
SELECT
toDate(event_time) AS day,
uniq(user_id) AS unique_users
FROM events
GROUP BY day
ORDER BY day;
-- Быстро, ~1-2% погрешность, минимум памяти
-- Точный подсчёт: когда бизнес требует абсолютной точности
-- (AB-тестирование, биллинг, compliance-отчёты)
SELECT
experiment_id,
variant,
uniqExact(user_id) AS exact_users
FROM ab_test_events
GROUP BY experiment_id, variant;
-- Медленнее, RAM пропорциональна кардинальности
-- Set-операции: пересечение сегментов через uniqTheta
SELECT
uniqTheta(user_id) AS segment_a_users
FROM events WHERE segment = 'premium';
-- Пересечение: пользователи И premium, И active
SELECT uniqThetaIntersect(sketch_a, sketch_b)
FROM (
SELECT
uniqThetaState(user_id) AS sketch_a
FROM events WHERE segment = 'premium'
) a, (
SELECT
uniqThetaState(user_id) AS sketch_b
FROM events WHERE segment = 'active'
) b;
Семейство quantile: оценка перцентилей
Перцентили — ключевой инструмент для анализа распределений: P50 (медиана), P95 (95-й перцентиль), P99 (99-й перцентиль).
Сравнительная таблица
| Функция | Алгоритм | Скорость | Точность | Ограничения |
|---|---|---|---|---|
| quantile | Reservoir sampling | Быстрая | Приближенная | Недетерминированная |
| quantileTDigest | T-Digest | Средняя | Хорошая (~1%) | — |
| quantileExact | Полная сортировка | Медленная | Точная | RAM = все значения |
| quantileTiming | Фиксированные бакеты | Очень быстрая | Хорошая | Максимум 30 000 (30 секунд) |
| quantileDeterministic | Детерминированный sampling | Быстрая | Приближенная | Результат повторяемый |
| quantileBFloat16 | BFloat16 квантизация | Очень быстрая | Низкая | Для быстрой оценки |
quantile — дефолтный выбор
-- P50 (медиана), P95, P99 для латентности запросов
SELECT
quantile(0.5)(query_duration_ms) AS p50,
quantile(0.95)(query_duration_ms) AS p95,
quantile(0.99)(query_duration_ms) AS p99
FROM system.query_log
WHERE type = 'QueryFinish';
Результат недетерминированный — при повторном запросе может незначительно отличаться (reservoir sampling использует случайную выборку).
quantileTDigest — баланс скорости и точности
-- P99 латентности API по эндпоинтам
SELECT
endpoint,
quantileTDigest(0.99)(latency_ms) AS p99_latency
FROM api_logs
GROUP BY endpoint
ORDER BY p99_latency DESC
LIMIT 10;
T-Digest обеспечивает ~1% относительную погрешность для крайних перцентилей (P95, P99) и лучшую точность для медианы. Состояние фиксированного размера — не зависит от количества данных.
quantileTiming — для латентностей
-- Очень быстрый P99 для сетевых латентностей
SELECT
service,
quantileTiming(0.99)(response_time_ms) AS p99
FROM requests
GROUP BY service;
quantileTiming принимает значения от 0 до 30 000 (интерпретируемые как миллисекунды, максимум 30 секунд). Значения больше 30 000 приравниваются к 30 000. Если ваши данные могут превышать 30 секунд, используйте quantileTDigest.
quantileExact — абсолютная точность
-- Точная медиана зарплат (финансовые отчёты)
SELECT
department,
quantileExact(0.5)(salary) AS median_salary
FROM employees
GROUP BY department;
-- Хранит все значения в памяти, затем сортирует
Использовать только когда бизнес-требования запрещают приближение (финансы, compliance). На миллиардах строк потребление памяти может быть значительным.
Фреймворк выбора функции
Выбор uniq-функции
- Нужна точность 0%? ->
uniqExact(проверьте RAM-бюджет) - Нужны set-операции (intersect/union)? ->
uniqTheta - Кардинальность более 100M? ->
uniqCombined64 - Нужен -State/-Merge для MV? ->
uniqCombined - Нужен фиксированный размер состояния? ->
uniqHLL12 - Все остальные случаи ->
uniq(дефолт)
Выбор quantile-функции
- Нужна точность 100%? ->
quantileExact - Данные — латентности до 30 секунд? ->
quantileTiming(самая быстрая) - Нужна точность на хвостах (P95/P99)? ->
quantileTDigest - Нужен детерминированный результат? ->
quantileDeterministic - Все остальные случаи ->
quantile(дефолт)
Множественные перцентили за один проход
ClickHouse поддерживает вычисление нескольких перцентилей одним вызовом функции:
-- Вместо 3 отдельных quantile():
SELECT
quantiles(0.5, 0.95, 0.99)(latency_ms) AS percentiles
FROM requests;
-- Возвращает Array: [p50, p95, p99]
-- Аналогично для T-Digest:
SELECT
quantilesTDigest(0.5, 0.95, 0.99)(latency_ms) AS percentiles
FROM requests;
Это эффективнее, чем три отдельных вызова — данные проходятся один раз.
Комбинаторы с приближенными функциями
Все uniq- и quantile-функции поддерживают комбинаторы из урока 01:
-- uniqIf: подсчёт уникальных только среди платящих
SELECT uniqIf(user_id, plan = 'paid') AS paying_users
FROM users;
-- quantileTDigestIf: P99 только для ошибок
SELECT quantileTDigestIf(0.99)(latency_ms, status_code >= 500) AS error_p99
FROM api_logs;
-- uniqState/uniqMerge: для MV + AggregatingMergeTree
-- (подробнее в уроке 01 о комбинаторах)
Ключевые выводы
- uniq — дефолтный выбор для подсчёта уникальных значений (~1-2% погрешность, минимум памяти).
uniqExact— только когда бизнес требует точности. - uniqTheta — единственный вариант, поддерживающий set-операции (intersect/union) на sketch-уровне.
- quantileTDigest — лучший баланс точности и скорости для P95/P99.
quantileTiming— самая быстрая, но ограничена 30 секундами. - quantiles()/quantilesTDigest() — множественные перцентили за один проход данных.
- Фреймворк выбора: точность -> ограничения -> память -> скорость. Начинайте с приближенной функции, переходите к точной только по требованию бизнеса.