- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
объявляет слой, измеренный род, его применимость к отданному ряду и границу
окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
записи: дословность содержимого точки иначе не удерживается, а оборванное
тело уходило под видом успешного `200`
6.5 KiB
Слой ответа выбирается по охвату точек внутри периода
- Дата: 2026-08-04
- Источник: openspec/changes/archive/2026-08-04-tochki-metriki-za-period/design.md
Решение
Слой, из которого собирается ряд, выбирается так:
Охват слоя — длина пересечения отрезка
[первая метка слоя, последняя метка слоя]с запрошенным периодом. Слой с пустым пересечением выбывает. Среди оставшихся берётся слой с наибольшим охватом, при равенстве — самый мелкий (sample→raw→minute→hour→day).
Это пересмотр прежнего правила, записанного в docs/architecture.md: «самый
мелкий слой, покрывающий весь запрошенный диапазон».
Почему
Прежняя формулировка неопределена на входе, который тот же документ объявляет законным. Границы слоя — границы данных, а не обещание покрытия: внутри диапазона законно есть дыры, и слоя, покрывающего диапазон целиком, может не существовать вовсе. Правило, не определённое на законном входе, реализатор доопределяет молча.
Мера — охват, а не число точек. body_mass в нижнем слое за три плотных дня
даёт больше объектов, чем часовой слой за год с еженедельным взвешиванием: по
числу точек «вес за год» вернул бы три дня, не сказав об этом ни словом.
Охват меряется метками точек, а не часами объектов, и это не придирка.
Объекты адресуются часом, поэтому выборка обязана быть шире запроса (точка
10:59 живёт в объекте 10:00), а ряд отбирается точной меткой. Путь построен
проходом ревью на предложении:
from = 10:30,to = 10:45. Слойhourимеет объект10:00с единственной точкой в10:00, слойminute— объект10:00с точками10:31…10:44. По часам объектов охваты равны, побеждаетhour— и после точного отбора ответ уходит пустым при непустых минутных данных.
Класс общий: предикат выбора источника и предикат отбора данных обязаны использовать одну границу.
Цена меры измерена, и она не нулевая. Индекс bucket_catalog идёт
(metric, layer, hour_utc, …), и без предиката по слою SQLite не сужает поиск по
hour_utc — он просматривает все строки метрики за всю историю, а план при этом
выглядит успешным (SEARCH … USING COVERING INDEX). Замер эксплуатационного
прохода на копии схемы: 2.06 мс при 52 560 строках метрики против 13.9 мс при
350 400, то есть цена росла бы вместе с возрастом сервиса при любой ширине
запроса. С явным перечислением слоёв — 0.026 мс. Отсюда же следствие: словарь
слоёв один (hae.Layers), из него выводятся и порядок, и перечень выборки, и
проверка параметра запроса, и текст отказа клиенту.
Последствия
+Правило определено на любом входе, включая тот, где ни один слой периода не покрывает.+Смены слоя внутри одного ответа не бывает: ряд, склеенный из двух слоёв, поехал бы незаметно для клиента, а вместе с ним поехала бы и будущая свёртка.−Правило максимизирует размер ответа: при равном охвате берётся самый мелкий слой, то есть «пульс за неделю» без параметров это сотни тысяч точек. Предел ответа — соседняя задача; цена измерена и названа (см. ниже).−Краевой объект, у которого есть точки и до, и после периода, но ни одной внутри, свой слой из выбора не выведет. Остаток узкий и честный: слой в ответе назван, аpointsпуст.
Открыто, решает владелец
Инвертировать ли умолчание при равном охвате. Сегодня берётся самый мелкий —
это правило architecture.md до пересмотра, и оно максимизирует размер ответа.
Измерено на этом маршруте: неделя нижнего слоя — 604 800 точек, 1.75 с и
1375 МиБ суммарных выделений на доменном слое; под HTTP вместе с сериализацией —
2.89 с, 279.7 МиБ тела, 1335 МиБ живой кучи; четыре одновременных запроса дают
4322 МиБ.
- (а) оставить как есть, предел вводит
read-api-response-limit; - (б) при равном охвате брать самый крупный слой, мелкий — только по
явному
layer.
Рекомендация: (а). Решение сцеплено с формой предела, и принимать его мимоходом на первой ручке — то же, от чего отказались на каталоге.