Фоновое освобождение места
Физическая очистка работает на действующем writer одновременно с API, completion worker и read gateways. Останавливать сервисы для обычной очистки не требуется. Сборщик удаляет содержимое отменённых/логически удалённых объектов после защитного срока, освобождает временные части опубликованных объектов и отменяет истёкшие незанятые загрузки. Он не выбирает опубликованные билды для удаления: это отдельные retention/delete операции.
Управление
Консоль: Каталог → Хранение и автоочистка → Физическая очистка. Настройки применяются без restart; выключение останавливает дальнейшие объекты после завершения текущего. Последний проход показывает обработанные/отложенные/ошибочные объекты и освобождённые байты. «Запросить проход» ставит работу в ближайший фоновый цикл, не обещает немедленное удаление; при нагрузке других репозиториев возможна задержка. Обновление состояния выполняется кнопкой; несохранённые поля автоматически не перезаписываются.
По умолчанию очистка выключена, grace — 24 часа, порция — 25 файлов, интервал — 60 секунд, пауза между файлами — 50 мс. Допустимые значения: grace 0–8760 часов; порция 1–100; интервал 5–86400 секунд; пауза 0–1000 мс. Grace 0 допускает удаление сразу после логического удаления и закрытия читателей. Это не корзина восстановления. Для включения на существующей установке сначала обновите все процессы по инструкции ниже. Пока физическая очистка репозитория выключена (по умолчанию так), временные parts/ завершённых multipart-загрузок остаются на диске: после публикации их удаляет только проход очистки (пометка temp_cleaned) репозитория, где она включена, или offline npm run gc; учитывайте этот объём при планировании диска.
| API | Право | Поведение |
|---|---|---|
GET/HEAD /api/v1/repositories/{repository}/storage/cleanup | storage.read | Настройки, revision, время и результат последнего прохода |
| PUT того же пути | storage.manage | {expectedRevision, policy}, CAS; в том числе динамическая пауза |
POST /api/v1/repositories/{repository}/storage/cleanup/run | storage.manage | {expectedRevision}, запрос ближайшего прохода включённой политики |
Операции доступны только управляемым сервисным ключам в пределах точных grants: права storage.read и storage.manage не выдаются парольным сессиям, персональным токенам, файловым ключам и ключу восстановления. Изменение настроек повторно проверяет права внутри транзакции. Фоновая физическая очистка выполняется как обслуживание уже отменённых объектов, не от имени сохранённого пользовательского ключа. Отзыв ключа запрещает дальнейшее управление, но не воскрешает логически удалённые объекты и не останавливает обслуживание. В отличие от retention, сборщик не получает права удалять живой каталог.
const storage = client.inRepository('builds').storage;
const current = await storage.cleanup();
const saved = await storage.configureCleanup(current.revision, {
...current.policy,
enabled: true,
graceHours: 24,
batchSize: 25,
});
await storage.requestCleanup(saved.revision);HTTP 200 POST означает запрос, а не завершение. Повторные запросы объединяются; endpoint не создаёт неограниченную очередь. SDK не повторяет mutation автоматически. Reader gateways предоставляют чтение состояния, изменения отправляются writer. Полный inventory находится в OpenAPI 0.11.0.
Безопасность и ресурсы
- GET/HEAD content (по ID, пакету и пути файла) захватывают shared pin до проверки состояния и удерживают его до закрытия источника HTTP. UPack inspection также защищён. Один дополнительный PostgreSQL session на gateway хранит reference-counted pins всех его читателей; длительных SQL-транзакций и соединения на каждый stream нет.
- Сборщик использует неблокирующий exclusive lock конкретного upload; для удаления самого blob — также content. Занятая запись/complete откладывается, активный читатель откладывает удаление его blob. Уборка временных частей опубликованного файла не блокирует ни текущие, ни новые скачивания. Сканирование идёт порциями по индексу с последним временем попытки, чтобы занятые/ошибочные записи не вытесняли остальные.
- Перед удалением повторно проверяются состояние, grace и ссылки из references, asset history/current и attachment history. Уже отменённый ID невозможно снова опубликовать. Пины metadata-history сохраняются независимо от состояния текущей привязки.
- Сначала удаляются staging/parts/blob и синхронизируются каталоги по действующему контракту LocalBlobStore; затем фиксируется
reclaimed. При ошибке диска квота сохраняется. Авария между unlink и SQL завершается идемпотентным повтором. Windows directory fsync имеет прежнее ограничение Node, описанное в ADR 0004. - В одном экземпляре БД работает один проход GC. Между файлами проверяется актуальная revision/пауза и ownership writer. Потеря защиты чтения прерывает дальнейшую выдачу; потеря ownership останавливает дальнейшее обслуживание. Это не HA и не защита от неподдерживаемого storage backend.
- Порция и пауза ограничивают интенсивность, но не задают строгую дисковую полосу: время unlink зависит от ФС и числа частей. Операции асинхронные; HTTP не ждёт завершения прохода. Нельзя обещать нулевое влияние фонового I/O на задержки.
- Результаты сохраняются в настройках и bounded журнале
cleanup.completed; ошибки не содержат файловых путей или секретов.retiredBytesуменьшается только после успешного физического удаления и записи учёта.
Обновление и восстановление
Нужны миграции 16/17: настройки GC, поля учёта и индексы кандидатов/due. Индексы создаются существующим resumable CREATE INDEX CONCURRENTLY. Обновите writer, readers и worker до включения политики. Первая установка/миграция выполняется по обычной процедуре обновления; отсутствие остановок относится к последующей очистке, а не к миграции старого runtime.
Процессы новой версии отмечают поддержку протокола. Если активен старый gateway/worker без отметки, GC откладывает работу с upgrade_required. Смешанные версии после включения GC не поддерживаются: нельзя затем запускать старый бинарный файл. Автоматический откат на схему 15 не допускается. Fencing старого кода, который не знает новый протокол, не заявляется.
Сохранённые настройки продолжают действовать после restart. Фоновый цикл writer проверяет один due repository каждые 5 секунд. Read gateways не становятся сборщиками. При выключенном writer сборка не идёт. Replica/storage failover остаётся отдельным профилем.
npm run gc сохранён как прежний offline repair tool; он не является механизмом этой автоматической очистки и продолжает отказывать рядом с активными процессами. Scrub также остаётся отдельным offline инструментом. Сборщик не удаляет произвольные файлы вне зарегистрированных ID и не сокращает исторические metadata snapshots или immutable identities. Резервная копия защищает свои blob сама (ADR 0054): перед удалением содержимого оба сборщика проходят допуск unlink (upload → content → 18471/18 shared), который отказывает при закрытом барьере capture и при backup pin. Такой объект откладывается (online — last_deferred, offline — deferred в gc.completed) и удаляется следующим проходом после снятия pins.
Offline GC также проверяет ownership до и после асинхронных операций и держит независимые per-object upload/content guards во время удаления. Потеря maintenance session останавливает дальнейшую работу; уже защищённый объект не становится доступен конкурентному upload/reader только из-за потери общей сессии. При неопределённом исходе удаления квота сохраняется до повторного прохода. Оба сборщика проверяют защиту между удалением staging, parts и blob. Уже переданный ОС вызов удаления нельзя отменить при потере самой object-lock session; это ограничение локального storage-протокола, не гарантия fencing файловой системы при HA/failover.
Решение: ADR 0035. Проверки: tests/cleanup.test.mjs, tests/integration/online-cleanup.test.mjs, UI-сценарий tests/browser/storage-policy.mjs; запуск через общий gate.