ADR 0008: общие бюджеты полосы и допуск по принципалам на шлюзе
Статус: принято, 2026-09-22.
Задача
Ограничение числа запросов не ограничивает байты/с. Если лимит скорости принадлежит отдельному соединению, клиент может умножить его параллельными передачами. Перед реализацией read gateways нужен проверяемый локальный исполнитель сетевого бюджета.
Решение
Один BandwidthGovernor на upload и один на download в процессе API. Каждая порция содержимого проходит одновременно через общий token bucket направления и bucket принципала. Принципал — id сервисной учётной записи; смена ключа с тем же id, репозитория или HTTP-соединения не создаёт новый bucket. Размер таблицы ограничен загруженными ключами, максимум 1000; состояния сохраняются до остановки процесса, а не до закрытия запроса. Бюджеты направлений независимы.
Burst ограничен min(rate / 10, 1 MiB), с округлением вниз. Квант не превышает 64 KiB и любого включённого burst. Пополнение использует монотонное время; на направление действует один таймер. Ожидание ограничено 256 квантами, по одному запросу разрешения на каждый активный поток. Очередь вращается по принципалам; ожидающий своего bucket клиент не блокирует свободную квоту другого. Если квант готового принципала ждёт общего bucket, маленькие кванты других клиентов не расходуют его пополнение бесконечно. Долгосрочное число выданных байтов не превышает rate × elapsed + burst с момента старта/полного bucket; это верхняя граница, не гарантия достижения скорости.
Исполнитель расположен в infrastructure, сборка и подключение к HTTP — в apps/api. Домен, сценарии публикации и БД не знают о TCP pacing. Все full/multipart uploads и downloads проходят через него. Сборка частей worker и offline операции остаются дисковыми, без сетевой квоты. Upload ограничивается скоростью чтения body с backpressure; download — выдачей bytes в ответ. Сетевые буферы ОС/proxy, HTTP/TLS overhead и физический uplink находятся вне этого счётчика. Потребление квоты не возвращается при отмене после выдачи: это консервативный учёт.
AdmissionQueue дополнительно ограничивает число активных запросов одного принципала, пропускает подходящих клиентов мимо временно заблокированного и не принимает запросы после close. Значения по умолчанию — один изменяющий upload-сценарий и min(4, maxDownloads) скачивания на принципала. Ограничение количества сохраняет память и защищает очередь, rate limit регулирует payload. В preClose очереди закрываются до ожидания HTTP-запросов; ожидающие порции отменяются. При потере владения БД новый квант отклоняется, ожидание не переходит в безлимитный режим.
/health/ready получает агрегированные счётчики допуска/квот без ID клиентов, ключей, путей или названий файлов. Endpoint по-прежнему требует сервисную авторизацию; все его авторизованные пользователи видят агрегаты узла. Схема ответа отражена в contracts/OpenAPI. Выданные разрешения на bytes не являются подтверждением доставки или публикации. Миграция БД не нужна.
Ограничения и следующий этап
Квоты действуют в одном API-процессе, включаются конфигурацией; значение 0 отключает отдельный rate ceiling. Restart сбрасывает buckets и счётчики, разрешая новый bounded burst. Это не долговременная квота объёма, не распределённый scheduler и не защита от отказа диска. Standalone ownership lock остаётся: второй API на том же каталоге не включён.
Следующий распределённый этап требует выбранного backend с подтверждёнными копиями, независимых read gateways, конечных leases сетевого бюджета, запрета работы после expiry и тестов общей полосы/сетевого разделения. Нельзя просто запустить несколько локальных bucket с полным лимитом площадки: сумма умножится на число процессов. Двухсерверный HA-стенд остаётся отложенным.