Backups: split restic operations into phases for Intelligent Tiering
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
# Разнесение 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 / штраф за раннее удаление архивного
|
||||
уровня внутри 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`) тем же механизмом, что и
|
||||
остальное, а гибкость реальная — поменять «раз в квартал» на «раз в
|
||||
месяц» = правка одной строки конфига.
|
||||
- **Storage class сейчас: Вариант A** (`-o s3.storage-class=INTELLIGENT_TIERING`
|
||||
в restic) **vs Вариант B** (lifecycle-правило / copy-in-place на бакете)
|
||||
**vs отложить.** A влияет только на новые объекты — уже лежащие бэкапы в
|
||||
STANDARD так и останутся; B переводит существующие данные, но требует
|
||||
завести управление бакетом, которого в проекте нет. **Выбрано отложить**
|
||||
до подтверждения min-retention архивного уровня IT.
|
||||
|
||||
## Решение
|
||||
|
||||
Операции 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`):
|
||||
чем меньше холодных паков переписываем, тем дольше держится охлаждение.
|
||||
|
||||
Storage class IT и lifecycle на бакете **намеренно отложены**: пока не
|
||||
подтверждён min-retention архивного уровня IT, transition существующих
|
||||
данных рискован (возможен штраф за раннее удаление при последующем prune).
|
||||
Сама миграция уже лежащих бэкапов делается lifecycle-правилом или
|
||||
copy-in-place с `--storage-class INTELLIGENT_TIERING`, а не сменой
|
||||
дефолтного класса бакета (та сработает только для новых объектов).
|
||||
Retention оставлен прежним (`--keep-daily 90 --keep-monthly 36`) — это
|
||||
решение про охлаждение и частоту операций, а не про глубину истории.
|
||||
|
||||
## Последствия
|
||||
|
||||
- `+` Ночной prune больше не сбрасывает охлаждение — IT реально экономит
|
||||
на архивном уровне.
|
||||
- `+` Нет наложения restic-операций: последовательные фазы + `flock`.
|
||||
- `+` Расписание обслуживания меняется правкой конфига, без релиза кода.
|
||||
- `-` Новая зависимость на сервере: `python3-croniter` (и явно
|
||||
зафиксированный `python3-requests`).
|
||||
- `-` Структурный `check` теперь еженедельный, а не каждую ночь: битый
|
||||
бэкап может остаться незамеченным до недели. Для хобби-сервера приемлемо.
|
||||
- `-` Подвох croniter: при суточном триггере поля минут/часов в
|
||||
выражениях декоративны (держим `* *`) — фаза идёт в момент ночного
|
||||
прогона, а не во время из выражения.
|
||||
- Осталось сделать: подтвердить min-retention / штраф за раннее удаление
|
||||
архивного уровня IT, затем перевести существующие бэкапы со STANDARD на
|
||||
`INTELLIGENT_TIERING` через lifecycle-правило или copy-in-place.
|
||||
Reference in New Issue
Block a user