Цена читающего маршрута: чекпойнт WAL по таймеру и условный запрос
- рядом с воркером свёртки живёт горутина, раз в минуту разбирающая журнал пассивным чекпойнтом; «журнал не разбирается» видно строкой владельцу, а не только по `df`. Признак — пара чисел, а не флаг занятости: тот молчит под удерживаемым читателем (`busy=0` при 6256 страницах и пяти перенесённых), а при занятой блокировке отдаёт `-1` вместо ответа, и `-1 >= -1` читалось бы как «разобрано целиком» - каталог отвечает `304` на `If-None-Match`, не открывая снимок витрины. Метка собрана из всего, от чего зависит ответ: версии витрины (`data_version` с закреплённого соединения плюс поколение — значение локально для соединения и не переживает переоткрытия), горизонта измерения и области действия ресурса. Версия снимается до и после сборки: снятая после пометила бы устаревший снимок свежим номером - предел и дедлайн ответа отложены в задачу Read API точек вместе с измеренной ценой первого запроса; попутно починен флаки-тест чужой задачи, искавший значение точки в сыром буфере записи лога
This commit is contained in:
@@ -524,3 +524,141 @@ Read API MUST опираться на фактические объекты за
|
||||
- **WHEN** доставка учтена
|
||||
- **THEN** в сохранённых заголовках вместо значения стоит пометка о сокрытии
|
||||
|
||||
### 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** предупреждение владельцу не пишется, потому что измерения не было
|
||||
|
||||
|
||||
Reference in New Issue
Block a user