В прошлой статье мини-цикла о работе с агрегатами я рассказывал, как организовать эффективное многопоточное преобразование потока первичных данных в данные агрегированные. Там мы рассматривали задачу "свертки" продаж в агрегаты вида товар/дата/кол-во. Сегодня мы рассмотрим более сложный вариант, который зачастую начинается со слов "А заказчик захотел…" и приводит нас к иерархичным агрегатам в нескольких одновременных разрезах, которые позволяют нам в СБИС практически мгновенно строить оперативные отчеты в подсистемах организации торговли, бухгалтерского учета и даже управления активными продажами. уметь быстро получить информацию не только по товарам, но и по складам в том числе и сводка-TOP продаж товаров на интервале в том числе с фильтром по складу... или без а еще график динамики продаж за месяц по дням... и за год по месяцам... и за все время по годам ... и с любым из фильтров склад/товар ... и чтобы все быстро работало! Итак, вычленяем ключевое для нас относительно предыдущей задачи: появляется новый разрез агрегации - по складу необходима агрегация "без учета" или "по всем" - например, сводные продажи всех товаров склада или продажи товара по всем складам нужны агрегаты для динамики (дневные/месячные/годовые) в разрезе любого фильтра Добавим в таблицу агрегатов новое поле - тип интервала агрегации (D/M/Y) и новый аналитический разрез - склад. Замечу, что тут для экономии размера данных мы использовали однобайтный спецтип "char". Например, такой тип имеет поле relkind (тип объекта) в системной таблице pg_class. Для аналитики "по всем" используем значение = 0 (не NULL) соответствующего разреза. Это позволит нам всегда передавать значения в один и тот же запрос, без изменения его модели на … IS [NOT] NULL. То есть для запроса "какие товары продавались лучше всего в таком-то месяце" будем использовать запрос вида: А если нам понадобится этот же рейтинг по конкретному складу, то просто передадим сюда же ID склада! Очевидно, для такого запроса подходящим будет индекс (quant, dt, wh, qty DESC). В результате, за единственный Index Scan мы получим сразу все, что хотим, без какой-либо динамической агрегации на моменте получения данных. Давайте теперь сконструируем запрос, который поможет нам нарисовать красивый график по динамике: Индекс под него - (quant, it, wh, dt). Почему на первом месте именно quant? Потому что у него очень маленькая селективность, и индекс будет занимать меньше места. Итак, вернемся к предыдущей статье и проблемам, которые мы успешно решали там - разделению кросс-блокировок между параллельно работающими потоками. В нынешней ситуации, разделив обработку по ключу (it, wh), мы заведомо устраним конфликты между обработчиками. Но возникнет небольшая проблема - давайте посмотрим, как именно эффективнее всего добиться формирования агрегатов: При проходе по курсору над flow-таблицей мы формируем в памяти "дифф" для инкремента записей соответствующих агрегатов по обрабатываемому этим потоком ключу (it, wh) - сразу для каждого из типов интервалов. Вместе с этим мы вставляем во flow новую "первичку" для последующих "надагрегатов", заменяя нулем каждый из вариантов разрезов анализа. Задача: "Вскипятить воду в чайнике" И физик и математик: налить воду в чайник, зажечь плиту, поставить чайник на огонь и подогреть до 100*С. Новая задача: "Вскипятить воду в чайнике. Чайник уже налит, огонь горит" Физик: поставить чайник на огонь и подогреть. Математик: выливаем воду из чайника на плиту - чайник пуст, огонь не горит - задача сведена к предыдущей!   (c) народный анекдот Понятно, что при последующей обработке такого ключа, содержащего хотя бы один ноль, записи "надагрегатов" формировать уже не нужно. Единственная точка, в которой у нас могут пересечься два параллельно работающих потока, это формирование/обновление записи queue, соответствующей этому ключу "надагрегата" - например, (0, 0). К счастью, это достаточно просто обходится вставкой новой записи в queue, если pg_try_advisory_xact_lock(it, wh) для такого ключа вернула нам FALSE. То есть да, в разрезе ключа распределения записи в очереди могут быть неуникальны. Но в этом нет ничего страшного, потому что они всего лишь выполняют функцию сигнализатора "во flow что-то может быть по этому ключу". И если нет - не страшно, при обработке этой записи очереди мы заглянем во flow, ничего не найдем, и спокойно завершим обработку. Итого - мы получили в БД все нужные агрегаты во всех требуемых разрезах, которые помогут нам обеспечить быстрый показ отчета/графика в любой комбинации фильтров: Мини-серия "Агрегаты в БД": зачем, как, а стоит ли? эффективная обработка потока «фактов» многомерные суперагрегаты <-- вы здесь прокси-таблицы Архитектура ИС: PostgreSQL, Node.js и highload