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).