Используем современные возможности файловой системы BTRFS для создания точек восстановления MySQL

btrfs_logoДобрый день, сегодня мы рассмотрим способ создания множественных точек восстановления СУБД MySQL с помощью возможности файловой системы BTRFS. Вкратце рассмотрим решаемую задачу: Давайте представим, что у нас имеется бизнес-критический ресурс, для которого нам бы имелось иметь почасовые точки восстановления, то есть иметь возможность восстановить состояние нашей базы данных 1 час назад или на произвольный момент с часовой точностью за последние сутки.

Данную задачу мы можем решить несколькими способами, приведем основные способы здесь:

  1. Дамп базы данных — способ используется часто, но подходит только для небольших баз данных, потому что для обеспечения синхронного бэкапа требуется заблокировать операции записи, чтобы избежать изменения таблиц в процессе создания дампов. Если же дамп создается долго (таблицы в базах большие, занимают несколько гигабайт каждая), то это не только приведет к останову сайта на это время, но и при выполнении данной операции каждый час сделает систему нефункциональной.
  2. Способ 1, комбинированный с репликацией Master-Slave. В данном способе дампы делаются не с основного (master) сервера, а с резервного (slave), который может спокойно быть остановлен на некоторое время без ущерба. Сложность данного способа заключается как в использовании двухузловой конфигурации, что требует двойного объема ресурсов, так и в том, что slave должен «догнать» master после завершения дампа до точки создания следующего дампа, в противном случае, на slave будет накапливаться отставание, что, в конечном итоге, ничем хорошим не закончится.
  3. Использование Percona Xtrabackup, способ подробно описан на сайте habrahabr. Способ хороший, годный, но больше предназначен для создания резервных копий, нежели для точек отката.
  4. Использование снимков файловой системы. Надо сказать, что на сегодняшний день в linux существуют три способа, которые позволяют реализовать данную схему:
    1. LVM2 — данная система может применяться для резервного копирования, но в нашей задачи ее использование невозможно, так как тома LVM со снимками работают очень медленно;
    2. ZFS —  эта легендарная файловая система словно волшебная таблетка, однако для linux ее требуется собирать отдельно, так как по лицензионным соображениям она не входит в стандартные сборки.
    3. BTRFS — новая файловая система, которая поддерживает все необходимые нам функции, однако, считается недостаточно стабильной. Слово «считается» здесь ключевое, так как существует достаточно большое число компаний, которые успешно используют данную FS, а на случай фатального сбоя будем иметь бэкап базы для быстрого запуска. Этот способ и будем использовать.

Используемая конфигурация оборудования

Для реализации задачи мы будем использовать VM из облака NetPoint, Вы же можете использовать то, что Вам больше нравится. Самое важное — планирование системы хранения данных VM. Мы будем использовать SSD-диск размером 10GB для СУБД и точек восстановления (за последние 24 часа) и SATA-диск 120GB для хранения бэкапов СУБД за последний месяц и копии точек восстановления за последние сутки для быстрого восстановления в случае если BTRFS сломается. Наша задача — иметь возможность восстановления работоспособности MySQL в последней точке восстановления в случае полной аварии BTRFS за 5-10 минут.

NB: BTRFS чувствительна к ограничением дискового пространства. Не допускайте, чтобы на файловой системе с BTRFS закончилось место.

Используемое программное обеспечение

  1. Debian 7
  2. MariaDB 10.0

Тома файловой системы

  1. SSD10GB: /mnt/db — BTRFS, файловая система для хранения основной СУБД и точек восстановления
  2. SAS120GB: /mnt/backup — EXT4, файловая система для хранения резервных копий точек и бэкапов за предыдущие дни.

Способ создание точек восстановления

Для создания точки восстановления мы будем использовать снимки BTRFS. Для корректного состояния баз данных в момент создания снимков мы будем использовать FLUSH TABLES WITH READ LOCK и UNLOCK TABLES, что позволит нам обеспечить целостность схемы БД в момент создания снимка, а значит позволит, в случае необходимости быстро восстановить БД из корректного снимка.

Развертывание VM

Мы будем использовать VM в облаке NetPoint, Вы можете использовать VM в нашем или любом другом облаке, а так же аппаратный сервер. При развертывании мы сделаем следующую разметку:

  • /dev/vda1 (Ext4) — / (корневая фс), 2GB
  • /dev/vda2 (swap) — раздел подкачки, 2GB
  • /dev/vda3 (BTRFS) — /mnt/db (остаток места)
  • /dev/vdb1 (Ext4) — /mnt/backup (все пространство)

После установки имеем:

root@btrfs-test:~# cat /etc/issue
Debian GNU/Linux 7 \n \l

