Learning Platform
Глоссарий Troubleshooting
Урок 14.04 · 22 мин
Средний
log rotationstreaming compressionsplitbackup pipelinesDE workflows

Архивы в DE-workflow: log rotation, streaming backups, split

Финальный урок модуля. Сборник production-готовых паттернов: rotation старых логов, streaming compression БД-дампов, разрезание больших архивов на части, integration с rsync для backup pipelines.


Pattern 1: Архивирование старых логов

DE-сервер копит логи Airflow, Postgres, Nginx. Если их не ротировать — диск кончается. Стандартный подход: ежедневный cron-job, который архивирует логи старше N дней и удаляет оригиналы.

#!/bin/bash
# rotate-logs.sh — ежедневная архивация логов старше 7 дней

set -euo pipefail

LOG_DIR="/var/log/airflow"
ARCHIVE_DIR="/var/log/archive"
DAYS=7
DATE=$(date +%Y-%m-%d)

mkdir -p "$ARCHIVE_DIR"

# Найти .log файлы старше 7 дней
OLD_LOGS=$(find "$LOG_DIR" -name '*.log' -mtime +$DAYS)

if [ -z "$OLD_LOGS" ]; then
  echo "No old logs to archive"
  exit 0
fi

# Архивировать в один tar.zst
echo "$OLD_LOGS" | tar --zstd -czvf "$ARCHIVE_DIR/airflow-logs-$DATE.tar.zst" -T -

# После успешного создания архива — удалить оригиналы
if [ -f "$ARCHIVE_DIR/airflow-logs-$DATE.tar.zst" ]; then
  echo "$OLD_LOGS" | xargs rm -f
  echo "Archived $(echo "$OLD_LOGS" | wc -l) files to $ARCHIVE_DIR/airflow-logs-$DATE.tar.zst"
fi

# Удалить архивы старше 90 дней
find "$ARCHIVE_DIR" -name 'airflow-logs-*.tar.zst' -mtime +90 -delete

-T - — список файлов из stdin. find ... | tar -T - — известный pattern.

Запуск через cron (модуль 16):

# /etc/cron.d/airflow-log-rotation
0 3 * * * root /opt/scripts/rotate-logs.sh > /var/log/log-rotation.log 2>&1

В реальности на production обычно используют logrotate — system utility для этого (модуль 15 покажет). Но понимать механику через tar — must.


Pattern 2: Streaming compression БД-дампов

Постгресовый dump 100GB БД в файл — нужно 100GB места для temporary, потом сжатие, потом удаление. Streaming compression — обходимся без temp:

# Простой dump + zstd
pg_dump --format=plain dbname | zstd -3 > /backup/dbname-$(date +%F).sql.zst

# С verbose progress (pv — pipe viewer)
pg_dump --format=plain dbname | pv | zstd -3 > /backup/dbname-$(date +%F).sql.zst

# Или
pg_dump --format=plain dbname | pv -p -t -e -r -s 100G | zstd -3 > backup.sql.zst
# -p прогресс, -t время, -e ETA, -r скорость, -s ожидаемый размер

Восстановление:

# Decompress + restore в один шаг
zstdcat /backup/dbname-2026-05-13.sql.zst | psql dbname

# Или
zstd -d -c /backup/dbname-2026-05-13.sql.zst | psql dbname

Без temp файла. Stream идёт прямо из pg_dump через zstd на диск (или через сеть). Это позволяет работать с БД больше доступного disk space.

Custom format для большой БД

# pg_dump --format=custom — бинарный, уже compressed
pg_dump --format=custom -Z 6 dbname > /backup/dbname-$(date +%F).dump
# -Z 6 — gzip compression level 6 (по умолчанию)

# Restore
pg_restore -d dbname /backup/dbname-2026-05-13.dump

# Параллельное восстановление (быстрее)
pg_restore -d dbname -j 4 /backup/dbname-2026-05-13.dump

Custom format уже сжат, не нужно дополнительно gzip. Также поддерживает selective restore (только конкретные таблицы).


Pattern 3: split — разрезание больших файлов

Иногда нужно разрезать огромный архив на части — для передачи через email (limit 20MB), для upload на cloud storage с size limit, для parallel download.

# Разрезать на куски по 100MB
split -b 100M huge-archive.tar.gz part_

# Результат: part_aa, part_ab, part_ac, ...

# С numeric suffix вместо alphabetic
split -b 100M -d huge-archive.tar.gz part_

