Резервное копироварие mysql с помощью xtrabackup

MySQL - сверх-популярный сервер баз данных. Его используют (или использовали) практически все. Одна из самых популярных задач в системном администрировании - бэкап (и восстановление). Или, как подвид - миграция данных (бэкап + последующее восстановление). Это делают практически все. И практически все используют для mysql_dump, что категорически неправильно и часто просто опасно. В этой статье я расскажу, почему mysql_dump - это плохое решение и что с ним можно сделать.

Проблема

У mysql_dump есть три основных проблемы - скорость, неконсистентность и блокировки. Все три проблемы проистекают из его архитектуры, так что вылечить их невозможно. Чтобы понять проблемы, опишу алгоритм работы mysql_dump:

  • mysql_dump получает список таблиц в базе
  • он проходит по списку по алфавиту (уже опасный момент)
  • для каждой таблицы он делает три действия:

  • сначала он снимает структуру. Это очень быстрое действие.

  • затем таблица блокируется на запись (целиком)
  • после этого mysql_dump выгружает все данные (грубо говоря - делает SELECT * FROM table)
  • все выгруженное отправляется на stdout. Обычно stdout перенаправляется в файл (классическая конструкция: mysql_dump dbname > file.sql)

  • после того, как сканирование таблицы заканчивается - блокировка снимается и dump переходит к следующей таблице.

Самая серьезная проблема, которая лежит на поверхности - блокировки. В момент, когда mysql_dump блокирует таблицу - записать в нее что-либо становится невозможно. В лучшем случае изменения в таблице будут накапливаться (если приложение умеет копить изменения и работать с базой асинхронно, как gorm). В худшем случае приложение упадет с ошибкой записи, а пользователь получит фигу в виде ошибки 500. Проблема консистентности лежит глубже, а потому она много опаснее, так как сходу эти грабли неочевидны, а по лбу бьют больно. Рассмотрим пример. У нас есть база, в которой лежит 3 таблицы: "affilates", "logs" и "organisations". Таблица logs очень большая. Таблицы affilates и organisations связаны через внешний ключ. Приложение вставляет запись в "affilates", а затем - пачку записей в "organisations" с ключом, указывающим на запись в "affilates". По логике работы mysql_dump, первой будет блокирована и сдамплена affilates. Записи в organisations можно вставлять только для уже существующих affilates, целосность данных не нарушается. После того, как affilates закончится и разблокируется, будет заблокирована logs. Как я уже писал, по условиям задачи она у нас большая и копируется, скажем, час (вполне реальный срок для большой таблицы). При этом никто не мешает создать запись в affilates, а потом из organisations сослаться на эту новую запись. Так, как упаковка affilates уже завершена - в дамп новая запись не попадет. После того, как мы упакуем organisations - в organisations образуются записи, ссылающиеся на не существовавшие в момент дампа ключи. И восстановить такой дамп без определенных магических движений будет невозможно. Ну и в качестве бонуса - мы потеряем данные, которые были созданы в процессе дампа, то есть целосность дампа будет на момент начала операции, а не ее окончания. Чтобы бороться со всеми этими проблемами и был придуман xtrabackup.

установка и настройка

xtrabackup - отдельный инструмент от компании percona. Из-за этого в большинстве штатных системных репозиториев он отсутствует - нужно ставить его из репозитория percona. Установка для debian и ubuntu:

wget https://repo.percona.com/apt/percona-release_0.1-3.$(lsb_release -sc)_all.deb
dpkg -i percona-release_0.1-3.$(lsb_release -sc)_all.deb
apt-get update
apt-get install percona-xtrabackup

То же самое для RHEL-based (CentOS, RedHat, Fedora, ScientificLinux)

yum install http://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm
yum clean all
yum makecache
yum install -y percona-xtrabackup

Для удобства работы с xtrabackup (особенно, если хочется с его помощь делать резервные копии автоматически) рекомендуется создать файл-ответчик с паролем администратора сервера. Назовем его, к примеру, /root/.percona. Пример такого файла:

[client]
user = root
password = YYYYYYYY

Примерение

Простой вариант - резервная копия всего сервера целиком:

innobackupex --rsync --defaults-file=/root/.percona --no-timestamp /var/tmp/backup
innobackupex --rsync --defaults-file=/root/.percona --no-timestamp --apply-log /var/tmp/backup

Восстановление выглядит еще проще. Нужно просто остановить процесс mysql и положить файлы в datadir (обычно это /var/lib/mysql/), а затем - запустить mysql обратно. Перед запуском - обязательно сменить права доступа на файлы, чтобы файлами владел владелец процесса mysql:

service mysql stop
rm -rf /var/lib/mysql/*
mv /var/tmp/backup/* /var/lib/mysql/
chown -R mysql:mysql /var/lib/mysql/
service mysql start

Усложненный вариант - создаем mysql slave от имеющегося мастера:

#on master
innobackupex --rsync --defaults-file=/root/.percona --no-timestamp /var/tmp/backup
innobackupex --rsync --defaults-file=/root/.percona --no-timestamp --apply-log /var/tmp/backup
rsync -rahP /var/tmp/backup NEW.SERVER.IP:/var/tmp/

#выдадим права для slave, чтобы он мог читать данные из master-сервера:
mysql --defaults-file=/root/.percona -Bse "GRANT REPLICATION SLAVE ON *.*  TO 'replica'@'NEW.SLAVE.SEVER.IP' IDENTIFIED BY 'XXXXXXXX'"

innobackupex создает специальный файл с информацией о текущем бинлоге и позиции в этом бинлоге. Эта информация необходима для подключения slave - чтобы slave мог знать, с какого точно момента в прошлом была сделана эта копия базы (и начал синхронизацию именно с нужного момента). Файл называется xtrabackup_binlog_info. Содержимое у него выглядит примерно вот так:

mariadb-bin.000003      642

Подключим slave-сервер к master-серверу. Для этого остановим mysql и подложим в datadir файлы, которые мы скопировали с master:

service mysql stop
rm -rf /var/lib/mysql/*
mv /var/tmp/backup/* /var/lib/mysql/
chown -R mysql:mysql /var/lib/mysql
service mysql start

Теперь подключим slave к master. Для этого в консоли mysql:

--- на всякий случай почистим остатки старой конфигурации slave.
--- В принципе их быть не должно.
STOP SLAVE;
RESET SLAVE;

--- настроим подключение к master
--- log_file и log_pos - из xtrabackup_binlog_info

CHANGE MASTER TO MASTER_HOST='NEW.MASTER.SERVER.IP',
MASTER_USER='replica',
MASTER_PASSWORD='YYYYYYYY',
MASTER_LOG_FILE='mariadb-bin.000003',
MASTER_LOG_POS=642;

--- запустим процесс репликации
START SLAVE

--- убедимся, что нет ошибок

SHOW SLAVE STATUS \G

сложный вариант - переносим отдельную таблицу

Искушающе простой способ (остановить mysql, подсунуть файлы с таблицей и запустить обратно) - не сработает, техника немного сложнее. Чтобы восстановить таблицу, нужно подготовить таблицу внутри резеврной копии к экспорту:

xtrabackup --prepare --export --target-dir=/var/tmp/backup

Теперь нужно создать таблицу с именем экспортируемой таблицы. Структура и содержимое не важны, важно только имя таблицы (имя базы тоже можно не учитывать):

mysql> CREATE TABLE ineedtorestore (a int(11) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1;
mysql> ALTER TABLE ineedtorestore DISCARD TABLESPACE;

Кстати, если при вводе второй таблицы выскочит ошибка ERROR 1030 (HY000): Got error -1 from storage engine, то нужно включить в my.cnf настройку innodb_file_per_table.

Теперь нужно скопировать файлы с именем таблицы, которую мы будем восстанавливать, в каталог базы и перечитать файлы командой:

mysql> ALTER TABLE ineedtorestore IMPORT TABLESPACE;

После этого мы получим новую таблицу из бэкапа.


Комментарии