Агрегирующие движки сокращают данные, объединяя строки одинакового сортировочного ключа. Выбор зависит от того, что можно складывать как готовое число, а для чего нужно сохранять промежуточное состояние вычисления.
SummingMergeTree
SummingMergeTree складывает выбранные числовые столбцы при слиянии. Например, дневной счётчик по категории:
CREATE TABLE daily_counts
(
day Date,
category LowCardinality(String),
events UInt64,
bytes UInt64
)
ENGINE = SummingMergeTree((events, bytes))
ORDER BY (day, category);Если два блока дают одному ключу events = 2 и events = 3, их объединённый вклад равен 5. Явный список суммируемых колонок защищает от случайного сложения технического числового атрибута. Значения колонок, которые не входят ни в ключ, ни в суммирование, нельзя использовать как надёжно выбранное последнее состояние.
Фоновое слияние может ещё не пройти или объединить лишь часть parts. Поэтому чтение снова агрегирует данные:
SELECT day, category, sum(events) AS events, sum(bytes) AS bytes
FROM daily_counts
GROUP BY day, category;Суммы и количества аддитивны: отдельные вклады можно сложить. Для минимального или максимального значения нужен соответствующий агрегат, а для среднего и уникальных значений простое сложение неверно.
Почему средние не складываются
В первой порции два измерения: 100 и 300, среднее 200. Во второй одно измерение: 900, среднее 900. Среднее двух готовых средних равно 550, но среднее трёх исходных значений — 1300 / 3, примерно 433,33.
Чтобы объединять порции, нужно хранить сумму и количество либо состояние avg. Аналогично множества пользователей {1, 2} и {2, 3} имеют по два уникальных значения, но в объединении их три. Два готовых unique count не позволяют восстановить пересечение.
AggregatingMergeTree и состояния
Состояние агрегатной функции содержит данные, необходимые для продолжения и объединения вычисления. AggregateFunction(avg, UInt32) обозначает состояние среднего над UInt32. Оно может храниться в таблице с движком AggregatingMergeTree.
Суффикс State создаёт состояние, Merge объединяет состояния и возвращает результат. Механизм можно показать без отдельной таблицы:
SELECT avgMerge(partial) AS mean
FROM
(
SELECT avgState(value) AS partial
FROM
(
SELECT arrayJoin([toUInt32(100), toUInt32(300)]) AS value
)
UNION ALL
SELECT avgState(toUInt32(900)) AS partial
);Результат — примерно 433,33. Обе ветви дают одинаковый тип состояния. Несовместимые типы аргументов нельзя молча смешивать: avgState(UInt32) и avgState(UInt64) имеют разные типы.
AggregatingMergeTree объединяет состояния одинакового ORDER BY при merge, а запрос с GROUP BY и функциями Merge завершает вычисление независимо от фоновых слияний. Сериализованное состояние не следует трактовать как обычное число или вручную разбирать его байты.
Простые агрегаты и цена хранения
SimpleAggregateFunction подходит функциям, чей промежуточный результат можно снова обработать той же функцией: например sum, min или max. Среднее так хранить нельзя. Обычное число, SimpleAggregateFunction и AggregateFunction выражают разные правила объединения.
Состояние уникальных значений или квантилей может быть существенно больше одного счётчика. Некоторые функции приближённые: скорость и компактность получаются ценой погрешности. Для витрины заранее определяют точность, ключ группировки и возможность добавить новый разрез. Если детали отброшены при агрегации, новый разрез может потребовать чтения исходных фактов.