# Результат: part_00, part_01, part_02, ...

# С suffix length 3 цифры
split -b 100M -d -a 3 huge-archive.tar.gz part_

# С дополнительным suffix
split -b 100M --additional-suffix=.bin huge-archive.tar.gz part_
# part_aa.bin, part_ab.bin, ...

Воссоединение — просто cat:

# Собрать обратно
cat part_* > huge-archive.tar.gz

# Проверить, что результат идентичен оригиналу
md5sum original.tar.gz huge-archive.tar.gz
# Хэши должны совпадать

Split по строкам

# Разрезать CSV на куски по 1M строк
split -l 1000000 huge.csv chunk_
# chunk_aa, chunk_ab, ...

# Сохранить header в каждом куске (стандартный split этого не умеет)
HEADER=$(head -1 huge.csv)
tail -n +2 huge.csv | split -l 1000000 - chunk_
for f in chunk_*; do
  (echo "$HEADER"; cat "$f") > "${f}.csv"
done

DE use case: обрабатывать огромный CSV параллельно (Spark, parallel awk).


Docker volumes и bind mounts: архивы и persistence

Pattern 4: tar + rsync для backup pipelines

Комбинировать архивирование с network transfer:

# Сразу streaming на remote — без temp файла
tar czf - /var/data | ssh backup-server "cat > /backups/data-$(date +%F).tar.gz"

# То же через rsync (с прогрессом)
tar czf - /var/data | pv | ssh backup-server "cat > /backups/data-$(date +%F).tar.gz"

# Альтернатива — tar + rsync для incremental
tar czf /tmp/data.tar.gz /var/data
rsync -avhP /tmp/data.tar.gz backup-server:/backups/
rm /tmp/data.tar.gz

Streaming через ssh быстрее для one-shot. rsync лучше для incremental (если только часть архива изменилась — но изменение в архиве обычно меняет весь файл, так что incremental не помогает для архивов).

Backup pipeline с rotation

#!/bin/bash
# nightly-backup.sh

set -euo pipefail

SRC="/var/data/lake"
BACKUP_HOST="[email protected]"
BACKUP_DIR="/backups/lake"
DATE=$(date +%Y-%m-%d)
LOCAL_TMP="/tmp/backup-$DATE.tar.zst"

# Создать архив локально
tar --zstd -cf "$LOCAL_TMP" "$SRC"

# Передать на NAS
rsync -avhP "$LOCAL_TMP" "$BACKUP_HOST:$BACKUP_DIR/"

# Удалить local tmp
rm "$LOCAL_TMP"

# Rotate на NAS — хранить только последние 30
ssh "$BACKUP_HOST" "ls -t $BACKUP_DIR/*.tar.zst | tail -n +31 | xargs rm -f"

# Notification
curl -s -X POST -H 'Content-type: application/json' \
  --data "{\"text\":\"Backup $DATE complete\"}" \
  "$SLACK_WEBHOOK"

Direct streaming (без local tmp)

Если local disk space ограничен:

tar --zstd -cf - "$SRC" | ssh "$BACKUP_HOST" "cat > $BACKUP_DIR/data-$DATE.tar.zst"

-cf - означает «write to stdout». Полезно когда вы тестируете backup pipeline на маленьком сервере.


Pattern 5: Параллельная обработка ZIP-файлов

Получили 100 partner-data ZIP файлов, нужно распаковать все:

# Последовательно (медленно)
for z in *.zip; do
  unzip "$z" -d /tmp/extracted/
done

# Параллельно с xargs
ls *.zip | xargs -P 8 -I {} unzip -q -o {} -d /tmp/extracted/
# -P 8 — 8 параллельных процессов
# -q — quiet (не спамить)
# -o — overwrite без вопросов

# Через GNU parallel (если установлен)
parallel -j 8 unzip -q -o {} -d /tmp/extracted/ ::: *.zip

DE-сценарий: monthly data dumps от партнёра в виде 30 daily ZIP файлов. Распаковать все за минуты, не часы.


Pattern 6: Verify integrity

Архивы могут портиться при передаче. Стандартная практика — checksums:

# Создать архив + checksum
tar czvf backup.tar.gz /data
sha256sum backup.tar.gz > backup.tar.gz.sha256

# На receiving стороне
sha256sum -c backup.tar.gz.sha256
# Должно вывести: backup.tar.gz: OK

# Если что-то изменилось — FAILED

Bash-функция для backup с verification:

