MAATRIX / Блог / Percona XtraBackup в Docker Compose: готовый файл

Percona XtraBackup в Docker Compose: готовый файл

MAATRIX

mysqldump на боевой базе на 20-30 ГБ означает минуты простоя под блокировкой и файл бэкапа, который распаковывается ещё дольше, чем снимался. Percona XtraBackup решает эту задачу иначе: копирует файлы InnoDB напрямую, без LOCK TABLES и без остановки записи, и восстанавливается на порядок быстрее, потому что не нужно заново проигрывать SQL построчно. Ниже — рабочий docker-compose.yml, скрипты для полного и инкрементального бэкапа и разбор граблей, на которые обычно наступают в первый день.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Чем XtraBackup лучше mysqldump

mysqldump выгружает данные в SQL-команды: INSERT на каждую строку (или пачками, если включён --extended-insert). Это универсально, но медленно на больших таблицах — и на бэкап, и на восстановление, потому что серверу приходится заново строить индексы и проверять целостность на лету.

XtraBackup работает на уровне файлов:

  • копирует .ibd-файлы InnoDB напрямую с диска, пока сервер продолжает принимать запросы на чтение и запись;
  • параллельно вычитывает redo-log, чтобы «дозаписать» в копию транзакции, прошедшие во время снятия бэкапа;
  • в конце ставит короткую блокировку (FLUSH TABLES WITH READ LOCK или, начиная с 8.0, backup lock) на доли секунды — только чтобы зафиксировать точку согласованности для не-InnoDB объектов (например, .frm, MyISAM-таблиц, если они остались).

Итог: бэкап снимается без блокировки записи вообще, а восстановление — это copy-back файлов плюс запуск MySQL, а не часы проигрывания INSERT-ов. Для MariaDB прямого аналога через сам XtraBackup нет — форк называется mariabackup, идёт в комплекте с MariaDB Server, синтаксис почти идентичен (--backup, --prepare, --copy-back совпадают). Дальше под MySQL/Percona Server имеется в виду percona-xtrabackup, под MariaDB — mariabackup; смешивать их между СУБД нельзя, форматы несовместимы.

Важный нюанс: XtraBackup читает файлы датадиры напрямую с диска, а не через сетевой протокол MySQL. Контейнеру с бэкапом нужен доступ к тому же volume, где лежат данные — сетевого подключения к серверу недостаточно, оно нужно только для короткой команды блокировки.

Готовый docker-compose.yml

Схема: сервис mysql хранит данные в именованном volume, сервис xtrabackup монтирует тот же volume в режиме чтения и держится «вхолостую» (tail -f /dev/null), чтобы бэкапы можно было запускать через docker exec по расписанию.

version: "3.8"

