Skip to content

0075. Границы запросов каталога ​

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

Контекст ​

Аудит производительности (2026-10-11, этап 3) проверил запросы каталога: их стоимость не должна расти вместе с репозиторием там, где пользователь просит мало. Также проверено, что блокировки репозитория не держатся дольше необходимого. Найдены такие места:

  • last_used_at ключа пользователя обновлялся при каждом запросе: одна запись в WAL на каждый вызов API.
  • Поиск ссылок и пакетов по artifact_id (проверки удаления, кандидаты политики хранения) шёл без индекса.
  • Обрезка аудита сервиса и событий хранения считала count(*) по всей таблице при каждой записи.
  • Пакетный запрос Git LFS (до 1000 объектов) делал по запросу на каждый объект.
  • Выбор версии пакета листал версии через OFFSET: глубокая страница заново сортировала все предыдущие.
  • npm packument строился и отдавался целиком при каждом запросе, даже если у клиента уже есть та же версия документа.
  • Политика хранения ранжировала все артефакты репозитория под блокировкой каталога, которая останавливает публикации в этот репозиторий. Замер: около 0,75 с на 100 000 артефактов и 2,4 с на 300 000.

Решение ​

  1. last_used_at обновляется не чаще раза в минуту на ключ, условием в самом UPDATE.
  2. Миграция 36 создаёт индексы arkvory_references(artifact_id) и arkvory_packages(repository, artifact_id) через CREATE INDEX CONCURRENTLY, как миграция 35.
  3. Обрезка аудита и событий удаляет строки ниже max(sequence) - N пачками до 1000 строк, не считая всю таблицу. Откаты оставляют пропуски в последовательности, поэтому строк может остаться меньше N.
  4. Пакетный запрос LFS читает размеры всех объектов одним запросом oid = ANY(...).
  5. Версии пакета читаются страницами keyset. Курсор непрозрачный: позиция сортировки последней строки (ключ SemVer или время продвижения), версия и id. Обход страницами даёт тот же порядок, что один полный запрос, в том числе при равных ключах.
  6. npm packument отдаётся с ETag (SHA-256 тела) и Vary: Accept; совпавший If-None-Match получает 304 без тела. На Accept: application/vnd.npm.install-v1+json отдаётся сокращённый документ npm: зависимости, bin, engines, платформы, dist и подобные поля, но без readme, скриптов и времён версий. Полный документ, который читает Unity Package Manager, не меняется.
  7. Политика хранения сначала ранжирует весь репозиторий на снимке REPEATABLE READ без блокировки, и этот расчёт только называет пакеты кандидатов. Под блокировкой ранжирование повторяется для этих пакетов: их версии находятся через индекс идентичности, без сканирования репозитория. Решение об удалении принимает только этот повтор. Артефакт, который входит также в пакет вне этого набора, пропускается: его место там неизвестно. Все пакеты каждого кандидата из первого расчёта входят в набор, поэтому кандидатов это условие не затрагивает. При группировке «весь репозиторий» корзина одна, и ранжирование под блокировкой остаётся полным.

Что проверено и оставлено ​

  • Поиск артефактов и npm — подстрока по имени и метаданным без индекса. Худший случай (совпадений нет): около 200 мс на 100 000 артефактов, без блокировок, на соединении пула запросов. Индекс pg_trgm потребовал бы расширения, которое есть не на каждом внешнем PostgreSQL. Ограничение числа просмотренных строк изменило бы публичный контракт страниц. Вернёмся к этому при измеренной нагрузке.
  • Снимок резервной копии держится только на время записи инвентаря и строк таблиц и ограничен сроком snapshotMilliseconds. Содержимое объектов копируется после закрытия снимка. Промежуточная запись на локальный диск ускорила бы только медленные хранилища резервных копий, ценой ещё одного тома.

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

  • Нагрузка на запись от аутентификации — не больше одного UPDATE в минуту на ключ. last_used_at показывается с точностью до минуты.
  • Публикации в репозиторий с политикой хранения ждут ранжирования только по затронутым пакетам. Замер: около 0,2 с на 94 пакета по 200 версий, независимо от размера репозитория. Полное ранжирование идёт без блокировки.
  • Повторный npm install с кэшем получает 304. Новый npm скачивает сокращённый документ.
  • Тесты:
    • обход keyset против полного запроса на 450 версиях с равными ключами;
    • packument: ETag, 304 и сокращённый формат через реальный npm install;
    • политика хранения: сборка, нужная другому пакету, не удаляется. Мутационная проверка подтверждает, что без этого условия тест падает.

Arkvory · ProAnimaStudio