Каталог разрезов и измеренный род агрегации

- род метрики выводится сверкой минутного слоя с часовым: часовое значение
  сходится с суммой минутных — накопительная, со средним — мгновенная, иначе
  `unknown` и свёртка не предлагается вовсе. На живом архиве (123 доставки,
  31 метрика) 7 накопительных, 9 мгновенных, противоречащих часов ноль
- `GET /api/v1/metrics` под токеном чтения отдаёт единицы, слои с границами и
  род вместе с основанием измерения; род нигде не хранится — он функция витрины,
  а витрина функция журнала, устаревать в нём нечему
- миграция 00009: покрывающий индекс, чтобы каталог отвечал по учётным колонкам,
  не разжимая содержимое объектов
This commit is contained in:
av
2026-08-02 19:23:59 +03:00
parent 98e0772ec5
commit 03edf1087d
39 changed files with 4744 additions and 58 deletions
@@ -0,0 +1,26 @@
-- +goose Up
-- Каталог разрезов отвечает по учётным колонкам объекта, а не по его
-- содержимому. Без индекса это невозможно: `bucket` объявлена `WITHOUT ROWID`,
-- то есть строка целиком, вместе со сжатым `payload`, живёт в дереве первичного
-- ключа. Агрегат по всем строкам тащил бы за собой страницы содержимого — при
-- 260 тысячах объектов за год это сотни мегабайт чтения на каждый запрос
-- каталога, притом что сам ответ несёт три десятка строк.
--
-- Индекс ПОКРЫВАЮЩИЙ: в нём есть всё, что спрашивает каталог, поэтому обращения
-- к самой таблице не будет вовсе. Порядок колонок задан двумя запросами:
--
-- 1. разрезы метрики — GROUP BY metric, layer (+ units, чтобы расхождение
-- единиц было видно строкой, а не выбиралось молча);
-- 2. общие часы двух слоёв — обход hour_utc по убыванию внутри (metric,
-- layer), поэтому hour_utc стоит третьим и до колонок значений.
--
-- Первичный ключ (metric, layer, hour_utc) в индекс дописывается самим SQLite:
-- у таблицы `WITHOUT ROWID` строка адресуется им.
--
-- Цена — около 60 байт на объект (≈16 МБ за год) и одна вставка в дерево на
-- запись объекта. Платит её только настоящее изменение: широкий проход, у
-- которого сошёлся хеш содержимого, объект не переписывает вовсе.
CREATE INDEX bucket_catalog ON bucket (metric, layer, hour_utc, first_ts, last_ts, points, units);
-- +goose Down
DROP INDEX bucket_catalog;