backup_with_verify() {
  local src="$1"
  local dst="$2"

  tar czf "$dst" "$src"
  local checksum=$(sha256sum "$dst" | awk '{print $1}')
  echo "$checksum  $dst" > "$dst.sha256"

  # Verify
  if sha256sum -c "$dst.sha256" > /dev/null; then
    echo "OK: $dst (sha256: $checksum)"
  else
    echo "FAIL: $dst checksum mismatch!" >&2
    return 1
  fi
}

backup_with_verify /home/levo/data /backup/data-$(date +%F).tar.gz

Pattern 7: Selective restore из tar

Иногда нужно достать один файл из huge tar:

# Список файлов
tar tvf big-archive.tar.gz | head -30

# Извлечь конкретный
tar xvf big-archive.tar.gz path/to/specific-file.csv

# Извлечь по паттерну
tar xvf big-archive.tar.gz --wildcards '*.csv'

# Только в stdout (не сохранять)
tar xOf big-archive.tar.gz path/to/file.csv | head
# -O — extract to stdout

DE use case: бэкап на 100GB, нужно достать конкретный CSV — не нужно распаковывать всё.


Pattern 8: Incremental backup с tar

tar сам по себе не делает incremental — он копирует всё. Для incremental используют --listed-incremental:

# Full backup (level 0)
tar --listed-incremental=/var/backup/snapshot.snar \
  -czf /backup/full.tar.gz /data

# Incremental (level 1) — только изменения с last full
tar --listed-incremental=/var/backup/snapshot.snar \
  -czf /backup/inc-$(date +%F).tar.gz /data

Файл snapshot.snar — это база, которая запоминает inode и timestamps. На основе её tar определяет, что изменилось.

В реальности — это сложно в обращении (restore требует full + все incrementals по порядку). Большинство DE предпочитают rsync с --link-dest (модуль 12 урок 03) или specialized tools (BorgBackup, restic).


Pattern 9: Encrypted backup

Backup с шифрованием для compliance (GDPR, HIPAA):

# tar + gpg
tar czf - /sensitive/data | gpg -c -o backup.tar.gz.gpg
# -c symmetric encryption (запросит пароль)

# Восстановление
gpg -d backup.tar.gz.gpg | tar xzf -

# С key вместо password (production-ready)
gpg --import recipient-public.key
tar czf - /sensitive/data | gpg -e -r recipient@email > backup.tar.gz.gpg

# Decrypt
gpg -d backup.tar.gz.gpg | tar xzf -

GPG (GnuPG) — стандарт криптографии для UNIX. Альтернатива — age (современнее, проще API).

# age — современная альтернатива
sudo apt install age
# или
brew install age

# Сгенерировать пару ключей
age-keygen -o key.txt

# Encrypt
tar czf - /data | age -r $(grep public key.txt | awk '{print $4}') > backup.tar.gz.age

# Decrypt
age -d -i key.txt backup.tar.gz.age | tar xzf -

Целостный DE-backup пример

Финальный production-готовый скрипт:

#!/bin/bash
# production-backup.sh

set -euo pipefail

SRC="/var/data"
BACKUP_HOST="[email protected]"
BACKUP_DIR="/backups/data"
DATE=$(date +%Y-%m-%d)
SLACK_WEBHOOK="${SLACK_WEBHOOK_URL:?Set SLACK_WEBHOOK_URL}"
RETENTION_DAYS=30

# Trap для cleanup и notify on failure
trap 'curl -s -X POST -H "Content-type: application/json" --data "{\"text\":\"BACKUP FAILED $DATE on $(hostname)\"}" "$SLACK_WEBHOOK"' ERR

echo "[$(date -Iseconds)] Starting backup for $DATE"

# 1. Создать и compression на лету, прямо на NAS
tar --zstd -cf - "$SRC" | \
  ssh "$BACKUP_HOST" "cat > $BACKUP_DIR/data-$DATE.tar.zst"

# 2. Verify checksum
EXPECTED=$(tar --zstd -cf - "$SRC" | sha256sum | awk '{print $1}')
ACTUAL=$(ssh "$BACKUP_HOST" "sha256sum $BACKUP_DIR/data-$DATE.tar.zst" | awk '{print $1}')

if [ "$EXPECTED" != "$ACTUAL" ]; then
  echo "Checksum mismatch! Expected $EXPECTED, got $ACTUAL" >&2
  exit 1
fi

