deшка
Урок 10.10

SummingMergeTree и AggregatingMergeTree

Частичные суммы, состояния агрегатов и правильное объединение среднего и уникальных значений.

Агрегирующие движки сокращают данные, объединяя строки одинакового сортировочного ключа. Выбор зависит от того, что можно складывать как готовое число, а для чего нужно сохранять промежуточное состояние вычисления.

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 выражают разные правила объединения.

Состояние уникальных значений или квантилей может быть существенно больше одного счётчика. Некоторые функции приближённые: скорость и компактность получаются ценой погрешности. Для витрины заранее определяют точность, ключ группировки и возможность добавить новый разрез. Если детали отброшены при агрегации, новый разрез может потребовать чтения исходных фактов.

Войти, чтобы отмечатьдальше →
10.11 →Загрузка: батчи, форматы и async inserts