Skip to content

0073. Счётчики занятого объёма, пулы соединений и лимиты загрузок ​

Статус: accepted. Owner: Ian Panaev. Дата: 2026-10-11.

Контекст ​

Аудит производительности (2026-10-11) нашёл места, которые первыми ломаются при росте каталога и числа CI-агентов. Процессор цикл событий не блокирует: разбор архивов и JSON ограничен, синхронного ввода-вывода в серверных путях нет. Узкие места — в базе и в лимитах:

  • каждое создание загрузки берёт глобальную блокировку (18471, 2) и под ней суммирует размер всех не освобождённых загрузок, а затем ещё раз по репозиторию; те же суммы считаются на каждом куске docker push и каждую минуту в проверке квот. При миллионах строк это полный проход по таблице внутри глобальной блокировки;
  • пул API — 10 соединений на 128 одновременных запросов, и до трёх из них постоянно держат служебные сессии (владение хранилищем, закрепления скачиваний, аренда шлюза);
  • пул блокировок записи у worker — 2 соединения, а каждое зеркало держит одно на всё копирование части;
  • по умолчанию 2 одновременные загрузки на сервер и 1 на ключ; SDK ждал ровно Retry-After, и отказанные клиенты возвращались одновременно;
  • max_connections управляемого PostgreSQL — 100 по умолчанию.

Решение ​

  1. Счётчики занятого объёма (схема 35). Таблица arkvory_upload_bytes(repository, status, bytes) хранит сумму размеров не освобождённых загрузок по репозиторию и статусу. Её ведёт триггер на arkvory_uploads в той же транзакции, что и изменение строки, — при любом пути (создание, публикация, отмена, очистка, политика хранения, зеркала, восстановление). Вручную её не обновляет ни один вызывающий код. Внутри одного изменения строки счётчики блокируются в порядке ключа.
    • Проверки ёмкости и квот, место для OCI-загрузок и сводка использования репозитория читают маленькую таблицу вместо прохода по загрузкам.
    • Глобальная блокировка создания остаётся: лимит ёмкости строгий, а держится она теперь миллисекунды.
    • Миграция блокирует запись в arkvory_uploads на время одного подсчёта (SHARE ROW EXCLUSIVE) и включает триггер в той же транзакции: ни одно изменение не проходит между подсчётом и триггером. Rolling update ждёт её; старый код таблицу не читает.
    • Таблица производная: в резервную копию не входит, нормализация восстановления пересчитывает её из строк.
  2. Пулы. Долгоживущие сессии получают свой пул (8 соединений: закрепления одновременных скачиваний тоже берут их, а с 3 скачивание на HA-стенде ждало соединение дольше 5 с) и не занимают соединения запросов. Пул запросов API по умолчанию — 20 (ARKVORY_DATABASE_POOL_SIZE, нижняя граница прежняя, 4). У worker пул блокировок записи — 2 плюс по соединению на зеркало (до 16). Метрики arkvory_database_pool_{connections,idle,waiting} по пулам.
  3. PostgreSQL. Управляемый кластер и Compose запускаются с max_connections=200. idle_in_transaction_session_timeout=60s во всех пулах каталога: зависшая транзакция освобождает блокировки на сервере.
  4. Лимиты загрузок. По умолчанию 8 одновременных загрузок на сервер (ARKVORY_MAX_UPLOADS, как и раньше до 32) и min(4, всего) на ключ. Явно заданные значения остаются допустимыми.
  5. Повторы SDK. После Retry-After клиент ждёт ещё до половины этого времени случайно (не дольше политики): отказанные вместе клиенты возвращаются в разное время.

Отклонённые предложения аудита ​

  • Возвращать соединение блокировки записи в пул после pg_advisory_unlock. Соединение уничтожается намеренно (ADR 0042): блокировки сессии гарантированно уходят вместе с ним, на этом держится защита от второго писателя. Новое локальное соединение стоит миллисекунды на часть от 8 МиБ.
  • lock_timeout для обычных запросов. Он действует и на рекомендательные блокировки: публикации, которые сейчас ждут блокировку репозитория до 10 с, падали бы раньше — меняется режим отказа без измеренной пользы.

Последствия ​

  • Схема 35; MINIMUM_RESTORE_SCHEMA прежний. Расхождение счётчика с суммой — ошибка, тесты сравнивают их после каждого вида изменения и при параллельных транзакциях.
  • Публикации одного репозитория уже сериализованы блокировкой репозитория; счётчик горячей точки не добавляет. Создание загрузок сериализовано глобальной блокировкой, как и раньше, но без полного прохода.
  • Следующие этапы того же аудита — лишние копии больших объектов (завершение multipart, OCI, npm, зеркала) и деградирующие запросы (политика хранения, поиск, разрешение пакетов, очистка) — меняют путь записи и запросы и решаются отдельно.

Arkvory · ProAnimaStudio