services:
  mysql:
    image: mysql:8.0
    container_name: mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "127.0.0.1:3306:3306"
    networks:
      - dbnet
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5

  xtrabackup:
    image: percona/percona-xtrabackup:8.0
    container_name: xtrabackup
    user: "999:999"
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: backup
      MYSQL_PASSWORD: ${BACKUP_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql:ro
      - ./backups:/backups
      - ./scripts:/scripts:ro
    entrypoint: ["/bin/sh", "-c", "tail -f /dev/null"]
    networks:
      - dbnet

volumes:
  mysql_data:

networks:
  dbnet:

user: "999:999" — это UID/GID пользователя mysql в официальном образе mysql:8.0 (можно проверить командой docker exec mysql id mysql). Без совпадения UID контейнер xtrabackup не сможет читать файлы датадиры даже в режиме :ro — это первая грабля, на которой спотыкаются почти все.

Отдельный пользователь MySQL для бэкапов создаётся с минимально нужным набором прав:

CREATE USER 'backup'@'%' IDENTIFIED BY 'СложныйПароль';
GRANT RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT, BACKUP_ADMIN,
      CREATE TABLESPACE ON *.* TO 'backup'@'%';
FLUSH PRIVILEGES;

BACKUP_ADMIN появился в MySQL 8.0 — без него XtraBackup 8.x не сможет взять backup lock и упадёт с ошибкой доступа на самом первом шаге. Для MariaDB вместо BACKUP_ADMIN нужен RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT — прав достаточно, отдельного backup-привилегии там нет.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Полный и инкрементальный бэкап

Полный бэкап — скрипт scripts/full-backup.sh, который запускается внутри контейнера xtrabackup:

#!/bin/sh
set -e
TS=$(date +%Y%m%d_%H%M%S)
TARGET="/backups/full_${TS}"
mkdir -p "$TARGET"

xtrabackup --backup \
  --host="$MYSQL_HOST" \
  --user="$MYSQL_USER" \
  --password="$MYSQL_PASSWORD" \
  --datadir=/var/lib/mysql \
  --target-dir="$TARGET" \
  --parallel=4

echo "$TARGET" > /backups/.last_full

Запуск: docker exec xtrabackup sh /scripts/full-backup.sh. На выходе — каталог с копией .ibd-файлов, redo-log-ом и файлом xtrabackup_checkpoints, по которому XtraBackup понимает, от какой точки LSN (log sequence number) можно строить следующий инкремент.

Инкрементальный бэкап снимает только страницы, изменившиеся с прошлого раза — на активной базе это в разы быстрее и легче полного:

#!/bin/sh
set -e
BASE=$(cat /backups/.last_full)
TS=$(date +%Y%m%d_%H%M%S)
TARGET="/backups/inc_${TS}"
mkdir -p "$TARGET"

xtrabackup --backup \
  --host="$MYSQL_HOST" \
  --user="$MYSQL_USER" \
  --password="$MYSQL_PASSWORD" \
  --datadir=/var/lib/mysql \
  --target-dir="$TARGET" \
  --incremental-basedir="$BASE"

Практичная схема для среднего проекта: полный бэкап раз в неделю (ночью в воскресенье), инкременты — каждые 6 часов между ними. Цепочка инкрементов не может расти бесконечно: чем она длиннее, тем дольше prepare при восстановлении и тем выше риск потерять всё восстановление из-за одного повреждённого звена. Больше 15-20 инкрементов на один full — сигнал сделать новый базовый бэкап.

Восстановление: prepare и copy-back

XtraBackup снимает данные в «сыром», не согласованном виде — незакоммиченные транзакции ещё не откачены, закоммиченные из redo-log ещё не применены к файлам. Перед восстановлением копию нужно «подготовить» — прогнать --prepare.

Для одного полного бэкапа без инкрементов:

xtrabackup --prepare --target-dir=/backups/full_20260830_020000

Если в цепочке есть инкременты, prepare делается последовательно, с флагом --apply-log-only на всех шагах, кроме последнего (иначе redo-log применится дважды и данные испортятся):

xtrabackup --prepare --apply-log-only --target-dir=/backups/full_20260830_020000

xtrabackup --prepare --apply-log-only \
  --target-dir=/backups/full_20260830_020000 \
  --incremental-dir=/backups/inc_20260830_080000

# последний инкремент — без --apply-log-only
xtrabackup --prepare \
  --target-dir=/backups/full_20260830_020000 \
  --incremental-dir=/backups/inc_20260830_140000

Дальше — сам откат. Останавливаем MySQL, очищаем датадир (он должен быть пустым — copy-back откажется работать поверх существующих файлов) и копируем подготовленный бэкап:

docker compose stop mysql
docker run --rm -v mysql_data:/var/lib/mysql alpine sh -c "rm -rf /var/lib/mysql/*"

docker run --rm \
  -v mysql_data:/var/lib/mysql \
  -v $(pwd)/backups/full_20260830_020000:/backup:ro \
  percona/percona-xtrabackup:8.0 \
  xtrabackup --copy-back --target-dir=/backup --datadir=/var/lib/mysql

docker run --rm -v mysql_data:/var/lib/mysql alpine chown -R 999:999 /var/lib/mysql
docker compose start mysql

Флаг --move-back вместо --copy-back работает быстрее (перемещает файлы вместо копирования), но необратимо уничтожает исходный бэкап в процессе — используйте его только когда точно не нужно оставить копию бэкапа на диске.

Автоматизация: расписание и off-site хранение

Внутри контейнера xtrabackup нет демона cron по умолчанию, поэтому проще всего повесить расписание на хосте — через системный cron, который дёргает docker exec:

# полный бэкап по воскресеньям в 02:00
0 2 * * 0 docker exec xtrabackup sh /scripts/full-backup.sh >> /var/log/xtrabackup.log 2>&1

# инкремент каждые 6 часов
0 */6 * * * docker exec xtrabackup sh /scripts/incremental-backup.sh >> /var/log/xtrabackup.log 2>&1

Ротация старых копий — отдельным скриптом, который чистит каталоги старше N дней:

find /backups -maxdepth 1 -type d -name "full_*" -mtime +30 -exec rm -rf {} \;
find /backups -maxdepth 1 -type d -name "inc_*" -mtime +30 -exec rm -rf {} \;

Хранить копии только на том же диске, что и сама база — половинчатое решение: при отказе диска или всего сервера теряются и данные, и бэкап одновременно. Разумный минимум — выгружать готовые (уже подготовленные prepare) архивы во внешнее хранилище через rclone или mc (клиент MinIO/S3) отдельным шагом после снятия бэкапа, либо держать бэкап-каталог на отдельном сервере — например, на арендованном VPS в другом регионе, синхронизируя его по rsync или borg. Если ищете инструмент именно для offsite-репликации файлов бэкапов, а не для снятия копии с самой БД, посмотрите настройку restic в Docker Compose — он хорошо дополняет XtraBackup именно на этом шаге, дедуплицируя архивы между запусками.

Частые проблемы

СимптомПричинаРешение
Access denied; you need (at least one of) the BACKUP_ADMIN privilege(s)Пользователю бэкапа не выдана привилегия BACKUP_ADMIN (MySQL 8.0+)Выполнить GRANT BACKUP_ADMIN ON *.* TO 'backup'@'%'
Permission denied при чтении /var/lib/mysql из контейнера xtrabackupUID контейнера не совпадает с владельцем файлов датадирыУказать user: "999:999" (или реальный UID из docker exec mysql id mysql)
xtrabackup: innobackupex: cannot connect to MySQL serverКонтейнеры в разных docker-сетях либо неверный MYSQL_HOSTПроверить, что оба сервиса в одной networks:, и что имя хоста совпадает с именем сервиса в compose
mariabackup падает на бэкапе, снятом percona-xtrabackup, и наоборотФорматы бэкапов MySQL/Percona Server и MariaDB несовместимы между утилитамиИспользовать mariabackup строго для MariaDB, percona-xtrabackup — для MySQL/Percona Server
copy-back завершается с Original file ... existsДатадира не пустая перед восстановлениемОчистить /var/lib/mysql перед copy-back или использовать пустой volume
После prepare MySQL не стартует, лог пишет об ошибке InnoDB на стартеИнкременты применены не в хронологическом порядке, либо пропущен --apply-log-only на промежуточных шагахПовторить prepare с нуля от исходного полного бэкапа, строго соблюдая порядок инкрементов
Диск на сервере с бэкапами заполняется быстрее, чем ожидалосьСлишком длинная цепочка инкрементов без новой базовой копии, либо не работает ротацияОграничить цепочку 15-20 инкрементами, проверить cron-задачу очистки на реальное выполнение (crontab -l, логи)

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли снимать бэкап без остановки записи в базу вообще?

Да, это основное преимущество XtraBackup перед mysqldump — блокировка на запись не ставится, короткая блокировка нужна только для фиксации точки согласованности не-InnoDB объектов.

Подходит ли этот compose-файл для MariaDB без изменений?

Нет, образ percona/percona-xtrabackup не совместим с MariaDB. Нужен образ с mariabackup (входит в официальные образы mariadb) и команды mariabackup --backup / --prepare / --copy-back — синтаксис похож, но бинарник и формат бэкапа другие.

Сколько места нужно закладывать под каталог бэкапов?

Ориентировочно объём данных под один полный бэкап плюс 20-30% на инкременты — но это зависит от интенсивности записи, так что первую неделю стоит понаблюдать за реальным ростом каталога.

Нужно ли останавливать MySQL перед снятием бэкапа?

Нет. Остановка нужна только на этапе восстановления — перед copy-back.

Как проверить, что бэкап действительно рабочий?

Регулярно разворачивать его на отдельном тестовом контейнере через prepare + copy-back и поднимать на нём MySQL — только успешный старт сервера и выборочные SELECT по ключевым таблицам подтверждают целостность копии.

XtraBackup умеет шифровать бэкап?

Да, флаги --encrypt и --encrypt-key/--encrypt-key-file шифруют файлы уже во время снятия (AES) — стоит включать, если копии синхронизируются во внешнее хранилище.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →