01 / Основной поток
Одна задача превращается в несколько независимых частей
Клиент создаёт задачу, RVQ делит её на части, воркеры забирают части параллельно, а после завершения система отправляет подписанный webhook.
Создание
Один task сохраняется вместе с набором sub_tasks.
Обработка
Несколько воркеров безопасно забирают разные части задачи.
Завершение
RVQ собирает статусы частей и ставит итоговый webhook в outbox.
02 / Безопасность
Каждый тип доступа получил отдельный ключ
Если обязательный ключ не настроен, соответствующий API не становится публичным — он отключается. Это безопасное поведение по умолчанию.
Partner API
APP_PARTNER_TOKEN
Создание задач и статистика.
Worker API
APP_WORKER_TOKEN
Получение и завершение sub_tasks.
Admin API
APP_ADMIN_TOKEN
Опасные административные операции, включая reset.
Webhook тоже проверяется
- Адрес должен входить в точный список
APP_WEBHOOK_ALLOWED_HOSTS. - Неожиданные redirects, credentials в URL и неразрешённые private IP блокируются.
- Каждая доставка подписывается HMAC, а benchmark проверяет подпись.
03 / PostgreSQL
Все операции блокируют строки в одном порядке
Раньше claim и complete могли брать одни строки в противоположной последовательности. Две транзакции ждали друг друга, и PostgreSQL останавливал одну из них как deadlock.
Было
Стало
Счётчики обновляются пачкой
Вместо отдельного trigger-вызова на каждую строку PostgreSQL группирует изменения всей команды и обновляет status counters в детерминированном порядке.
Индекс совпадает с порядком claim
Partial index теперь использует (queued_at, id), поэтому перед
SKIP LOCKED не нужен дополнительный incremental sort.
04 / Производительность
Меньше параллелизма оказалось быстрее
Pool из 64 соединений создавал слишком много одновременных транзакций вокруг одной головы очереди. При 32 соединениях запросы меньше спорят за строки и быстрее освобождают pool.
API pool = 64
p99: 166–194 ms
Избыточная конкуренция за queue-head и parent task rows.
API pool = 32
p99: 2–7 ms
Тот же чистый стенд, нагрузка и продолжительность.
API pool
32 connections
Create, claim и complete.
Dispatcher pool
32 connections
Webhook backlog не отбирает соединения у API.
WAL budget
32 GB
Checkpoint не попадает внутрь qualification window.
Соединения открываются до первого запроса
При запуске RVQ сначала применяет схему и прогревает оба PostgreSQL pool. Только после этого открывается HTTP listener, поэтому первый трафик не оплачивает создание соединений.
05 / Проверка
Benchmark теперь сам решает: PASS или FAIL
Раньше инструмент только печатал числа. Теперь --assert-slo
возвращает ненулевой код, если система не выполняет бюджет.
Что проверяется
- create и claim достигают заданного throughput;
- HTTP p99 меньше 100 ms;
- webhook lag p99 меньше 5 секунд;
- нет errors и shed requests;
- нет webhook с неверной подписью.
Как выглядит прогон
06 / Результат
Чистый 2× performance gate
PostgreSQL 16, 60 секунд warm-up, затем 120 секунд измеряемой нагрузки: по 1666 create, claim и complete ticks в секунду.
| Поток | Достигнуто | Errors | Shed | p99 |
|---|---|---|---|---|
| create | 1666.0 rps | 0 | 0 | 2.17 ms |
| claim | 1666.1 rps | 0 | 0 | 5.20 ms |
| complete | 1267.9 rps* | 0 | 0 | 7.01 ms |
| webhook | 1666.9 rps | 0 invalid | — | 203.78 ms lag |
* Complete ограничен количеством полученных sub_tasks, а не производительностью endpoint.
120 тестов прошли, включая 15 интеграционных тестов с реальным PostgreSQL.