ADR 0035: физическая очистка при работающем сервисе
Статус: принят. Дата: 2026-09-25. Owner: Ian Panaev.
Причина
Offline GC требовал остановки раздачи. Retention создавал tombstones, но сам не освобождал ёмкость. Для постоянного хранилища нужны физическая очистка и динамическая пауза без обслуживания всего сервиса.
Решение
Политика отдельно от last-N retention, по репозиторию, CAS, выключена до осознанного включения после обновления процессов. Application авторизует управление; domain валидирует границы; infrastructure владеет PostgreSQL locks и файловыми операциями; writer composition root запускает ограниченные фоновые проходы. UI и SDK работают через документированный API.
Content shared session locks с reference counts защищают все byte downloads и ZIP inspection. Один session на gateway; exclusive GC пытается захватить тот же content key и существующий upload key без ожидания. Не держим SQL-транзакцию во время I/O. При занятости объект откладывается. Управление квотой фиксируется только после удаления. Незавершённый проход повторяется по сохранённым cancelled/reclaimed состояниям. Старый offline collector остаётся отдельным repair tool.
Namespace advisory locks: (18471,16) — singleton GC; (18471,17) — отметка поддерживаемого протокола на storage ownership session. Content keys — SHA-256 от content:<uuid> в пространстве bigint, upload keys сохраняют прежний SHA-256 от uuid. Совпадение хешей консервативно уменьшает параллелизм; не даёт разрешения на удаление.
Уточнение аудита 2026-09-26: offline repair использует тот же порядок per-object upload/content guards, дополнительно к глобальной maintenance session. Application получает обязательный portable Cancellation и проверяет его до/после await; LocalBlobStore проверяет guard между фазами удаления. Поэтому потеря общей сессии не оставляет старый процесс бесконтрольно удалять объекты рядом с новым writer. Уже выполняемый системный вызов нельзя отозвать, если потеряна сама сессия объектных guards; требование к внешнему fencing для будущего HA сохраняется. Квота после неопределённого удаления освобождается только проверенным повтором.
Миграция 16 добавляет состояние без удаления старых данных; 17 строит индексы конкурентно. Существующий declaration inventory операций разделён по хранению без изменения старых ACL/operationId; исключение ARCH-002 устранено, новые исключения размера не добавляются.
Границы
Требуются обновление всех читателей/worker и соблюдение exclusive writer. При обнаружении старого активного процесса GC откладывается; последующий запуск старых бинарников не поддерживается. DB/session disconnect обрывает дальнейшую выдачу, не переносит TCP на другой узел. LocalBlobStore и согласованный shared root остаются прежними профилями; независимая репликация и online backup не реализуются здесь. Паузы ограничивают I/O, но не гарантируют неизменный throughput.
Приёмка
Реальные PostgreSQL/HTTP: активный download при retirement, независимые content pins, занятый/истёкший upload, grace, защищённые ссылки, ошибка удаления, crash window до reclaimed, пауза между объектами, restart и автоматический запуск. API scopes/CAS и RU/EN UI проверяются отдельными сценариями через гейты.
Offline regression: принудительное завершение maintenance backend при удерживаемых per-object guards, запуск replacement API, отказ конкурирующим reader/upload до release, сохранение bytes/quota и прекращение прохода. Unit-проверки покрывают потерю ownership после page/lock/delete и между filesystem-фазами. Все сценарии подключены общими unit/integration gates.