Database backup

Материал из BiTel WiKi

Версия от 08:32, 15 января 2009; Admin (Обсуждение | вклад)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Проблема: резервирвание базы данных объемом несколько десятков или сотен гигабайт без остановки сервиса.

Можно использовать "взрослые" накопители типа NAS/SAN, которые имеют встроенную поддержку снапшотов и резервного копирования информации, но это решения дорогостоящие (хотя, что может быть ценнее информации, которая является основой для расчетов с абонентами? вопрос риторический). Поэтому мы пойдем "пионерским" путем. Нам потребуется обычный linux с поддержкой lvm/lvm2. О том, как подготовить дисковое хранилище для размещения базы, я тут особо распространяться не буду. Желающие всегда могут почитать man'ы (pvcreate/vgcreate/lvcreate сотоварищи). Замечу только, что при создании logical volume для хранения базы необходимо оставлять порядка 2-4% нераспределенных physical extents для того, чтобы lvm было куда писать измененные данные во время бекапа. Пример:

# vgdisplay vg1
  --- Volume group ---
  VG Name               vg1
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  3201
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                1
  Open LV               1
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               271.94 GB
  PE Size               32.00 MB
  Total PE              8702
  Alloc PE / Size       8650 / 270.31 GB
  Free  PE / Size       52 / 1.62 GB
  VG UUID               LgXAR3-SPn6-skB8-O0Y0-HI8N-o6gh-0Zy7qc

Здесь я оставил нераспределенными 52 extent'а, что в сумме дает 1.6 гигабайт. Этого вполне хватает для работы базы в течение получаса. Если не хочется рисковать - можно увеличить процент нераспределенных extent'ов.

Итак, предположим, что хранилище под базу соответствует соглашению: оно находится на logical volume, и volume group имеет достаточно нераспределенных extent'ов. Далее все, что нам нужно - это написать пару скриптов, которые будут создавать снапшот и копировать информацию. Кроме штатных средств (lvcreate, lvremove) нам потребуется пакет rsync. Хочется сразу отметить, что полный бекап базы объемом пару десятков гигабайт может занять несколько часов, поэтому мы будем использовать для копирования rsync, который позволяет копировать только измененные файлы (и даже только части измененных файлов). Это означает, что на момент бекапа где-то (там, куда мы копируем текущую базу) уже должна быть хотя бы частично скопированная база, актуальная на какой-то момент в прошлом. Это также означает, что мы имеем всего лишь один бекап - по состоянию на момент последнего резервирования (то есть, если бекап производится раз в сутки - бекап за прошлые сутки). Эту схему, конечно, можно модифицировать, но упражнение по модификации пусть будет вашим домашним заданием. Мы же решаем вполне конкретную задачу: получить консистентную копию базы без прерывания сервиса.

Итак, скрипт №1 (/usr/local/libexec/backupdb/backup.sh):

#!/bin/sh
 
lvcreate=/usr/sbin/lvcreate
lvremove=/usr/sbin/lvremove
mysql=/usr/bin/mysql
mount=/bin/mount
umount=/bin/umount
rsync=/usr/bin/rsync
snapscript=/usr/local/libexec/backupdb/makesnap.sh
nice=/bin/nice
date=/bin/date
 
created=0
 
echo $($date '+%Y-%m-%d %H:%M:%S') creating snapshot
$mysql <<-LCK
        FLUSH TABLES WITH READ LOCK;
        system /bin/sh $snapscript
        UNLOCK TABLES;
LCK
echo $($date '+%Y-%m-%d %H:%M:%S') mounting snapshot
$mount -r /dev/vg1/snap /snapshot
echo $($date '+%Y-%m-%d %H:%M:%S') mounting backup storage
$mount /backup
echo $($date '+%Y-%m-%d %H:%M:%S') rsyncing...
$nice -n 19 $rsync -aH --bwlimit=25000 --delete /snapshot/ /backup/
echo $($date '+%Y-%m-%d %H:%M:%S') unmounting snapshot
$umount /snapshot
echo $($date '+%Y-%m-%d %H:%M:%S') removing snapshot
$lvremove -f vg1/snap >/dev/null 2>&1
echo $($date '+%Y-%m-%d %H:%M:%S') unmounting backup storage
$umount /backup

Скрипт №2 (/usr/local/libexec/backupdb/makesnap.sh):

#!/bin/sh
 
lvcreate=/usr/sbin/lvcreate
lvremove=/usr/sbin/lvremove
sleep=/bin/sleep
sync=/bin/sync
 
created=0
 
while [ $created -eq 0 ] ; do
        $sync ; $sync
        $lvcreate -s -l 52 -Mn -n snap vg1/storage >/dev/null 2>&1
        if [ $? -eq 0 ] ; then
                created=1
        else
                $lvremove -f vg1/snap >/dev/null 2>&1
        fi
        $sleep 1
done

Здесь:

/dev/vg1 
volume group, на которой у меня располагается logical volume "storage" (хранилище базы данных)
/dev/vg1/storage 
собственно логический том "storage"
/dev/vg1/snap 
снапшот этого логического тома
/backup/ 
раздел, где располагается копия базы данных, для простоты я внес раздел в /etc/fstab с опцией noauto

Зачем так сложно, в два скрипта? Почему не сделать один? Все просто: база должна быть консистентной. Этого можно добиться только временно заблокировав все таблицы на момент снятия снапшота, чем и занимается скрипт №2.

Кстати, хозяйке на заметку. Если по какой-то причине скрипт №2 не сможет сделать снапшот, он заблокирует работу MySQL перманентно, до прерывания. В качестве домашнего задания модифицируйте скрипты так, чтобы скрипт №2 выполнял не более, скажем, 30 попыток сделать снапшот и в случае неудачи всех попыток скрипт №1 вместо удаления копии базы завершал работу с корректной диагностикой. Правда, я столкнулся с такой ситуацией всего один раз, но это не значит, что креститься надо строго после того как грянет гром. =)

Обратите внимание на строчку создания снапшота в скриипте №2. Я задействовал все свободные PE из группы (52 штуки). Как говорится, YMMV.

После этого делаем примерно так:

# ln -s /usr/local/libexec/backupdb/backup.sh /etc/cron.daily/backupdb

И еще нам нужно подготовить первичную копию базы для последующего бекапа. Это можно сделать все тем же rsync'ом:

# mount /backup
# rsync -aH --bwlimit=10000 /storage/ /backup/
# umount /backup

Обратите внимание на два момента:

  1. Я ограничиваю скорость копирования опцией bwlimit. Это необходимо для того, чтобы не нарушать нормальную работу базы
  2. Нас не заботит консистентность базы. Все равно очередной бекап "доведет" ее до состояния консистентности благодаря возможностям rsync
  3. При использования хранилица InnoDB реализация данной схемы возможна при запуске mysqld с ключем --innodb_file_per_table, что заставит MySQL хранить каждую InnoDB таблицу в отдельном файле. См.: http://dev.mysql.com/doc/refman/5.0/en/multiple-tablespaces.html

--boco 10:36, 10 июня 2008 (UTC)

Личные инструменты