RVQ / implementation overview

qualification / PASS

Как работает очередь после полного review

Простое объяснение без внутреннего жаргона: как задача проходит через систему, где были узкие места и почему новая версия быстрее и безопаснее.

Performance gate
2× target
Lock retries
0
API p99
2–7 ms
Tests passed
120

01 / Основной поток

Одна задача превращается в несколько независимых частей

Клиент создаёт задачу, RVQ делит её на части, воркеры забирают части параллельно, а после завершения система отправляет подписанный webhook.

Маршрут одной задачи request → parallel work → durable delivery
01 / ORIGIN
Клиент Отправляет задачу и набор частей.
02 / DURABLE QUEUE
RVQ + PostgreSQL Фиксируют task и sub_tasks одной транзакцией.
03 / PARALLEL
Пул воркеров Забирает разные части без ожидания чужих блокировок.
04 / OUTBOX
Гарантия доставки Результат и событие завершаются вместе.
05 / CALLBACK
Webhook Подписанный итог возвращается партнёру.
активное движение долговечное состояние граница компонента
1

Создание

Один task сохраняется вместе с набором sub_tasks.

2

Обработка

Несколько воркеров безопасно забирают разные части задачи.

3

Завершение

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.

Было

Claim: A -> B Complete: B -> A T1 держит A, ждёт B T2 держит B, ждёт A Результат: deadlock + retry

Стало

Claim: A -> B -> C Complete: A -> B -> C Порядок: (queued_at, id) Результат: 0 lock retries

Счётчики обновляются пачкой

Вместо отдельного 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 с неверной подписью.

Как выглядит прогон

|---- warm-up 60s ----|---- measurement 120s ----| ^ | reset metrics Cold-start можно проверить отдельно: --warmup 0

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.

PASS
200 026 из 200 026 webhook-подписей валидны.
120 тестов прошли, включая 15 интеграционных тестов с реальным PostgreSQL.