Files
healthlog/docs/adr/ADR-2026-08-04-sloy-vybiraetsya-po-ohvatu-tochek.md
av 29ca8d415c httpapi: точки метрики за период отдаются одним запросом
- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
  объявляет слой, измеренный род, его применимость к отданному ряду и границу
  окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
  точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
  под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
  хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
  записи: дословность содержимого точки иначе не удерживается, а оборванное
  тело уходило под видом успешного `200`
2026-08-04 18:46:45 +03:00

6.5 KiB
Raw Permalink Blame History

Слой ответа выбирается по охвату точек внутри периода

  • Дата: 2026-08-04
  • Источник: openspec/changes/archive/2026-08-04-tochki-metriki-za-period/design.md

Решение

Слой, из которого собирается ряд, выбирается так:

Охват слоя — длина пересечения отрезка [первая метка слоя, последняя метка слоя] с запрошенным периодом. Слой с пустым пересечением выбывает. Среди оставшихся берётся слой с наибольшим охватом, при равенстве — самый мелкий (samplerawminutehourday).

Это пересмотр прежнего правила, записанного в 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.

Рекомендация: (а). Решение сцеплено с формой предела, и принимать его мимоходом на первой ручке — то же, от чего отказались на каталоге.