# 3. Rotation
ssh "$BACKUP_HOST" "find $BACKUP_DIR -name 'data-*.tar.zst' -mtime +$RETENTION_DAYS -delete"

# 4. Notify success
SIZE=$(ssh "$BACKUP_HOST" "du -h $BACKUP_DIR/data-$DATE.tar.zst | awk '{print \$1}'")
curl -s -X POST -H "Content-type: application/json" \
  --data "{\"text\":\"Backup $DATE: $SIZE on $BACKUP_HOST\"}" \
  "$SLACK_WEBHOOK"

echo "[$(date -Iseconds)] Backup complete: $SIZE"

Все patterns в одном: streaming, verification, rotation, error handling, notifications.


Попробуй сам

# 1. Streaming compression
echo "Generating test data..."
seq 1 1000000 > /tmp/test-data.txt

ls -lh /tmp/test-data.txt

# Streaming через pipe (без temp)
cat /tmp/test-data.txt | gzip > /tmp/test-data.txt.gz
ls -lh /tmp/test-data.txt.gz

# 2. Split
split -b 1M /tmp/test-data.txt /tmp/chunk_
ls /tmp/chunk_*

# Reassemble
cat /tmp/chunk_* > /tmp/reassembled.txt
md5sum /tmp/test-data.txt /tmp/reassembled.txt
# Хэши совпадают

# 3. Selective extract
mkdir /tmp/dir-for-tar
echo "a" > /tmp/dir-for-tar/file1.txt
echo "b" > /tmp/dir-for-tar/file2.txt
echo "c" > /tmp/dir-for-tar/file3.txt

tar czvf /tmp/multi.tar.gz /tmp/dir-for-tar
tar tvf /tmp/multi.tar.gz

# Only file2
tar xOvf /tmp/multi.tar.gz tmp/dir-for-tar/file2.txt

# 4. Checksum verification
tar czf /tmp/data.tar.gz /tmp/dir-for-tar
sha256sum /tmp/data.tar.gz > /tmp/data.tar.gz.sha256

# Verify
sha256sum -c /tmp/data.tar.gz.sha256
# Modify
echo "tampered" >> /tmp/data.tar.gz
sha256sum -c /tmp/data.tar.gz.sha256 || echo "Tampering detected!"

# 5. Cleanup
rm -rf /tmp/test-data.txt /tmp/test-data.txt.gz /tmp/chunk_* /tmp/multi.tar.gz /tmp/dir-for-tar /tmp/reassembled.txt /tmp/data.tar.gz /tmp/data.tar.gz.sha256

Cross-link: предыдущий урок 03 — zip. Модуль 11 — rsync, который часто комбинируется с tar для backup. Модуль 15 — cron для регулярных backup-задач. Модуль 17 — production-bash с error handling.


Проверка знанийKnowledge check
Senior DE: «У нас Postgres БД 200GB. Каждую ночь нужен backup, но на сервере свободно только 50GB. Как сделать?»
ОтветAnswer
Решение: streaming compression без temp файла. pg_dump dbname | zstd -3 | ssh backup-host "cat > /backups/dbname-$(date +%F).sql.zst". Что происходит: 1) pg_dump читает БД и выводит SQL в stdout (стрим, не temp файл). 2) zstd -3 сжимает поток на лету (CPU-эффективно). 3) ssh передаёт сжатый поток на backup-сервер. 4) cat записывает в файл на backup-сервере. На local-сервере НИКОГДА не накапливается даже 1GB временных данных — всё streaming. Дополнительные оптимизации: 1) Параллельный dump (pg_dump --jobs=4 -F d -f /tmp/dumpdir/) — но это создаёт temp directory, не подходит для нашего случая. 2) pg_dump --format=custom -Z 6 (бинарный, уже сжатый) -> размер ~50-70GB сжатого, тоже streaming. 3) Проверка checksum после: shasum локально (на pg_dump через pipe — параллельно zstd через tee) и на backup-сервере. 4) Restoration: ssh backup-host 'zstdcat /backups/dbname-DATE.sql.zst' | psql dbname. 5) Production добавки: trap для error notification, rotation старых backups, slack notify. 6) Альтернатива — pg_basebackup для физического backup (быстрее восстановление, но требует больше места). Streaming через pipe + ssh — стандарт DE для backup БД больше доступного локального места.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Что делает команда `find /var/log/airflow -name '*.log' -mtime +7 -print0 | tar --null -czvf logs.tar.gz -T -`?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4