вторник, 27 марта 2012 г.

MySQL: backup and partial restore

Одна из методик создания резервных копий баз данных MySQL основывается на использовании снимков LVM. Скрипт, который отвечает за создание резервной копии выглядит примерно так:

#!/bin/bash
MYSQL_VG="VolGroup00"
MYSQL_LV="mysql"
MYSQL_SRC="/mnt/mysql-root"
MYSQL_DST="root@backup.server:/backup/mysql-server"

echo "FLUSH TABLES WITH READ LOCK; \! /sbin/lvcreate -s -n ${MYSQL_LV}-backup -L 1G ${MYSQL_VG}/${MYSQL_LV}" | mysql
echo "UNLOCK TABLES" | mysql
mkdir ${MYSQL_SRC}
mount /dev/${MYSQL_VG}/${MYSQL_LV} ${MYSQL_SRC} -o ro
rsync -avzxHS --delete --numeric-ids ${MYSQL_SRC}/ ${MYSQL_DST}/
umount ${MYSQL_SRC}
rmdir ${MYSQL_SRC}
lvchange -an ${MYSQL_VG}/${MYSQL_LV}-backup
lvremove ${MYSQL_VG}/${MYSQL_LV}-backup

Примечание: примеры ориентированы на Debian Squeeze и в других дистрибутивах могут потребоваться доработки.

В самом начале буферы таблиц в MySQL базах сбрасываются на диск и вводится блокировка операций записи в таблицы. Затем создается снимок LVM тома, на котором расположены базы MySQL. После создания снимка снимается блокировка записи и MySQL продолжает работать в нормальном режиме. Расплатой за это удобство является снижение скорости дисковых операций записи в MySQL в то время, пока активен снимок LVM.

Далее снимок монтируется как обычное блочное устройство и файлы, в которых MySQL хранит базы данных, переносятся в резервную копию любым удобных способом. В примере используется rsync, что позволяет передавать по сети только измененившиеся части файлов. По окончании переноса файлов, снимок LVM размонтируется и удаляется из менеждера томов.

Таким образом делается бэкап без остановки работы сервера. Однако создание резервных копий не самоцель, их нужно также восстанавливать в случае потери данных или необходимости получить доступ к более раннему состоянию баз данных. Самый простой способ восстановить резервную копию, созданную описанных выше способом, это остановить сервер MySQL и скопировать данные обратно.

# service mysql stop
# rsync -avxzHS --delete --numeric-ids root@backup.server:/backup/mysql-server/ /var/lib/mysql/
# service mysql start

Такой способ восстановления подходит, если нужно вернуть все базы в исходное состояние на момент создания резервной копии. Но что делать, если нужно восстановить только одну базу, одну таблицу или только несколько записей из конкретной таблицы? Что если основной сервер не допускает остановки? Конечно можно поднять отдельный MySQL сервер на другом сервере, скопировать туда бэкап и после запуска вытащить из него нужные данные посредством mysqldump. Но что делать, если другого сервера нет или версия MySQL сервера содержит патчи, которые делают резервную копию несовместимой с дистрибутивной версией? Я постараюсь дать одно из возможных решений этой проблемы.

В своем способе я также буду запускать отдельную копию MySQL сервера, но основная идея заключается в том, чтобы не копировать данные из бэкапа, а примонтировать их через сеть. Я буду использовать sshfs для этих целей. Чтобы при запуске второго сервера не изменились данные в бэкапе я применю оверлей на базе AUFS. Одна из его частей будет примонтированным бэкапом, а вторая - хранить все изменения относительно бэкапа. Таким образом я избегаю любых модификаций резервной копии.

# mkdir /mnt/mysql-{backup,datadir,tmp}
# sshfs root@backup.server:/backup/mysql-server /mnt/mysql-backup
# mount -t aufs none /mnt/mysql-datadir -o dirs=/mnt/mysql-tmp:/mnt/mysql-backup=ro
# mysqld_safe --defaults-file=/root/mysql-backup-restore.cnf

/root/mysql-backup-restore.cnf - конфигурация для второго сервера, которую я сделал на базе конфига из дистрибутива.

[mysqld]
user=root
pid-file        = /root/mysql-restore.pid
socket          = /root/mysql-restore.sock
basedir         = /usr
datadir         = /mnt/mysql-datadir
tmpdir          = /tmp
language        = /usr/share/mysql/english
skip-networking
skip-external-locking
key_buffer              = 16M
max_allowed_packet      = 16M
thread_stack            = 192K
thread_cache_size       = 8
myisam-recover         = BACKUP
query_cache_limit       = 1M
query_cache_size        = 16M
expire_logs_days        = 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet      = 16M

[isamchk]
key_buffer              = 16M

!includedir /etc/mysql/conf.d/

После запуска вторая копия MySQL сервера доступна только через сокет /root/mysql-restore.sock, чем позволяет исключить посторонний доступ на время восстановления данных. Чтобы подключиться к запущенному серверу, нужно явно указать сокет:

# mysql -S /root/mysql-restore.sock
# mysqldump -S /root/mysql-restore.sock --add-drop-table dbname table1 table2 > dbrestore.sql

После того, как данные сняты посредством mysqldump нужно остановить вторую копию сервера. Для этого нужно выполнить:

# mysqladmin -S /root/mysql-restore.sock shutdown

Когда дополнительный сервер остановится можно отмонтировать /mnt/mysql-datadir и /mnt/mysql-backup, и удалить директории /mnt/mysql-{backup,datadir,tmp}

Несомненным преимуществом такого подхода является время, которое тратится на частичное восстановление резервной копии. Из недостатков пожалуй отсутствие sshfs и aufs во многих дистрибутивах (для centos я так и не смог найти пакета для добавления поддержки aufs). Возможно его следует заменить на любую другую реализацию union fs, но я это делать не пробовал.

Комментариев нет:

Отправить комментарий