144 lines
12 KiB
Markdown
144 lines
12 KiB
Markdown
# Разнесение 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`.
|
||
|
||
Сверка биллинга до/после (день 22.06 → 24.06): суточный счёт почти не
|
||
изменился (74,43 ₽ → 74,50 ₽). ~1468 из ~22560 gbyte·ч (≈6,5% объёма,
|
||
это `rivendell`) ушли из STANDARD в IT, но сели на уровень **FREQUENT** с
|
||
ценой, **идентичной STANDARD** (обе строки дают 0,0033 ₽/(ГБ·ч)) —
|
||
экономии на день миграции нет, как и ожидалось. Штрафа за `TRANSITION`
|
||
в биллинге нет (открытый вопрос про min-retention закрыт эмпирически).
|
||
Появилась новая статья — почасовая тарификация количества объектов в IT
|
||
(`Количество объектов в умном хранилище`, 0,04 ₽/день), у STANDARD её не
|
||
было.
|
||
|
||
Retention оставлен прежним (`--keep-daily 90 --keep-monthly 36`) — это
|
||
решение про охлаждение и частоту операций, а не про глубину истории.
|
||
|
||
## Последствия
|
||
|
||
- `+` Ночной prune больше не сбрасывает охлаждение — IT реально экономит
|
||
на архивном уровне.
|
||
- `+` Нет наложения restic-операций: последовательные фазы + `flock`.
|
||
- `+` Расписание обслуживания меняется правкой конфига, без релиза кода.
|
||
- `-` Новая зависимость на сервере: `python3-croniter` (и явно
|
||
зафиксированный `python3-requests`).
|
||
- `-` Структурный `check` теперь еженедельный, а не каждую ночь: битый
|
||
бэкап может остаться незамеченным до недели. Для хобби-сервера приемлемо.
|
||
- `-` Подвох croniter: при суточном триггере поля минут/часов в
|
||
выражениях декоративны (держим `* *`) — фаза идёт в момент ночного
|
||
прогона, а не во время из выражения.
|
||
- `+` Миграция существующих объектов — разовая copy-in-place, без репака
|
||
restic: содержимое и ключи паков не меняются, restic остаётся рабочим.
|
||
- `+` Подтверждено биллингом: за `TRANSITION` (copy-in-place) штрафа нет,
|
||
суточный счёт после перевода `rivendell` не вырос (74,43 → 74,50 ₽).
|
||
- `-` После перевода объекты стартуют на уровне FREQUENT и охлаждаются
|
||
~120 дней — полка экономии устанавливается не сразу. Подтверждено: на
|
||
день миграции FREQUENT тарифицируется как STANDARD (0,0033 ₽/(ГБ·ч)),
|
||
экономии ещё нет — её даёт только архивный уровень (~0,63 ₽/ГБ).
|
||
- `-` IT добавляет статью, которой нет у STANDARD: почасовая плата за
|
||
количество объектов (`Количество объектов в умном хранилище`, у
|
||
`rivendell` ~0,04 ₽/день). Мелочь, но растёт с числом объектов в репо.
|
||
- Осталось сделать: дождаться, когда `rivendell` начнёт охлаждаться и
|
||
счёт по нему поедет вниз (полка через ~120 дней), затем повторить пару
|
||
«флип дефолта + copy-in-place» для `eos` (основная экономия) и
|
||
`buckland`.
|