Резервное копироварие 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;
После этого мы получим новую таблицу из бэкапа.