Files
pet-project-server/docs/adr/ADR-2026-06-22-restic-intelligent-tiering-phases.md
T
av 2930842e3f
Linting / YAML Lint (push) Waiting to run
Linting / Ansible Lint (push) Waiting to run
Backups: update ADR after migration
2026-06-23 09:38:09 +03:00

10 KiB

Разнесение restic-операций на фазы под Intelligent Tiering

  • Дата: 2026-06-22

Контекст

Бэкапы restic уже лежат в Yandex Object Storage на стандартном классе хранения (STANDARD). Yandex выпустил класс «Умное хранилище» (Intelligent Tiering, IT): объекты автоматически охлаждаются до архивного уровня (примерно 0,63 ₽/ГБ против 2,38 ₽/ГБ у STANDARD) с сохранением мгновенного доступа на всех уровнях (анонс: https://yandex.cloud/ru/blog/s3-intelligent-tiering). Данные restic — это профиль «записал один раз, читаю редко», то есть идеальный кандидат на охлаждение. Цель — перевести уже лежащие бэкапы со STANDARD на IT и сэкономить на хранении.

Проблема в том, что любой repack/recompress объекта создаёт новый объект, который входит в IT как «Частый доступ» и заново стартует таймер охлаждения (30 дней → «Нечастый», ещё 90 → «Архивный»). А наш оркестратор backup-all.py гнал каждую ночь связку backup → check → forget --prune → check. Ночной prune перепаковывает data-паки → постоянно сбрасывает охлаждение → отменяет экономию IT. Нужно было перестроить обслуживание так, чтобы не мешать охлаждению.

Дополнительное ограничение по миграции существующих данных: по докам Yandex изменение класса бакета по умолчанию не трогает уже загруженные объекты — они остаются в STANDARD, новый класс применяется только к новым загрузкам. Перевести уже лежащие бэкапы в IT можно lifecycle-правилом или copy-in-place (aws s3 cp --storage-class INTELLIGENT_TIERING); оба тарифицируются как операция TRANSITION. Перезаливка объектов заново для restic не годится — это churn, эквивалентный репаку. Управления бакетом (terraform/aws-cli) в проекте нет, так что миграцию пришлось бы делать вручную или заводить такое управление.

Идентификатор класса подтверждён доками — INTELLIGENT_TIERING (Yandex поддерживает STANDARD, COLD, ICE, INTELLIGENT_TIERING). Явный min-retention (12 месяцев со штрафом за раннее удаление) документирован только для класса ICE; для архивного уровня внутри IT такого минимума в доках нет — значит transition и последующий prune для IT низкорисковы. Финальная проверка — по биллингу после первой реальной миграции.

Рассмотренные варианты

  • Отдельные скрипты на репозиторий (backup.sh/check.sh/prune.sh/ verify.sh + свои cron-записи, как в исходном гайде). Ближе к гайду дословно, но теряем оркестратор: авто-дискавери приложений, мультистор и единые apprise-уведомления. Плюс 4 независимые cron-записи провоцируют наложение операций (долгий prune налезает на ночной backup → конфликт restic-локов). Отвергнут.
  • Адаптировать backup-all.py — добавить фазы и расписание внутрь оркестратора, один ночной триггер. Сохраняет всю существующую инфраструктуру, фазы идут последовательно в одном процессе → локи не конфликтуют. Выбран.
  • Расписание: простые knobs (день недели/число/месяцы как поля конфига) vs cron-выражения через croniter. Knobs — без зависимости, но негибко (новая ось → правка кода). Выбран croniter: пакет ставится из apt (python3-croniter) тем же механизмом, что и остальное, а гибкость реальная — поменять «раз в квартал» на «раз в месяц» = правка одной строки конфига.
  • Перевод существующих данных в IT: copy-in-place vs lifecycle vs отложить. Lifecycle в Yandex переводит только «на более холодный» (STANDARD→COLD→ICE), переход именно в IT им не заявлен — отпал. Перезаливка объектов для restic не годится (churn ≈ репак). Остаётся copy-in-place (aws s3 cp --recursive --storage-class INTELLIGENT_TIERING, серверная копия «на себя»). Выбран copy-in-place — после того как доки сняли блокер по min-retention. Для будущих записей отдельно — флип класса бакета по умолчанию на IT (вариант A с -o s3.storage-class в коде не понадобился).

Решение

Операции restic в files/backups/backup-all.py разнесены на фазы с разной частотой, потому что у них принципиально разная цена для IT:

  • backup + forgetкаждый прогон. forget теперь без --prune: удаляет только метаданные снапшотов (операция DELETE не тарифицируется), не репакует data-паки и не сбивает охлаждение.
  • check (структурный) — еженедельно; prune — квартально; verify (check --read-data-subset) — помесячно. Расписание задано cron-выражениями в секции [schedule] конфига и вычисляется через croniter. Триггер один ночной, фазы одного прогона идут последовательно в одном процессе → restic-локи между ними не конфликтуют. Наложение соседних прогонов гасится flock -n в cron.

prune тюнингован под IT (--max-unused 20%, --max-repack-size 5G): чем меньше холодных паков переписываем, тем дольше держится охлаждение.

Перевод бакетов в IT идёт двумя действиями на каждый бакет: смена класса по умолчанию на IT в консоли (будущие записи restic) + разовая copy-in-place существующих объектов (aws s3 cp s3://<bucket>/ s3://<bucket>/ --recursive --storage-class INTELLIGENT_TIERING --metadata-directive COPY). Класс отдаётся прямо в листинге (list-objects-v2 --query 'Contents[].[StorageClass,Key]') — им и проверяем. Грабли: для проверки нельзя --max-items 1 (клиентская пагинация aws-cli дописывает в вывод токен None) — нужен серверный --max-keys.

Статус миграции: rivendell переведён 2026-06-23 (дефолт бакета = IT со скриншота, все объекты config/data/ показывают INTELLIGENT_TIERING). eos (основная экономия) и buckland — следующими, после нескольких дней наблюдения за биллингом rivendell.

Retention оставлен прежним (--keep-daily 90 --keep-monthly 36) — это решение про охлаждение и частоту операций, а не про глубину истории.

Последствия

  • + Ночной prune больше не сбрасывает охлаждение — IT реально экономит на архивном уровне.
  • + Нет наложения restic-операций: последовательные фазы + flock.
  • + Расписание обслуживания меняется правкой конфига, без релиза кода.
  • - Новая зависимость на сервере: python3-croniter (и явно зафиксированный python3-requests).
  • - Структурный check теперь еженедельный, а не каждую ночь: битый бэкап может остаться незамеченным до недели. Для хобби-сервера приемлемо.
  • - Подвох croniter: при суточном триггере поля минут/часов в выражениях декоративны (держим * *) — фаза идёт в момент ночного прогона, а не во время из выражения.
  • + Миграция существующих объектов — разовая copy-in-place, без репака restic: содержимое и ключи паков не меняются, restic остаётся рабочим.
  • - После перевода объекты стартуют на уровне FREQUENT и охлаждаются ~120 дней — полка экономии устанавливается не сразу.
  • Осталось сделать: несколько дней последить за биллингом и бэкапами rivendell (убедиться, что за transition нет штрафа), затем повторить пару «флип дефолта + copy-in-place» для eos и buckland.