Initialize
Continous integration / Tests (push) Canceled after 0s
Testing clients / Build beanstalkd (push) Canceled after 0s
Continous integration / Docker build (push) Canceled after 0s
Testing clients / Test Pheanstalk PHP (push) Canceled after 0s
Testing clients / Test Greenstalk Python (3.9) (push) Canceled after 0s
Testing clients / Test Pystalk Python (3.10) (push) Canceled after 0s

This commit is contained in:
2026-10-15 10:52:58 +05:00
commit b9bcb01322
73 changed files with 12864 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
# Многопроцессный Beanstalkd
Статус: постановка на реализацию, код не реализован.
Дата фиксации: 2026-10-15.
База: исходники Beanstalkd 1.13 в этом каталоге.
## Цель
Позволить одному логическому серверу использовать несколько CPU-ядер для обработки независимых tubes, сохранив модель заданий Beanstalkd и динамический жизненный цикл очередей.
Принятое решение: процессы-владельцы групп tubes. У каждого владельца собственный event loop, задания, очереди, таймеры и WAL; сетевые frontend-процессы направляют операции владельцам через IPC. Supervisor обеспечивает эксклюзивность owner slots и управление жизненным циклом процессов.
```text
+-- owner slot 0: tubes, jobs, WAL
clients --> frontend(s) --IPC--+-- owner slot 1: tubes, jobs, WAL
+-- owner slot N: tubes, jobs, WAL
supervisor: topology, process generations, startup, shutdown, restart
```
## Границы Первого Этапа
- Один хост, статическая карта распределения, отдельный WAL каждого владельца.
- Каждый `reserve` / `reserve-with-timeout` работает с одной наблюдаемой tube. Настройка `watch target`, `ignore default` допускается.
- Связь tube с владельцем определяется по имени через фиксированные логические разделы. Постоянного каталога дескрипторов всех tubes нет.
- Consumer может держать несколько заданий. Его `use`, текущий watch list и ранее полученные резервации не обязаны относиться к одной tube или одному владельцу.
- Политика перехода от существующего однопроцессного сервера определяется в эпике 01. Рекомендуется opt-in с сохранением legacy, но обязательная поддержка двух режимов пока не утверждена.
- Полный end-to-end путь команд, восстановление, диагностика и проверки отказов входят в результат, а не откладываются после демонстрации `put`/`reserve`.
## Вне Объёма
- Межшардовый выбор задания из нескольких наблюдаемых tubes.
- Распараллеливание одной tube, глобальный FIFO между tubes.
- Динамическая миграция, автоматическая балансировка и изменение карты при работе сервера.
- Кластер между хостами, репликация и автоматическое назначение данных упавшего владельца другому slot.
- Shared-memory allocator, zero-copy и io_uring как предварительное условие реализации.
- Exactly-once выполнение пользовательского кода.
## Основные Инварианты
1. Имя tube всегда имеет одного владельца в пределах версии топологии, даже когда объект tube не существует.
2. Объект создаётся только владельцем и только по операции, которая предполагает создание по протоколу.
3. Два процесса не изменяют данные одного owner slot одновременно.
4. Переходы состояний job последовательны у владельца; права на reserved job принадлежат логической клиентской сессии.
5. Один reserve имеет не более одного результата; job имеет не более одной действующей резервации.
6. Очерёдность команд и ответов одного TCP-соединения сохраняется.
7. Сбой, недоступность и неизвестный результат не маскируются под успешную операцию или отсутствие задания.
8. Накопление IPC-запросов, ответов и тел ограничено памятью; медленный клиент не блокирует чужие очереди неограниченно.
9. Перезапуск с WAL использует ту же топологию и корректное пространство ID; несовпадение конфигурации обнаруживается до записи данных.
## Эпики И Зависимости
| ID | Постановка | Зависимости | Результат |
| --- | --- | --- | --- |
| 01 | [Контракты и baseline](01-contract.md) | Нет | Решения по внешнему поведению, отказам, ID и конфигурации |
| 02 | [Владение tubes](02-ownership.md) | 01 | Детерминированная маршрутизация и локальное создание |
| 03 | [IPC и сессии](03-ipc-sessions.md) | 01; интерфейсы 02 | Межпроцессный транспорт и безопасный жизненный цикл сессий |
| 04 | [Queue engine и протокол](04-queue-engine.md) | 02, 03 | Все операции над очередями через владельцев |
| 05 | [WAL и ID](05-persistence.md) | 01, 02; интеграция 03, 04 | Восстановление, уникальные ID, изоляция журналов |
| 06 | [Supervisor и эксплуатация](06-supervisor.md) | 02, 03; интеграция 04, 05 | Управляемый запуск, отказ и остановка процессов |
| 07 | [Приёмка и производительность](07-validation.md) | Baseline после 01; итог после 02-06 | Воспроизводимые функциональные и нагрузочные доказательства |
Порядок не запрещает совместную разработку согласованных интерфейсов. Минимальный запуск дочернего процесса для тестов можно сделать раньше эпика 06; это не означает готовность supervisor. Тесты пишутся в каждом эпике, а не только в конце.
## Общая Приёмка
- Все обязательные решения эпика 01 закрыты и документированы до реализации зависящего от них контракта.
- Исходные тесты используются как регрессионная база; отклонения допустимы только по согласованной матрице совместимости. Если выбран отдельный legacy-режим, он проходит прежний набор без ограничения multi-watch.
- Успешно проверены минимум два owner slots и несколько frontend-процессов, в том числе гонка создания одной tube через разные frontend.
- Протестированы job lifecycle, права сессий, таймеры, slow clients, аварии и WAL при одинаковых с baseline настройках durability.
- На независимых tubes подтверждён рост пропускной способности и реальная работа нескольких owner CPU; точный порог фиксируется после baseline, до финальных замеров.
- Для одной горячей tube ограничение последовательности явно показано в отчёте, а не скрыто агрегированной нагрузкой.
- Обновлены `README.md`, `doc/protocol.txt`, исходник руководства и эксплуатационные инструкции при реализации. Внешние ограничения и совместимость старых данных описаны явно.
## Основание
Текущий сервер последовательно выполняет `prottick`, `socknext` и callback в `serv.c:srvserve`. Реестры jobs/tubes и генератор ID глобальны. `process_queue` связывает job с `Conn*`, а WAL защищён от второго владельца. Поэтому разделение только listening socket или вызов `fork()` не дают корректного общего сервера.
Критический краткий контекст для последующей работы: [../AGENTS.md](../AGENTS.md).
+52
View File
@@ -0,0 +1,52 @@
# 01. Контракты И Baseline
Статус: не начат. Зависимости: нет.
## Цель
До изменения модели исполнения зафиксировать проверяемые границы совместимости, интерфейсы процессов и условия сравнения производительности. Принятое архитектурное направление не является утверждением всех деталей ниже.
## Задачи
1. Сопоставить `doc/protocol.txt` с `prot.c`, `conn.c`, `job.c`, `tube.c` и WAL. Зафиксировать значимые расхождения кода и документации, не исправляя их попутно без отдельного решения.
2. Описать роли supervisor, frontend, owner; определить число frontend и способ принятия соединений. Не создавать центрального диспетчера всех queue-операций без измерения его предела.
3. Определить opt-in нового режима, конфигурацию топологии и совместимость со старым запуском, TCP/Unix sockets, socket activation, сменой пользователя и сигналами.
4. Снять baseline существующей версии: CPU, throughput, latency, память и WAL I/O. Сохранить воспроизводимые команды и параметры.
5. Подготовить таблицу состояний сессии, ожидания reserve, передачи ответа, аварии frontend и owner. Указать точки изменения состояния и подтверждения операции.
## Решения До Реализации
| Вопрос | Ограничение для решения | Где реализуется |
| --- | --- | --- |
| Переход и legacy | Выбрать сохранение отдельного legacy с opt-in либо согласованный переход на новую архитектуру. Два режима не являются уже утверждённым обязательством; новый режим с одним owner не равен прежнему серверу по возможностям | 04, 05, 06, 07 |
| Multi-watch reserve | Не выбирать первую tube и не выдавать `TIMED_OUT` как будто очередь пуста. Выбрать документированный отказ/закрытие или явно согласованное расширение; не объявлять такой ответ стандартом 1.13 | 04 |
| Наблюдение одной tube, резервации нескольких | Сохранить `reserve-job`, смену watch list и проверки TTR/`DEADLINE_SOON` по всем резервациям сессии; дополнительное ограничение требует отдельного согласования | 03, 04 |
| Неизвестный результат мутации | Не повторять `put` автоматически. Определить закрытие соединения и судьбу сессии при разрыве IPC; request ID не даёт durable exactly-once | 03, 06 |
| Сбой владельца | Выбрать политику затронутых TCP-сессий; безопасная исходная стратегия: закрыть затронутые сессии и очистить их ссылки у остальных владельцев. Не продолжать старые резервации после replay как будто аварии не было | 03, 05, 06 |
| Формат топологии | Фиксировать hash/version/seed при наличии, число разделов, карту, slot IDs, fingerprint. Проверять до открытия WAL для записи | 02, 05 |
| Job ID | Decimal uint64 на wire; уникальность, маршрутизация, порядок внутри tube, переполнение и частичные перезапуски. Определить импорт старых ID | 04, 05 |
| Старый WAL | Выбрать явный offline import либо контролируемый отказ нового режима; исходные данные не изменять автоматически. Если legacy сохраняется, он должен читать прежние данные | 05 |
| Диагностика | Определить смысл `pid`, `id`, `uptime`, `rusage-*`, binlog-полей и счётчиков после частичного перезапуска | 04, 06 |
| Платформы | Определить поддерживаемые платформы новой архитектуры и политику остальных; если legacy сохраняется, его существующие платформы не регрессируют. Linux-first допустим только как явная граница | 03, 06, 07 |
| Численные лимиты | Утвердить лимиты IPC/body buffering, сроки очистки умерших сессий, restart policy и критерии производительности | 03, 06, 07 |
Открытые решения оформлять в соответствующем документе как выбранный контракт с обоснованием. Внешние ограничения сверх одной наблюдаемой tube выносить на согласование, а не выбирать неявно в коде.
## Baseline
- Матрица: одна/много tubes, один/много клиентов, короткие/крупные bodies, ready/delayed workload, WAL off/on, обычный fsync и `-f 0`.
- Генератор нагрузки не должен насыщать CPU раньше сервера. Фиксировать CPU affinity, оборудование, ОС, compiler/CFLAGS, размеры данных и семя распределения.
- Измерять завершённый lifecycle `put -> reserve -> delete` и отдельные операции; точно определить единицу throughput.
- Выполнить профилирование: обход tubes в `next_awaited_job` и `soonest_delayed_job`, системные вызовы, копирование, WAL. Не считать кандидатов доказанными bottlenecks без профиля.
- Не менять алгоритмы исходной версии в рамках снятия baseline.
## Приёмка
- Для каждой строки таблицы есть выбранное поведение либо явно блокируемая ею реализация; блокирующие решения закрыты до начала зависимых изменений.
- Сценарии transient multi-watch, смены tube с живой резервацией и `reserve-job` другой tube присутствуют в тест-плане.
- Есть спецификация запуска, топологии, session identity, command ownership и ошибок.
- Есть сохранённый baseline и заранее заданные критерии сравнения для эпика 07.
## Точки Кода
`main.c`, `util.c:optparse`, `net.c`, `serv.c`, `prot.c:dispatch_cmd`, `conn.c`, `dat.h`, `Makefile`, `testserv.c`.
+56
View File
@@ -0,0 +1,56 @@
# 02. Владение Динамическими Tubes
Статус: не начат. Зависимости: 01.
## Цель
Однозначно назначать процесс всем возможным именам tubes без постоянного реестра самих очередей и без конкурентного claim при каждом создании.
## Контракт
```text
partition = stable_hash(validated_tube_name) % PARTITION_COUNT
owner_slot = partition_map[partition]
```
- Hash работает с точными байтами имени по протоколу: без изменения регистра, locale-зависимой обработки и рандомизации между процессами.
- `PARTITION_COUNT` фиксирован и не равен числу живых процессов по определению. Число разделов, алгоритм и карта выбираются в 01.
- Owner slot не является PID. Перезапуск процесса не меняет принадлежность имён.
- Все процессы подтверждают одинаковые fingerprint/version топологии до обслуживания запросов.
- Данные с несовместимой картой не открываются для записи. Отсутствующая или повреждённая метаинформация не должна вызывать создание новой карты поверх существующих данных.
- Без WAL карта всё равно должна быть единой для живого логического сервера. При полном запуске без сохранённых данных допустима новая явно заданная конфигурация.
## Задачи
1. Реализовать проверку и загрузку карты разделов, стабильный router и тестовые векторы hash.
2. Отделить разрешение владельца от локального поиска/создания объекта. `find` не имеет побочного эффекта `create`.
3. Оставить `tube_find_or_make` или его минимально изменённый эквивалент внутри владельца. Реестр локальных tubes содержит только его объекты.
4. Обрабатывать `use`, `watch`, `ignore` и disconnect через ссылки логических сессий. Подтверждать изменение клиентского состояния после корректного изменения ссылок; предусмотреть rollback частичного attach.
5. Сохранять tube пока есть job любого живого состояния или удерживающая клиентская/системная ссылка. Удалять после освобождения последней ссылки без изменения карты.
6. Определить `default`: одна логическая tube на всём сервере у вычисленного владельца, не отдельная пользовательская очередь в каждом процессе. Сохранить штатную системную ссылку, если она нужна для эквивалентности текущему `prot_init`.
7. При WAL replay создавать tubes у соответствующего владельца; чужое имя в WAL должно выявлять неверную топологию/раскладку данных, а не тихо создавать вторую tube.
## Сценарий Одновременного Создания
Два frontend получают `use emails` и `watch emails`. Оба вычисляют один slot. Его event loop обрабатывает сообщения последовательно: первое создаёт tube, второе находит ту же tube и добавляет свою ссылку. Дополнительная distributed lock или таблица claims на имя не нужна.
Эксклюзивный claim требуется для самого owner slot и обеспечивается supervisor из эпика 06.
## Приёмка
- Hash-векторы одинаковы между процессами и перезапусками; тесты включают допустимые граничные имена, регистр и максимальную длину.
- Параллельные `use`/`watch` одной новой tube через разные frontend создают один объект у одного владельца и корректные счётчики ссылок.
- `stats-tube`/`pause-tube` несуществующего имени возвращают `NOT_FOUND` и не оставляют объект.
- Tube с reserved job не удаляется после ухода остальных клиентов.
- Пустая обычная tube удаляется после последней ссылки; повторное создание попадает в тот же slot.
- `default` представлен один раз в `list-tubes`; подключения не создают локальные копии в остальных owners.
- Авария frontend освобождает его ссылки; запоздалый attach старого поколения не воскрешает сессию.
- Подмена hash, карты или числа разделов при существующих данных приводит к безопасному отказу запуска, а не перемаршрутизации.
## Не Делать
Никакого `hash(name) % live_owners`, переноса очередей при сбое, автоматического распределения новых tubes по текущей нагрузке или долговечного каталога пустых tubes.
## Точки Кода
`tube.c`, `conn.c:on_watch/on_ignore`, `prot.c:prot_init/dispatch_cmd/prot_replay`, `dat.h:Tube`, новый минимальный модуль топологии при необходимости.
+45
View File
@@ -0,0 +1,45 @@
# 03. IPC И Логические Сессии
Статус: не начат. Зависимости: 01, интерфейсы маршрутизации 02.
## Цель
Отделить TCP-соединение от состояния consumer у владельцев. Обеспечить межпроцессный обмен без общих указателей, неограниченных буферов и неявного повторения команд.
## Задачи
1. Реализовать неблокирующий IPC на Unix domain sockets с явным framing; выбрать stream или message-oriented транспорт с учётом крупных bodies и платформ.
2. Определить versioned envelope: topology fingerprint, поколения endpoints, session ID, request ID, opcode, размеры метаданных и body. Не сериализовать C-структуры с padding/указателями как wire contract.
3. Ввести session identity, уникальную в пределах поколения frontend, и идентичность запуска supervisor. Защититься от повторного использования PID/FD и запоздалых сообщений.
4. Хранить у frontend транспортное состояние, `use`, watch list и маршруты резерваций. Хранить у владельцев логические ссылки, ожидания и принадлежащие сессиям jobs.
5. Сохранить порядок команд и ответов на одном клиентском соединении, включая pipelined input. Не считать IPC request ID разрешением менять порядок протокола.
6. Ввести лимиты на размеры frames, входящие запросы, ожидающие ответы и body buffering. Превышение лимитов вызывает backpressure/задокументированный отказ, а не переполнение памяти.
7. Определить отмену reserve и detach сессии при disconnect, half-close, IPC failure и смерти frontend. Повторный detach должен быть безопасен; старый attach не должен отменять cleanup.
8. Определить управление памятью ответа: тело job, передаваемое медленному клиенту, остаётся доступным после timeout/delete исходного job. Копия или явная pin/reference имеют ограниченный срок жизни.
9. Не повторять мутации после неизвестного результата. Если предусмотрены повторы в живом канале, обеспечить дедупликацию в ограниченном окне с определённым моментом освобождения записей; не обещать сохранение dedup после crash без отдельного журнала.
## Межвладельческие Резервации Одной Сессии
Ограничение одной наблюдаемой tube не исключает сохранённые резервации у нескольких владельцев. Это возникает после смены watch list или `reserve-job`.
- Owner остаётся источником истины о job и его TTR.
- При ожидании нового reserve необходимо учесть ближайший deadline всех резерваций сессии, а не только текущей tube.
- Спроектировать обновления после `touch`, release/delete/bury, timeout и новой резервации, чтобы frontend/coordinator не принимал решения по устаревшему deadline.
- Описать упорядочивание событий job available, timeout, `DEADLINE_SOON`, disconnect. Только один из конкурирующих результатов завершает ожидающий запрос.
- Согласовать cleanup остальных владельцев при гибели одного owner и закрытии затронутой клиентской сессии.
Это координация состояния одной сессии, а не межшардовый выбор минимального priority из нескольких watch tubes. Смешивать эти задачи нельзя.
## Приёмка
- Тесты partial read/write, объединённых frames, некорректных длин, oversized body и разрыва IPC посреди сообщения проходят без повреждения состояния.
- Старые поколения, неизвестные session IDs и несоответствующая топология отклоняются до мутации.
- Медленный client/frontend не вызывает неограниченного роста памяти или блокировки всех owners.
- Disconnect во время `put`, ожидания reserve, выдачи job и передачи большого body корректно освобождает ресурсы.
- Авария frontend очищает все его сессии и резервации за установленное в 01 время; явно отличать завершившийся процесс от временно медленного.
- Потеря ответа на `put` не вызывает скрытого второго `put`; неоднозначный результат виден как ошибка соединения по контракту.
- Переключение tube при живой резервации, `reserve-job` другой tube и последующие `touch`/`DEADLINE_SOON` проверены через разные owners.
## Точки Кода
`dat.h:Conn/Socket/Job`, `conn.c`, `prot.c:conn_process_io/conn_timeout/reply_job`, платформенный event loop. Новый IPC-модуль должен иметь самостоятельные тесты framing и жизненного цикла.
+61
View File
@@ -0,0 +1,61 @@
# 04. Queue Engine И Протокол
Статус: не начат. Зависимости: 02, 03; ID/WAL интегрируются с 05.
## Цель
Все операции над заданиями исполняются владельцем соответствующей tube; frontend сохраняет протокольный контракт без общей памяти и центрального глобального планировщика.
## Задачи
1. Отделить parsing/response I/O от мутаций queue engine в `prot.c`. Сохранить существующие heaps, списки и однотредовое исполнение владельца, не переписывая их на shared memory.
2. Удалить зависимость владения job от локального `Conn*` frontend. Использовать логическую сессию и локальное owner-side состояние.
3. Сделать глобальные реестры и счётчики локальными роли процесса. Один процесс-владелец может сохранить process-local globals, если они не смешиваются с frontend; не вводить контейнеры/абстракции без необходимости.
4. Перенести delayed jobs, pause и TTR в timer loop владельца. Согласовать reserve timeout и safety margin с управлением сессиями из 03.
5. Реализовать маршрутизацию всех команд по таблице; подтвердить завершение каждой команды только в согласованной точке состояния/WAL.
6. Сохранить проверки числовых диапазонов, framing body/CRLF, ошибки нехватки памяти и порядок ответов.
7. Реализовать явно выбранную реакцию на multi-watch reserve. Не запретить промежуточный `watch target` при подключении с `default`.
## Маршрутизация
| Команды | Источник владельца/исполнитель |
| --- | --- |
| `use`, `watch`, `ignore` | Имена tubes и изменение ссылок сессии |
| `put`, `peek-ready`, `peek-delayed`, `peek-buried`, `kick` | Текущая `use` tube |
| `reserve`, `reserve-with-timeout` | Единственная наблюдаемая tube плюс состояние deadlines сессии |
| `peek`, `delete`, `release`, `bury`, `touch`, `reserve-job`, `kick-job`, `stats-job` | Job ID; не текущая `use`/watch tube |
| `pause-tube`, `stats-tube` | Явно указанное имя |
| `list-tube-used`, `list-tubes-watched` | Подтверждённое состояние сессии frontend |
| `list-tubes`, `stats` | Сбор данных владельцев/frontend по определённому контракту |
| `quit` | Закрытие клиентской сессии и cleanup у всех затронутых owners |
## Совместимость
- Priority/FIFO сохраняются внутри tube. Текущий `job_pri_less` использует `(priority, id)`; изменение ID требует доказательства эквивалентности или отдельного ordering field.
- `reserve-job` не привязывается к watch list и сохраняет выбор ready/buried/delayed job по ID.
- `delete` чужой reserved job недопустим, но ready/delayed/buried jobs доступны по прежним правилам. `release`, `bury`, `touch` проверяют сессию-владельца резервации.
- Disconnect возвращает резервации в очереди; timeout во время передачи body не приводит к use-after-free.
- `use` не меняет watch list, а `ignore` не освобождает уже зарезервированные jobs.
- Недоступный owner не трактуется как пустая tube или отсутствующий job. Ответы `NOT_FOUND`/`TIMED_OUT` не подменяют отказ инфраструктуры.
## Статистика
- Суммировать только действительно аддитивные job/command counters с определённым владельцем каждого счётчика.
- Connection/producer/worker/waiting counters считают уникальные клиентские сессии, а не IPC endpoints и не число owner-side копий.
- `list-tubes` объединяет реальные объекты, не все возможные имена, и не дублирует `default`.
- Определить согласованность snapshot при конкурентных изменениях. Не обещать атомарный глобальный snapshot без реализации барьера.
- Для `pid`, server `id`, `uptime`, `rusage-*` и binlog-полей применять контракт 01. Номера сегментов разных WAL нельзя бессмысленно суммировать.
## Приёмка
- Каждая строка routing table имеет успешные, ошибочные и межвладельческие тесты, где применимо.
- Полный lifecycle ready/reserved/delayed/buried и приоритеты проходят на двух и более owners.
- `use` одной tube при watch другой корректен, в том числе если owners различны.
- Один consumer может держать несколько jobs; смена watch/`reserve-job` не теряет старые резервации.
- Нулевая/положительная задержка reserve, safety margin, `touch`, pause/unpause и disconnect соответствуют выбранному контракту.
- Гонки reserve vs delete, timeout vs touch, pause vs reserve и disconnect vs reply не дают двух активных резерваций или двух ответов.
- Исходный `make check` используется по матрице совместимости 01. Если выбран отдельный legacy-режим, он проходит весь прежний набор, включая multi-tube; ограничение нового режима не переносится на legacy.
## Точки Кода
`prot.c`, `conn.c`, `job.c`, `tube.c`, `dat.h`, `testserv.c`, `testjobs.c`, `testheap.c`.
+44
View File
@@ -0,0 +1,44 @@
# 05. WAL, Job ID И Восстановление
Статус: не начат. Зависимости: 01, 02; интеграция 03, 04.
## Цель
Изолировать журналы владельцев и обеспечить восстановление без пересечения ID, появления двух копий tube или незаметной потери совместимости данных.
## Задачи
1. Определить layout общего data root, метаданных топологии и отдельных каталогов owner slots. Каждый WAL имеет одного writer и собственную блокировку.
2. Проверять топологию и layout до любых изменяющих операций с журналами; создавать начальные метаданные атомарно с необходимой durability, если сервер запускается с пустыми данными.
3. Задать формат decimal uint64 job ID и маршрутизацию к владельцу без глобального mutable каталога каждого job на критическом пути, если выбранная схема это позволяет.
4. Решить восстановление генератора ID при частичном restart: чтения максимального ID среди живых jobs недостаточно, если большие ID уже удалены. Учитывать старые сообщения/сессии и generation fencing; документировать срок гарантий уникальности.
5. Определить переполнение и исчерпание диапазона ID: контролируемый отказ вместо wraparound и коллизий.
6. Сохранить порядок FIFO внутри tube при изменении ID. Если вводится отдельный sequence, определить момент выдачи и запись/восстановление; не изменять порядок поступления относительно текущей версии без решения.
7. Запускать replay каждого owner отдельно. До готовности replay его partition space недоступен; frontend не перенаправляет операции к соседям.
8. Восстанавливать jobs в соответствии с текущей семантикой WAL, не считать клиентские резервации пережившими restart. Старые frontend-сессии не продолжают работу поверх восстановленных jobs.
9. Проверить compaction, segment rotation, file references, disk-full/short write/fsync errors и взаимодействие с подтверждением команд.
10. Реализовать выбранную в 01 политику старого WAL: отдельный offline import с сохранением оригинала либо явный отказ нового режима. Если отдельный legacy сохраняется, он читает прежний формат.
## Ограничения
- Нельзя совместно писать независимыми owners в существующий binlog-каталог, даже если убрать `waldirlock`.
- Tube descriptor не становится персистентным объектом: имя восстанавливается из живых jobs. Пустые клиентские tubes не обязаны переживать restart.
- Частичный перезапуск без WAL теряет данные только соответствующего owner по заявленному контракту; это нельзя выдавать за восстановление или HA.
- С `-b` границы потери данных определяются реальной записью и настройками fsync. Не требовать отсутствия любых потерь при `-F` и не ослаблять `-f 0` под видом ускорения.
- При изменении `Jobrec` следовать процедуре `Walver` в `dat.h`, включая чтение старых версий. Многопроцессность сама по себе не требует изменения `Jobrec`.
- Онлайн-миграция и изменение карты не входят в эпик. Увеличение числа slots с переназначением partitions требует отдельной offline-процедуры, не простого изменения config.
## Приёмка
- Два owners одновременно записывают задания, используют разные WAL и не пересекающиеся ID.
- После удаления всех jobs и частичного restart генерация ID не создаёт опасных коллизий по утверждённому контракту; тест включает запоздалые сообщения старого поколения.
- Неизменная топология восстанавливает jobs в правильных tubes и owners; несовместимая карта не меняет существующие файлы при отказе запуска.
- Полный/частичный restart проверен с ready, delayed, buried и ранее reserved jobs; внешние сессии не сохраняют ложное владение восстановленными jobs.
- Ошибка одного WAL не повреждает файлы другого. Реакция логического сервера соответствует 01/06.
- Проверены crash windows до/после записи и подтверждения, compaction, rotation, недостаток места и повреждённый журнал.
- Старые данные остаются пригодны для legacy либо импортируются явным инструментом согласно выбранной политике; исходник не уничтожается автоматически.
- Документированы ID layout, topology metadata, срок гарантий ID, WAL compatibility и ограничения durability.
## Точки Кода
`job.c:make_job_with_id/next_id/job_pri_less`, `dat.h:Jobrec/Walver/Wal/File`, `walg.c`, `file.c`, `serv.c:srv_acquire_wal`, `prot.c:prot_replay`, WAL-тесты в `testserv.c`.
+47
View File
@@ -0,0 +1,47 @@
# 06. Supervisor И Эксплуатация
Статус: не начат. Зависимости: 02, 03; итоговая интеграция 04, 05.
## Цель
Сделать многопроцессный сервер управляемым сервисом с единой конфигурацией, предсказуемым запуском и корректной реакцией на сбои.
## Задачи
1. Ввести supervisor, запускающий owners/frontend с явно заданными ролями, slot IDs, generations и топологией.
2. Обеспечить один активный процесс на slot. Перед заменой подтвердить завершение прежнего процесса; PID reuse не является подтверждением его идентичности.
3. Организовать startup barrier: проверка config, locks и топологии, восстановление WAL, IPC handshake, готовность к клиентским операциям.
4. Корректно закрывать лишние унаследованные listening/client/IPC/WAL descriptors. Не наследовать живые jobs/сессии через fork как способ совместного владения данными.
5. Интегрировать выбранные CLI/config, `-u`, TCP/Unix sockets и socket activation. Проверить границы привилегий и права на IPC/data root.
6. Реализовать согласованную обработку `SIGUSR1` drain и остановки. Drain прекращает новые `put` во всём логическом сервере, но не означает остановку выдачи существующих jobs.
7. Ввести restart policy с ограничением частоты/числа попыток и диагностикой. Во время недоступности slot его имена не передаются другим owners.
8. Определить поведение при смерти supervisor: оставшиеся процессы не продолжают принимать изменения без действующей управляющей эпохи; исключить запуск второго независимого сервера поверх тех же slots.
9. Обеспечить логи с role/slot/generation и метрики IPC backlog, restart count, owner readiness, загрузки и WAL ошибок.
10. Обновить эксплуатационную документацию: запуск, проверки готовности, restart, drain, резервное копирование/восстановление при остановленном сервере, запрет несогласованного изменения карты.
## Матрица Отказов
| Событие | Требуемое свойство |
| --- | --- |
| Смерть frontend | Владельцы освобождают ссылки/ожидания/резервации всех его сессий; старое поколение больше не принимается |
| Смерть owner | Затронутые сессии обрабатываются по контракту 01; очистка связанных сессий у остальных owners; перезапуск только того же slot |
| Разрыв IPC | Неизвестные результаты не повторяются автоматически; endpoint/session fencing исключает поздние изменения |
| Смерть supervisor | Нет бесконтрольных orphan writers и двух поколений активного сервера |
| Ошибка WAL | Явная диагностика и согласованная политика отказа; не тихое переключение на другой owner |
| `SIGUSR1` | Глобально согласованный drain, включая owners/frontend, перезапущенные во время drain |
| Остановка | Нет новых принятых операций после согласованного барьера; bounded shutdown, явная политика незавершённых ответов |
## Приёмка
- Запускаются один и несколько owners/frontend с одинаковой картой; ошибочная конфигурация не оставляет работающих дочерних процессов и занятых sockets.
- Попытка второго владельца slot отвергается как с WAL, так и без WAL.
- Kill/restart владельца сохраняет маршруты остальных tubes и не создаёт дубль упавшего slot.
- Затронутые клиенты получают документированный отказ/закрытие, а не `NOT_FOUND` из-за отсутствующего процесса.
- После перезапуска во время drain новый процесс не принимает `put` до снятия/завершения drain по контракту.
- Проверены supervisor/frontend/owner crashes, частичный startup failure, сигналы и отсутствие утечек FD/zombie-процессов.
- Запуск и платформы соответствуют выбранной в 01 политике перехода. Если legacy сохраняется, его startup и поддерживаемые платформы не регрессируют; неподдерживаемая платформа нового режима выдаёт явный отказ сборки/запуска.
- Обновлены руководство и примеры запуска; статусы/метрики позволяют определить недоступный slot без анализа PID по догадкам.
## Точки Кода
`main.c`, `serv.c`, `net.c`, `util.c`, платформенные event backends, `Makefile`, `doc/beanstalkd.ronn`, `README.md`, `testserv.c`.
+60
View File
@@ -0,0 +1,60 @@
# 07. Приёмка И Производительность
Статус: не начат. Baseline зависит от 01; итоговая приёмка зависит от 02-06.
## Цель
Доказать корректность и полезность многопроцессного режима воспроизводимыми тестами. Наличие нескольких PID или рост общей загрузки CPU сами по себе не являются приёмкой.
## Функциональная Матрица
| Область | Обязательные сценарии |
| --- | --- |
| Регрессии | `make`, `make check` по матрице 01; полный прежний набор при сохранении legacy; новый режим с 1 и несколькими owners |
| Создание tube | Одновременные `use`/`watch` через разные frontend, delete/recreate, единственный `default`, отсутствие создания на `stats-tube` |
| Routing | Все команды из 04; `use` и watch на разных owners; операции по ID независимо от текущей tube |
| Lifecycle | Ready/reserved/delayed/buried, kick/kick-job, приоритеты и FIFO, несколько резерваций на сессию |
| Watch | `watch target` перед `ignore default`; отказ multi-watch reserve; смена tube с живыми резервациями |
| Сессии | `reserve-job` другого owner, touch/delete старых резерваций, чужой reserved job, cleanup всех owners |
| Таймеры | Reserve timeout 0/positive, TTR, `DEADLINE_SOON`, delay, pause/unpause; гонки на границах времени |
| I/O | Partial requests/replies, pipelining, half-close, медленный client, large body, broken IPC, некорректные frame lengths |
| Ресурсы | IPC backpressure, OOM, ограниченные buffers, утечки памяти/FD после многократного reconnect |
| Отказы | Kill frontend/owner/supervisor, restart generations, stale messages, duplicate requests при наличии retry |
| WAL | Раздельные logs, partial/full restart, удалённые максимальные ID, compaction, rotation, disk full, crash windows |
| Эксплуатация | Drain, startup failure, невозможность двух writers, несовместимая карта, старые данные |
| Статистика | Нет двойного счёта сессий, один `default`, корректные process/binlog поля, недоступный owner |
## Свойства Для Проверки
- Один job не имеет двух действующих резерваций, но обработка внешним worker может повториться после TTR. Не объявлять корректные TTR redeliveries дубликатами сервера.
- Один ожидающий reserve завершается ровно одним исходом либо закрытием соединения; поздний IPC-ответ не меняет этот исход.
- Зафиксированный набор полностью завершённых операций согласуется с итоговым состоянием при quiescent проверке. Не требовать атомарности произвольного распределённого `stats` snapshot.
- После завершённого cleanup не остаются ссылки умершей сессии; сообщение старого поколения не создаёт их заново.
- Успешные операции не теряются сверх заявленных гарантий WAL/fsync. Неизвестный результат при обрыве соединения учитывается отдельно от подтверждённого успеха.
- Недопустимая топология отвергается без модификации исходных данных.
## Нагрузочная Матрица
1. Сравнить исходную версию, новый режим с одним owner, новый режим с 2/4/доступным числом owners и независимые исходные экземпляры как ориентир стоимости IPC.
2. Выполнить равномерную нагрузку по многим tubes, одну горячую tube и перекошенное распределение. Отдельно измерить создание/удаление множества короткоживущих tubes.
3. Варировать число frontend и клиентов; показать, не стал ли frontend/supervisor/генератор нагрузки новым bottleneck.
4. Повторить сравнения для WAL off/on, одинаковых `-f`/`-F`, размера body, queue depth и смеси команд.
5. Измерять throughput завершённых lifecycle и отдельных команд, p50/p95/p99 latency, CPU каждого процесса, RSS/суммарную память, IPC traffic/backlog, disk throughput/fsync latency.
6. Использовать прогрев, несколько повторов, одинаковые параметры сборки и инфраструктуры. Публиковать разброс и сырые результаты, а не только лучший запуск.
7. Сопоставить с численными критериями, зафиксированными в 01 после baseline и до итоговых тестов. Если пороги не выполнены, представить профиль и причину; эпик не считать пройденным автоматически.
## Средства
- Существующие команды: `make`, `make check`, `make bench`. Последняя является набором микробенчмарков, а не end-to-end испытанием.
- Добавить Unix integration harness для запуска ролей, kill/restart и инъекции отказов; переиспользовать `ct/` и существующие подходы `testserv.c`, где это сохраняет ясность.
- Для ошибок памяти использовать доступные sanitizers или эквивалентные средства на поддерживаемой платформе. Они не заменяют проверки межпроцессной семантики.
- Не завязывать таймерные тесты только на короткие sleeps: использовать синхронизацию/наблюдаемые события и разумные временные допуски, чтобы избежать flaky результатов.
## Итоговые Артефакты
- Автоматические тесты и воспроизводимые команды их запуска.
- Отчёт по compatibility matrix с явными ограничениями нового режима и политикой старых данных.
- Отчёт по производительности с baseline, конфигурациями, численными результатами и выводом о bottlenecks.
- Проверенные эксплуатационные инструкции и перечень оставшихся ограничений: одна tube последовательна, cross-shard watch reserve и live migration отсутствуют.
Реализация считается завершённой только после выполнения общей приёмки из [00-overview.md](00-overview.md), а не после успешного happy path на двух процессах.