SAMPLE BY: приблизительные запросы
Таблица с миллиардами строк. Нужна приблизительная статистика — средний чек, количество уникальных пользователей, распределение событий. Точный ответ требует сканирования всей таблицы. Приблизительный — достаточно прочитать 1-10% данных и экстраполировать.
ClickHouse поддерживает SAMPLE BY — встроенный механизм детерминированной выборки. В отличие от случайной выборки (RAND()), SAMPLE BY всегда возвращает одни и те же строки при одинаковой доле — результаты воспроизводимы.
Объявление SAMPLE BY в DDL
SAMPLE BY определяется при создании таблицы. Три обязательных требования:
- Выражение SAMPLE BY должно входить в ORDER BY — ClickHouse использует физическую сортировку для эффективной выборки
- Выражение должно возвращать unsigned integer — обычно intHash32 или intHash64
- Хеш-функция обеспечивает равномерное распределение — intHash32 превращает любой столбец в равномерно распределённые значения
CREATE TABLE events (
event_date Date,
user_id UInt64,
event_type String,
revenue Float64
) ENGINE = MergeTree()
ORDER BY (intHash32(user_id), event_date)
SAMPLE BY intHash32(user_id);
SAMPLE BY выражение должно быть частью ORDER BY ключа. Если ORDER BY не содержит SAMPLE BY выражение, ClickHouse вернёт ошибку при создании таблицы. Обратите внимание: intHash32(user_id) стоит первым в ORDER BY — это обеспечивает физическую группировку данных по хеш-значениям для эффективной выборки.
Три формы SAMPLE в запросах
SAMPLE fraction — доля от 0 до 1
-- Прочитать примерно 10% данных
SELECT avg(revenue), count()
FROM events
SAMPLE 0.1;
-- 1% данных
SELECT uniq(user_id)
FROM events
SAMPLE 0.01;
SAMPLE 0.1 означает: прочитать ~10% строк таблицы. ClickHouse выбирает строки, где intHash32(user_id) попадает в определённый диапазон значений. Поскольку intHash32 распределяет равномерно, ~10% строк будет выбрано.
SAMPLE N — абсолютное количество строк
-- Прочитать примерно 1 000 000 строк
SELECT avg(revenue)
FROM events
SAMPLE 1000000;
ClickHouse автоматически вычислит необходимую долю исходя из общего количества строк.
SAMPLE 1/N — дробная форма
-- Эквивалент SAMPLE 0.1
SELECT count()
FROM events
SAMPLE 1/10;
Детерминированность
Критическое свойство SAMPLE BY — детерминированность. Один и тот же запрос с одним и тем же SAMPLE всегда возвращает одни и те же строки.
-- Оба запроса вернут идентичные строки
SELECT user_id FROM events SAMPLE 0.1;
SELECT user_id FROM events SAMPLE 0.1;
Это возможно потому, что выборка основана на хеш-значении (intHash32(user_id)), а не на случайном числе. Строки с хеш-значениями в диапазоне [0, MAX_UINT32 * 0.1] всегда одни и те же.
Практическое следствие: можно сравнивать результаты приблизительных запросов во времени. Если SAMPLE 0.1 SELECT count() WHERE status='ERROR' показал 150 вчера и 200 сегодня — это реальный рост ошибок, а не случайное отклонение выборки.
Экстраполяция с _sample_factor
Виртуальный столбец _sample_factor содержит коэффициент выборки (обратную долю). Для SAMPLE 0.01 значение _sample_factor = 100.
-- Приблизительное общее количество событий
SELECT count() * _sample_factor AS estimated_total
FROM events
SAMPLE 0.01;
-- Приблизительная сумма дохода
SELECT sum(revenue) * _sample_factor AS estimated_revenue
FROM events
SAMPLE 0.1;
_sample_factor полезен для агрегатных функций count() и sum(). Для avg() экстраполяция не нужна — среднее по выборке и есть приближение среднего по всей таблице. Для uniq() и quantile() экстраполяция также не нужна.
Когда использовать SAMPLE BY
Хорошие сценарии:
- Дашборды реального времени на таблицах с миллиардами строк — 1-10% выборка даёт достаточную точность за доли секунды
- Exploratory analytics — быстрый анализ паттернов перед написанием точного запроса
- A/B тестирование — SAMPLE BY intHash32(user_id) даёт стабильные когорты пользователей
Плохие сценарии:
- Точные финансовые отчёты — приблизительные значения неприемлемы
- Фильтры с низкой кардинальностью в WHERE — выборка может пропустить все записи определённого значения
- Таблицы менее 100 000 строк — полный скан и так быстр
Полный пример
-- DDL: таблица аналитики с SAMPLE BY
CREATE TABLE analytics (
event_date Date,
user_id UInt64,
action String,
duration_ms UInt32
) ENGINE = MergeTree()
ORDER BY (intHash32(user_id), event_date)
SAMPLE BY intHash32(user_id);
-- Вставка данных
INSERT INTO analytics VALUES
('2024-01-15', 1001, 'page_view', 250),
('2024-01-15', 1002, 'click', 50),
('2024-01-15', 1003, 'page_view', 300);
-- Приблизительное количество уникальных пользователей
-- (10% выборка вместо полного сканирования)
SELECT uniq(user_id) AS approx_users
FROM analytics
SAMPLE 0.1;
-- Экстраполяция общего количества действий
SELECT
action,
count() * _sample_factor AS estimated_count,
avg(duration_ms) AS avg_duration
FROM analytics
SAMPLE 0.1
GROUP BY action;
Ключевые выводы
- SAMPLE BY — встроенный механизм детерминированной выборки. Одна и та же доля всегда возвращает одни и те же строки.
- DDL: SAMPLE BY выражение должно входить в ORDER BY и возвращать unsigned integer. Типичная формула:
intHash32(column). - Три формы:
SAMPLE 0.1(доля),SAMPLE 1000000(абсолютное число строк),SAMPLE 1/10(дробь). _sample_factor— виртуальный столбец для экстраполяции:count() * _sample_factorдаёт приблизительное общее количество.- Используйте для дашбордов и exploratory analytics на больших таблицах. Не используйте для точных финансовых отчётов.