- байтовый порядок канонических форм остался тай-брейком только внутри одной доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил меньшее значение, из-за чего step_count терял род и verify:archive был красным - правило перестало быть коммутативным осознанно, поэтому порядок свёртки приведён к журнальному: проход воркера прекращается на отложенной доставке, а свёртка вне порядка журнала пишет WARN - заведены счётчики PointsHeld и PointsErased — удержание полнотой и единственное направление, в котором правило теряет содержание
131 lines
11 KiB
Markdown
131 lines
11 KiB
Markdown
## MODIFIED Requirements
|
|
|
|
### Requirement: Свёртку ведёт один фоновый воркер в порядке журнала
|
|
|
|
Свёртку принятых доставок SHALL вести одна горутина, обрабатывающая доставки в
|
|
порядке журнала — `(received_at, id)`, как он определён capability пересборки.
|
|
Распараллеливать свёртку MUST NOT.
|
|
|
|
Порядок здесь — не удобство отладки, а условие правильности содержимого:
|
|
тай-брейк при равной полноте точек разрешается в пользу пришедшей доставки, то
|
|
есть исход слияния есть функция порядка свёртки. Свёрнутая не в порядке журнала
|
|
доставка возвращает координату к версии, которую источник уже пересчитал, и
|
|
живая витрина расходится с тем, что даёт пересборка.
|
|
|
|
Проход воркера SHALL прекращаться на первой доставке, чей исход свёртки
|
|
**классифицирован как отложенный** (занятость базы, отмена снаружи), а не
|
|
перешагивать её. Перешагнув, проход свернул бы её преемниц раньше неё.
|
|
|
|
Предикат остановки — именно класс исхода, а не статус доставки. Доставка,
|
|
оставшаяся `pending` из-за отказа записи самого исхода, курсор двигает и проход
|
|
не останавливает: иначе проход выбирал бы её бесконечно, свёртка встала бы
|
|
целиком, а приём продолжал бы отвечать `200`.
|
|
|
|
Очередь при этом не встаёт: собственный дедлайн свёртки обстоятельством
|
|
MUST NOT считаться — доставка, не уложившаяся в бюджет, получает `failed` и
|
|
очередь освобождает, а занятость базы блокирует запись всем одинаково. Проход
|
|
возобновляется сигналом приёма или периодическим пробуждением.
|
|
|
|
Стоящая голова очереди молчать MUST NOT: у неё нет верхнего предела ожидания, и
|
|
снаружи она неотличима от здорового потока, потому что приём продолжает отвечать
|
|
`200`. Метка отставания (см. ниже) SHALL писаться и на выходе прохода по
|
|
барьеру, а не только на пустой выборке: занятая голова очереди до пустой
|
|
выборки не пропускает проход НИКОГДА, и метка, привязанная к ней, молчала бы
|
|
ровно в том состоянии, ради которого заведена. Флаг «первый проход завершён»
|
|
при этом взводиться MUST NOT — проход до конца очереди не дошёл.
|
|
|
|
Достижимая гарантия называется точно: в порядке `(received_at, id)`
|
|
сворачиваются все доставки, **видимые воркеру** на момент выборки. Доставка,
|
|
ставшая видимой позже курсора прохода, подбирается следующим проходом;
|
|
абсолютного порядка при конкурентных приёмах система не обещает, потому что
|
|
строка учёта становится видимой только после записи тела (измерено 184 мс на
|
|
62 МиБ).
|
|
|
|
Остаточное окно молчать MUST NOT. Перед свёрткой воркер SHALL спрашивать
|
|
журнал, есть ли доставка **позже** этой по `(received_at, id)`, уже записавшая
|
|
исход разбора в витрину — то есть в статусе `parsed` или `partial`. Есть —
|
|
пишется одна запись `WARN` на доставку, с её идентификатором, ожиданием в
|
|
секундах и без значений точек.
|
|
|
|
Спрашивать журнал система SHALL **до** свёртки, а писать запись — **после** и
|
|
только если свёртка состоялась: после свёртки предикат уже видит саму эту
|
|
доставку разобранной, а отложенная доставка витрину не трогала, и запись о
|
|
свёртке вне порядка утверждала бы событие, которого не было, — да ещё
|
|
повторялась бы каждым проходом, пока голова очереди занята. Это единственное наблюдение, по которому расхождение живой витрины с
|
|
пересборкой вообще обнаружимо до сверки отпечатков; лечится оно
|
|
`healthlog reindex`.
|
|
|
|
Статус `failed` в предикат входить MUST NOT: такая доставка в витрину ничего не
|
|
записала, и перестановка относительно неё содержимого не разводит. А
|
|
`failed` — штатный исход (невыводимый слой), и его учёт превратил бы `WARN` в
|
|
шум, на который перестают смотреть.
|
|
|
|
Пересборка эту проверку выполнять MUST NOT: она идёт в порядке журнала по
|
|
построению, и её тишина здесь содержательна.
|
|
|
|
Проверка эта — **страж окна, а не постоянная часть свёртки**: закрыв порядок на
|
|
самом приёме, её SHALL снять вместе с окном. Сказано здесь потому, что иначе
|
|
страж переживёт стерегомое и станет тем, что следующий читатель удалит без
|
|
объяснения.
|
|
|
|
Последствие предела называется вслух: доставка без плотных метрик, свёрнутая
|
|
раньше своей предшественницы, слоя не выведет и получит `failed` — то есть её
|
|
точки в витрину не попадут до пересборки. Та же перестановка при равной полноте
|
|
точек оставляет в витрине версию не той доставки, что стоит в журнале последней.
|
|
Окно узкое (обе доставки должны приниматься одновременно), и изменение его
|
|
сужает, а не открывает: прежде проход перешагивал отложенную доставку.
|
|
Устранение предела — отдельный вопрос, оно требует удерживать порядок на самом
|
|
приёме.
|
|
|
|
Воркер SHALL продвигаться по неразобранным доставкам строго возрастающим
|
|
курсором в пределах одного прохода. Курсор обязателен для завершимости:
|
|
доставка, у которой не удалось записать даже исход разбора, остаётся `pending`,
|
|
и проход без курсора выбирал бы её бесконечно.
|
|
|
|
Приём SHALL будить воркер после того, как доставка учтена. Потеря сигнала
|
|
отказом быть MUST NOT: доставка от этого не перестаёт числиться `pending`.
|
|
Помимо сигнала воркер SHALL просыпаться периодически — иначе доставка,
|
|
оставшаяся `pending` по причине выше, ждала бы следующей доставки, а ночью
|
|
телефон молчит часами.
|
|
|
|
Отказ отдельного прохода воркер SHALL переживать: отказ выборки пишется `ERROR`
|
|
и прекращает проход, но не цикл. Отмена работы снаружи отказом при этом
|
|
считаться MUST NOT — штатная остановка не должна писать `ERROR`. Воркер, умерший
|
|
от временного отказа базы, остановил бы свёртку до конца жизни процесса, пока
|
|
приём продолжал бы отвечать `200`.
|
|
|
|
#### Scenario: Видимые доставки сворачиваются в порядке журнала
|
|
|
|
- **GIVEN** несколько доставок числятся `pending` до начала прохода
|
|
- **WHEN** воркер делает проход
|
|
- **THEN** он сворачивает их в порядке `(received_at, id)`
|
|
- **AND** доставка без плотных метрик наследует слой предшествующей ей по этому
|
|
порядку доставки той же автоматизации, а не соседа по времени вставки
|
|
|
|
#### Scenario: Отложенная доставка держит очередь
|
|
|
|
- **GIVEN** в очереди несколько доставок, и свёртка первой из них отложена
|
|
занятостью базы
|
|
- **WHEN** воркер делает проход
|
|
- **THEN** её преемницы в этом проходе не сворачиваются
|
|
- **AND** следующий проход снова начинает с отложенной доставки
|
|
|
|
#### Scenario: Свёртка вне порядка журнала не молчит
|
|
|
|
- **GIVEN** доставка стала видимой после того, как её преемница по журналу уже
|
|
вышла из очереди
|
|
- **WHEN** воркер сворачивает её
|
|
- **THEN** система пишет `WARN` с идентификатором доставки и без значений точек
|
|
|
|
#### Scenario: Доставка, не записавшая исход, не зацикливает проход
|
|
|
|
- **WHEN** свёртка доставки не смогла записать исход разбора и оставила её
|
|
`pending`
|
|
- **THEN** проход воркера завершается, а не выбирает её повторно
|
|
|
|
#### Scenario: Доставка без входящего потока всё равно подбирается
|
|
|
|
- **GIVEN** доставка осталась `pending`, и новых доставок не приезжает
|
|
- **WHEN** наступает очередное периодическое пробуждение
|
|
- **THEN** воркер пробует свернуть её снова
|