# Слой ответа выбирается по охвату точек внутри периода - **Дата:** 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`. **Рекомендация:** (а). Решение сцеплено с формой предела, и принимать его мимоходом на первой ручке — то же, от чего отказались на каталоге.