root@btrfs-test:~# mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
udev on /dev type devtmpfs (rw,relatime,size=10240k,nr_inodes=506167,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime,size=406136k,mode=755)
/dev/disk/by-uuid/ce3734ea-967f-40eb-81a3-f6f66e98b000 on / type ext4 (rw,relatime,errors=remount-ro,user_xattr,barrier=1,data=ordered)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
tmpfs on /run/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=1202800k)
/dev/vdb1 on /mnt/backup type ext4 (rw,relatime,user_xattr,barrier=1,data=ordered)
/dev/vda3 on /mnt/db type btrfs (rw,relatime,space_cache)
rpc_pipefs on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw,relatime)

Далее необходимо установить MySQL. Я буду использовать реализацию MariaDB 10.0, которая принята к использованию по умолчанию в нашей компании. Установка достаточно простая.

# apt-get install python-software-properties 
# apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db 
# add-apt-repository 'deb http://mirror.mephi.ru/mariadb/repo/10.0/debian wheezy main'
# apt-get update
# apt-get install mariadb-server

Теперь перенесем наши базы данных MySQL на BTRFS:

# service mysql stop
[ ok ] Stopping MariaDB database server: mysqld.

# mv /var/lib/mysql /mnt/db/mysql
# ls /mnt/db/mysql/
aria_log.00000001 aria_log_control debian-10.0.flag ibdata1 ib_logfile0 ib_logfile1 multi-master.info mysql mysql_upgrade_info performance_schema
# ln -s /mnt/db/mysql /var/lib/mysql

Запустим MariaDB

# service mysql start
[ ok ] Starting MariaDB database server: mysqld.
[info] Checking for corrupt, not cleanly closed and upgrade needing tables..
# ps xa | grep mysql
 6490 pts/0 S 0:00 /bin/bash /usr/bin/mysqld_safe
 6694 pts/0 Sl 0:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306
 6695 pts/0 S 0:00 logger -t mysqld -p daemon.error
 6811 pts/0 R+ 0:00 grep mysql

Попробуем теперь создать снимок mysql (для теста, что все ОК). Данный снимок не будет корректным, потому что таблицы MySQL не ограничены для записи и не синхронизованы с диском, просто проверим, что снимки делаются:

# btrfs subvolume snapshot /mnt/db /mnt/db/@mysql_initial_db
Create a snapshot of '/mnt/db' in '/mnt/db/@mysql_initial_db'
# ls /mnt/db/@mysql_initial_db/
mysql
# ls /mnt/db/@mysql_initial_db/mysql/
aria_log.00000001 aria_log_control debian-10.0.flag ibdata1 ib_logfile0 ib_logfile1 multi-master.info mysql mysql_upgrade_info performance_schema

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

Создаем файл /opt/backup-mysql следующего содержания:

#!/usr/bin/perl

use strict;
use warnings;
use DBI;

my $MYSQL_LOGIN='root';
my $MYSQL_PASSWORD=$ENV{'MYSQLPASS'};
my $DIR = '/mnt/db';
my $BACKUP = '/mnt/backup';
my $hour = `date +"%H"`;
chomp $hour;
my $day = `date +"%d"`;
chomp $day;

# create directory where to store last backups
system("mkdir $BACKUP/last24 2>/dev/null");

my $dbh = DBI->connect("DBI:mysql:;host=127.0.0.1",$MYSQL_LOGIN,$MYSQL_PASSWORD) or die DBI->errstr;
$dbh->do("FLUSH TABLES WITH READ LOCK") or die $dbh->errstr;
if(!system("test -d $DIR/\@point-$hour")){
 system("btrfs subvolume delete $DIR/\@point-$hour");
}
system("btrfs subvolume snapshot $DIR $DIR/\@point-$hour");
$dbh->do("UNLOCK TABLES") or die $dbh->errstr;

system("rm -Rf $BACKUP/last24/hour-$hour 2>/dev/null");
system("cp -Rf $DIR/\@point-$hour $BACKUP/last24/hour-$hour");

if ("$hour" == "23") {
 system("rm -Rf $BACKUP/day-$day 2>/dev/null");
 system("cp -Rf $DIR/\@point-$hour $BACKUP/day-$day");
}

Ставим права доступа на файл 755:

# chmod 755 /opt/backup-mysql

Кратко, по шагам, что же он делает:

  1. соединяемся с mysql
  2. сбрасываем таблицы на диск
  3. удаляем снимок на текущий час, если он есть (повторный запуск в этом часе или за предыдущие сутки);
  4. создаем снимок файловой системы в форме @point-<час>;
  5. разблокируем таблицы;
  6. копируем снимок в /mnt/backup/last24/hour-<час>;
  7. если текущий час равен 23, то копируем снимок в /mnt/backup/day-<деньмесяца>;

Скрипт берет пароль из переменных окружения, то есть перед его запуском необходимо сделать:

# export MYSQLPASS=mysqlsecretpassword

Для периодического запуска возможно воспользоваться CRON, напишем небольшой скрипт в /etc/cron.hourly/backup-mysql:

#!/bin/bash
export MYSQLPASS=mysqlsecretpassword
/opt/backup-mysql

Дадим ему права на выполнение:

# chmod 755 /etc/cron.hourly/backup-mysql

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