- рядом с воркером свёртки живёт горутина, раз в минуту разбирающая журнал пассивным чекпойнтом; «журнал не разбирается» видно строкой владельцу, а не только по `df`. Признак — пара чисел, а не флаг занятости: тот молчит под удерживаемым читателем (`busy=0` при 6256 страницах и пяти перенесённых), а при занятой блокировке отдаёт `-1` вместо ответа, и `-1 >= -1` читалось бы как «разобрано целиком» - каталог отвечает `304` на `If-None-Match`, не открывая снимок витрины. Метка собрана из всего, от чего зависит ответ: версии витрины (`data_version` с закреплённого соединения плюс поколение — значение локально для соединения и не переживает переоткрытия), горизонта измерения и области действия ресурса. Версия снимается до и после сборки: снятая после пометила бы устаревший снимок свежим номером - предел и дедлайн ответа отложены в задачу Read API точек вместе с измеренной ценой первого запроса; попутно починен флаки-тест чужой задачи, искавший значение точки в сыром буфере записи лога
9.9 KiB
ADDED Requirements
Requirement: Каталог отвечает на условный запрос
Система SHALL выставлять на ответе каталога заголовок ETag и SHALL отвечать
304 Not Modified на запрос с If-None-Match, чья метка совпадает с текущей
версией витрины. При совпадении снимок витрины открываться MUST NOT: смысл
условного запроса в том, что самый частый запрос потребителя — повтор
неизменившегося — не стоит ничего.
Метка MUST строиться из всего, от чего зависит ответ: версии витрины и
горизонта измерения. Горизонт едет вместе с часами, и метка из будущего,
лежащая в витрине, въезжает в окно сама — без единого коммита. Путь построен и
прогнан: та же версия витрины, cumulative против unknown. Значит версии
витрины для метки НЕ ДОСТАТОЧНО, и слабая форма метки этого не лечит: смена
измеренного рода — изменение семантическое, на нём Read API строит арифметику
года.
Горизонт входит в метку огрублённым до часа, и огрубление точное, а не приблизительное: метки объектов лежат ровно на часах, поэтому отбор по горизонту меняется ровно при переходе через час. Цена названа: один полный ответ в час на потребителя при неизменившейся витрине.
Форма метки MUST оставаться слабой (W/"…"): она выведена из состояния, а не из
байтов ответа. На исход 304 это не влияет — If-None-Match сравнивается слабо
в любом случае.
Метка MUST выставляться, только если за всё время сборки ответа в базу никто не коммитил; правило снятия версии принадлежит хранилищу и здесь не повторяется. Ответ без метки — законный исход, а не отказ: клиент просто не сможет спросить условно в следующий раз.
Метка действительна только в пределах одного ресурса, и область действия MUST входить в саму метку. Маршрут, чей ответ есть функция параметров запроса (Read API точек), и транспорт, у которого адреса нет вовсе (MCP), обязаны подмешивать в неё канонизированную форму запроса — иначе «не изменилось» ответит на другой набор данных. Требовать этого прозой недостаточно: правило MUST быть выражено формой вызова, потому что забыть его — единственный путь всей задачи, ведущий к выдаче не тех данных.
Ответ 304 MUST нести ту же метку и MUST NOT нести тела и представленческих
заголовков. Клиент, не приславший If-None-Match, MUST получать ровно то же,
что и до появления условного запроса.
Ответы каталога MUST быть помечены непригодными для разделяемого кеша
(Cache-Control: private, no-cache). До появления валидатора эвристическое
кеширование посредником было маловероятным; с меткой ответ становится штатно
кешируемым, а при выключенной проверке токенов (законная конфигурация) в
запросе нет и Authorization — тогда выгрузку истории здоровья вправе
сохранить любой прокси на пути.
Проверка токена чтения MUST предшествовать условному запросу: 304 без токена
подтверждал бы состояние витрины тому, кому она не открыта.
Разбор условия MUST следовать HTTP и MUST NOT превращать кривой заголовок в отказ:
- звёздочка (
*) совпадает с любой существующей меткой; метки нет — условие не выполнено, и клиент со звёздочкой получает данные, а не вечный304; - неразбираемое значение условия не выполняет и даёт
200, а не400.
Следствие названо вслух: 304 не выполняет измерения и потому не пишет
предупреждений владельцу. Предупреждения каталога (данные из будущего,
противоречащий род агрегации) привязаны к сборке ответа; с условным опросом они
становятся функцией смены версии витрины, а не числа запросов. Состояние при
этом не исчезает: следующая доставка меняет версию, ответ собирается, и
предупреждение пишется — а пока витрина стоит, повторять его на каждый опрос
трёх потребителей значило бы обесценить уровень.
Scenario: Повтор на неизменившейся витрине
- GIVEN клиент получил каталог и запомнил его
ETag - WHEN он повторяет запрос с
If-None-Matchэтой метки, а витрина не менялась - THEN ответ —
304без тела, с той же меткой
Scenario: Витрина изменилась
- GIVEN клиент получил каталог и запомнил его
ETag - WHEN свёртка записала объект и клиент повторяет запрос с прежней меткой
- THEN ответ —
200с полным каталогом и новой меткой
Scenario: Горизонт сдвинулся
- GIVEN витрина не менялась
- WHEN горизонт измерения перешёл через час
- THEN метка отличается от прежней
Scenario: Метка другого ресурса
- GIVEN клиент присылает метку, выданную другим читающим маршрутом
- WHEN совпадает версия витрины
- THEN условие не выполнено, и ответ —
200
Scenario: Клиент не спрашивает условно
- WHEN каталог запрашивается без
If-None-Match - THEN ответ —
200с полным каталогом, меткой и правилом кеширования
Scenario: Две метки на неизменившейся витрине совпадают
- GIVEN витрина не менялась между двумя запросами
- WHEN каталог запрошен дважды
- THEN метки совпадают, и тела ответов совпадают побайтово
Scenario: Звёздочка в условии
- WHEN каталог запрашивается с
If-None-Match: * - THEN ответ —
304с текущей меткой
Scenario: Условие нечитаемо
- WHEN каталог запрашивается с
If-None-Match, который меткой не является - THEN ответ —
200с полным каталогом, а не400и не304
Scenario: Условный запрос без токена чтения
- GIVEN список токенов чтения непуст
- WHEN каталог запрашивается с
If-None-Match, но без токена - THEN ответ —
401, а не304
Scenario: Витрина изменилась во время сборки ответа
- GIVEN между снятием версии до и после сборки в базу был коммит
- WHEN ответ сформирован
- THEN он уходит с полным телом и без заголовка
ETag
Scenario: Версия витрины недоступна
- GIVEN версию витрины прочитать не удалось
- WHEN каталог запрашивается, в том числе с
If-None-Match - THEN ответ —
200с полным каталогом и без метки, а не500и не304
Scenario: Условный ответ не собирает каталог
- GIVEN в витрине лежат данные, помеченные будущим
- WHEN каталог отвечает
304по совпавшей метке - THEN предупреждение владельцу не пишется, потому что измерения не было