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.
Решение
last_used_atобновляется не чаще раза в минуту на ключ, условием в самомUPDATE.- Миграция 36 создаёт индексы
arkvory_references(artifact_id)иarkvory_packages(repository, artifact_id)черезCREATE INDEX CONCURRENTLY, как миграция 35. - Обрезка аудита и событий удаляет строки ниже
max(sequence) - Nпачками до 1000 строк, не считая всю таблицу. Откаты оставляют пропуски в последовательности, поэтому строк может остаться меньше N. - Пакетный запрос LFS читает размеры всех объектов одним запросом
oid = ANY(...). - Версии пакета читаются страницами keyset. Курсор непрозрачный: позиция сортировки последней строки (ключ SemVer или время продвижения), версия и id. Обход страницами даёт тот же порядок, что один полный запрос, в том числе при равных ключах.
- 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, не меняется. - Политика хранения сначала ранжирует весь репозиторий на снимке
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; - политика хранения: сборка, нужная другому пакету, не удаляется. Мутационная проверка подтверждает, что без этого условия тест падает.