# Разнесение restic-операций на фазы под Intelligent Tiering - Дата: 2026-06-22 ## Контекст Бэкапы restic уже лежат в Yandex Object Storage на стандартном классе хранения (STANDARD). Yandex выпустил класс «Умное хранилище» (Intelligent Tiering, IT): объекты автоматически охлаждаются до архивного уровня (примерно 0,63 ₽/ГБ против 2,38 ₽/ГБ у STANDARD) с сохранением мгновенного доступа на всех уровнях (анонс: ). Данные 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:/// s3:/// --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`.