ADR 0152 — Резервное копирование и восстановление томов для compose
Статус
Proposed. Существует потому, что FASE 29 должна мигрировать предыдущую установку, а путь, на который для этого указывала документация, не годится.
Контекст
Текущее резервное копирование покрывает одну базу и работает в одном месте:
docker/backup/backup.shделаетpg_dump --format=custom | aws s3 cp -(:124-140), прямо в S3 без касания диска, с хранением по количеству (:160-182) и строкой вbackup_runsпри каждом выполнении, успешном или нет (trap registrar EXIT,:44-80).docker/backup/restore.shвосстанавливает в тестовую базу (brabo_restore_test,:18), проверяет три вещи — список таблиц, количество критичных в окне резервной копии и плотную непрерывностьseqsession_events(:139-224) — и никогда не трогает источник (:8-9).make test-restore→deploy/k8s/test-restore.sh, который требуетkubectl, кластер и CronJobbrabo-backupв namespace (:56-58).
Три последствия, все измерены:
- У миграции установки через compose нет пути.
test-restore— это Kubernetes; эквивалентной цели для compose не существует, а вdocker-compose.prod.ymlнет сервисаbackup(образ собирается bake и используется только в k8s). - Резервная копия покрывает только Postgres. Ничто в
docker/backup/не касаетсяneo4j_data,git_local_repos,project_workspaces,ollama_dataилиbrabo_projects_base(docker-compose.prod.yml:474-486). - Она также требует S3 — пять обязательных переменных (
backup.sh:11-15). В установке на одной машине требовать bucket, чтобы иметь возможность мигрировать, — значит требовать инфраструктуру, о которой установщик только что сказал, что она не нужна.
Решение
1. Классифицировать тома, прежде чем их копировать
Лёгкая ошибка — "сделать резервную копию всех томов". Семь томов — не одно и то же, и одинаковое обращение с ними стоит места и скрывает важное:
| том | природа | решение |
|---|---|---|
pgdata | источник истины — event log, действия, всё | уже покрыт pg_dump; остаётся логическим дампом, а не копией файлов |
git_local_repos | источник истины — bare repos LocalGitProvider | входит, и это находка этого ADR |
neo4j_data | производный — граф является памятью, спроецированной из event log (ADR 0099), никогда не источником истины | не входит: не хватает перепроецирования, а не резервной копии |
project_workspaces | производный — worktrees, которые WorktreeManager пересоздаёт из bare | не входит |
ollama_data | повторно получаемый — модели, которые скачиваются заново | не входит |
brabo_projects_base | пользователя, а не продукта | не входит, и установщик никогда её не удаляет (ADR 0150) |
git_local_repos — реальная дыра. У проекта с provider local там лежит
bare-репозиторий; потеря этого тома — потеря кода, произведённого
агентами, и сегодня ни одна резервная копия его не покрывает. Он не восстанавливается
из Postgres — event log хранит повествование, а не объекты git.
2. Локальное назначение, S3 необязательно
Резервное копирование начинает принимать назначение на диске помимо S3. В установке на одной машине каталог — честное назначение; требовать bucket для миграции значило бы требовать инфраструктуру, которую установщик не просит.
Пять переменных S3 перестают быть обязательными и становятся тем, чем на практике уже были: конфигурацией одного из назначений.
3. Путь восстановления, работающий для compose
Рождается эквивалент test-restore без кластера: поднимает образ резервного копирования
через docker compose run, восстанавливает в тестовую базу и выполняет те же три
проверки из restore.sh:139-224. Никакой новой логики проверки — меняется
оболочка, а не суждение.
deploy/k8s/test-restore.sh остаётся нетронутым: это два окружения с двумя
оболочками, и их объединение заставило бы compose-вариант зависеть от kubectl.
4. Перепроецирование графа — ответ для Neo4j
Восстановление neo4j_data из резервной копии восстановило бы, возможно, старую проекцию
рядом с Postgres, восстановленным на другой момент, — два производных состояния
разных моментов, без ничего, что их согласует. Правильный ответ для
производной памяти — перепроецировать из источника.
Эта фаза не строит перепроецирование: она объявляет, что это путь и что до его появления мигрированная установка рождается с пустым графом — что является известной деградацией (RAG остаётся в pgvector, который живёт в Postgres и приходит в дампе), а не потерей данных.
5. Установщик использует это и не гадает
Путь миграции ADR 0150: резервная копия → подтверждение → удаление → установка → восстановление, с восстановлением, доказанным решением 3. Без хорошей резервной копии установщик не переходит к удалению.
Что этот ADR явно отвергает
- Копировать все тома. По решению 1: три из семи производные или повторно получаемые, и их копирование создаёт впечатление покрытия, не добавляя восстановимости.
- Копию файлов
pgdata. Копирование каталога данных работающего Postgres даёт резервную копию, которая может не восстановиться; логический дамп остаётся. - Восстанавливать
neo4j_data. По решению 4. - Выполнять восстановление в исходную базу.
restore.shникогда не трогает источник (:8-9), и это свойство не обсуждается ради удобства миграции. - Объединять с
deploy/k8s/test-restore.sh.
Последствия
- Миграция существующей установки получает доказанный путь, а установщик получает право удалять тома — потому что доказал, что может вернуть их обратно.
git_local_reposвходит в резервную копию, и это разница, которую этот ADR вносит вне области установки: сегодня любая потеря этого тома — потеря кода, в любом окружении, и никто это не зафиксировал.- Мигрированная установка рождается с пустым графом, пока не появится перепроецирование. Объявлено, с названным эффектом: чтения, зависящие от графа, деградируют; RAG не затрагивается.
- Резервное копирование получает второе назначение, а с ним второй путь сбоя —
заполненный диск. Строка в
backup_runsпродолжает записываться в обоих случаях уже существующимtrap.