вторник, 19 августа 2008 г.

PyVSB - простая в использовании система бэкапа данных

Меня давно привлекал язык программирования Python - по-моему все мнения о нем, которые мне доводилось слышать, были исключительно положительными, да и в последнее время при работе в Linux я стал замечать, что все больше и больше программ, которыми я пользуюсь, написаны на Python'е. Прочитав Python tutorial и просмотрев Python Library Reference, я понял, что совершенно не зря он стал так популярен в последнее время - это просто замечательный язык, который отлично подойдет как web-разработчику так и разработчику настольных программ, особенно если скорость написания программы для него имеет решающее значение.

После прочтения документации к Python, я стал думать, как бы мне закрепить полученные знания на практике. Идеальным вариантом было бы написание относительно небольшой программы, которая использует как можно больше возможностей библиотеки Python, и которая пригодилась бы мне впоследствии, чтобы не "писать программу, ради написания программы". Попытавшись вспомнить, каких программ мне не хватает, я пришел в тупик - оказалось, что в Linux я нашел для себя все, что мне было необходимо. Однако, я вспомнил, что уже довольно давно для бэкапа использую самописные скрипты, которые хоть и сжимают сохраненные данные, но не обеспечивают инкрементального бэкапа. Поставить какую-нибудь bacula и читать целый том документации к ней мне не хотелось - для "домашнего" бэкапа ее возможности совершенно не нужны. До поиска каких-нибудь более простых систем бэкапа у меня как-то не доходили руки.

Тут то я и нашел, на чем мне можно получить практический опыт программирования на Python - нужно написать относительно простую систему для "домашнего" бэкапа, которая будет иметь только необходимые функции и минимум настроек в конфигурационном файле.

Необходимые для меня функции - это:
  • Инкрементальный бэкап
  • Сжатие сохраняемых данных
  • Отправка уведомлений на email
  • Возможность запуска собственных скриптов до и после бэкапа
  • Фильтрация файлов, подлежащих резервному копированию, по регулярным выражениям

Вот что у меня получилось (взято из Readme файла к программе):

PyVSB

Простая в использовании система бэкапа данных для GNU/Linux.


Основные возможности:

- Инкрементальный бэкап
- Сжатие сохраняемых данных
- Отправка уведомлений на email
- Возможность запуска собственных скриптов до и после бэкапа
- Фильтрация файлов, подлежащих резервному копированию, по регулярным выражениям


Инкрементальный бэкап

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


Сжатие сохраняемых данных

Данные бэкапа можно сохранять и без сжатия для экономии процессорного времени во время бэкапа, но если занимаемое ими место на диске имеет решающее значение, то при сохранении их можно сжимать алгоритмами gz, bz2 и 7z. 7z имеет самую высокую степень компрессии, но при сжатии требует больше всего процессорного времени.

Чтобы можно было хотя бы примерно оценить, какое количество пространства эти алгоритмы могут вам сэкономить, и сколько процессорного времени на это понадобится, я сделал tar архив из своей директории /usr и обжал его с помощью данных алгоритмов. Результаты теста очень приблизительные и, естественно, будут меняться от типов данных и конфигурации компьютера, поэтому имеет смысл смотреть только на примерные соотношения размер/время и решать, какой алгоритм для вас предпочтительней.

Исходный размер данных: 3990 Мб
gz: размер файла: 1621 Мб, затраченное время: 14,5 мин.
bz2: размер файла: 1483 Мб, затраченное время: 34 мин.
7z: размер файла: 1144 Мб, затраченное время: 93,5 мин.


Отправка уведомлений на email

По завершении бэкапа на указанный в конфигурационном файле email адрес может отправляться отчет. Все строки в отчете начинаются с одного из трех символов: I (Info), W (Warning) и E (Error), которые отражают уровень важности данного сообщения. Info - обычные сообщения, которые несут чисто информационную нагрузку, например, "Бэкап начат в 12.32.24 16.08.2008". Warning - ошибки, которые возникают, когда не получается сделать резервную копию какого-либо файла, например, если не хватает прав доступа. Error - ошибка, которая требует вашего внимания, если в отчете есть такие ошибки, значит, что-то во время бэкапа пошло не так. Если в отчете присутствуют предупреждения и ошибки, то это будет отражено в теме сообщения. Например, тема email сообщения, не содержащего предупреждений и ошибок, будет "PyVSB backup report", а тема сообщения с ошибками - "PyVSB backup report [E]".


Возможность запуска собственных скриптов до и после бэкапа

Если вы хотите перед началом бэкапа стереть временные файлы из директорий, подлежащих бэкапу, или сделать дамп базы данных до начала бэкапа в директорию, подлежащую бэкапу, а потом стереть только что записанные файлы, то вы можете назначить скрипты (программы) которые будут запущены до и после бэкапа.


Фильтрация файлов, подлежащих резервному копированию, по регулярным выражениям

Зачастую при резервном копировании директорий оказывается, что какие-либо из них содержат файлы (директории), которые вам не хотелось бы бэкапить, например, из-за их большого размера. В таком случае очень пригодится возможность настройки фильтров на основе регулярных выражений, отсеивающих лишние файлы, или наоборот, пропускающих только те, которые подходят под описание фильтра.




Описание процесса работы с PyVSB

Для создания бэкапа необходим конфигурационный файл PyVSB. Конфигурационный файл не обязательно должен быть один - их может быть неограниченное количество у каждого пользователя. Это позволяет, например, производить резервное копирование разных каталогов с разным периодом времени.

PyVSB вводит следующие понятия:
- Корневой каталог бэкапа (backup root) - директория, в которую будут записываться все файлы бэкапа.
- Группа (backup group) - директория, находящаяся в корневой директории бэкапа и содержащая некоторое количество бэкапов.
- Текущая группа (current group) - группа, имеющая имя current. При создании бэкапа он всегда пишется в текущую группу.
- Элемент бэкапа (backup entry) - путь к файлу или каталогу, указанный в конфигурационном файле и подлежащий резервному копированию.

Инкрементальность бэкапов поддерживается только на уровне групп, т. к. при создании бэкапа просматривается список контрольных сумм всех файлов, принадлежащим бэкапам только текущей группы.

В конфигурационном файле можно задать максимальное количество бэкапов в текущей группе. Как только количество бэкапов в current группе станет равным этому числу, current группа будет переименована в группу с именем ${имя_первого_бэкапа_в_группе}_-_${имя_последнего_бэкапа_в_группе}. После этого в данную группу бэкапы больше добавляться не будут и будет создана новая current группа.

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

Также ограничение на количество бэкапов в группе рекомендуется задавать по следующей причине. При создании бэкапа в памяти программы создается список со всеми уникальными файлами группы, с помощью которого реализуется возможность инкрементального бэкапа. Если каждый бэкап у вас содержит очень большое количество файлов, то чем больше бэкапов в группе, тем больше времени и оперативной памяти будет требоваться программе на создание этого списка перед началом бэкапа.

Бэкапы внутри группы зависят друг от друга, если у них есть файлы с одинаковыми контрольными суммами. Группы не зависят друг от друга и могут быть удалены, или, к примеру, перемещены на другой носитель (CD, DVD).

При восстановлении необходимо указать PyVSB директорию с бэкапом (${путь_к_корневой_директории_бэкапа}/${имя_группы}/${имя_бэкапа}). Данный бэкап будет восстановлен в текущую директорию.

Пример конфигурационного файла с описанием всех возможных опций можно посмотреть в файле example.pyvsb.conf.

Для получения информации о работе с программой выполните команду pyvsb --help.




Описание принципа работы PyVSB (не обязательно к прочтению)

При разработке PyVSB делался акцент на то, чтобы сделать структуру корневой директории бэкапа максимально простой и не использовать каких-либо собственных форматов файлов. PyVSB использует только текстовые файлы (для хранения управляющей информации), и *.tar файлы (для хранения данных бэкапа). Для экономии места данные файлы сжимаются алгоритмами gz, bz2 и 7z (в зависимости от настроек).

Структура корневой директории бэкапа предельно проста:
${backup_root}
|
|-- ${group_1}
|   |
|   |-- ${backup_1}
|   |   |-- data{.tar,.tar.gz,.tar.bz2,.tar.7z}
|   |   |-- extern_files.bz2
|   |   |-- log.bz2
|   |   `-- unique_files.bz2
|   |
|   `-- ${backup_2}
|       |-- data{.tar,.tar.gz,.tar.bz2,.tar.7z}
|       |-- extern_files.bz2
|       |-- log.bz2
|       `-- unique_files.bz2
|
`-- ${group_2}
|
|-- ${backup_1}
|   |-- data{.tar,.tar.gz,.tar.bz2,.tar.7z}
|   |-- extern_files.bz2
|   |-- log.bz2
|   `-- unique_files.bz2
|
`-- ${backup_2}
|-- data{.tar,.tar.gz,.tar.bz2,.tar.7z}
|-- extern_files.bz2
|-- log.bz2
`-- unique_files.bz2

data{.tar,.tar.gz,.tar.bz2,.tar.7z} - архив, содержащий все сохраненные файлы бэкапа. Если при создании резервной копии файла оказывается, что в данной группе уже есть резервная копия с такой же контрольной суммой, то в данный архив файл не записывается - в него сохраняется файл с типом "жесткая ссылка", своей ссылкой указывающий на пустую строку и имеющий те же атрибуты, что и оригинальный файл. Все жесткие ссылки записываются в архив и впоследствии восстанавливаются как обычные файлы.

extern_files.bz2 - список файлов, которые не были добавлены в архив из-за того, что в текущей группе уже существовали резервные копии с такими контрольными суммами. Список содержит контрольную сумму файла и путь к файлу.

log.bz2 - содержит все сообщения, которые выводились PyVSB в процессе бэкапа. Не несет никакой полезной нагрузки с точки зрения последующих бэкапов или восстановления - создается исключительно для пользователя и может быть удален.

unique_files.bz2 - список файлов, содержащихся в архиве с их контрольными суммами.


При восстановлении PyVSB считывает unique_files.bz2 из всех бэкапов группы и составляет список уникальных файлов группы. Далее программа распаковывает архив с данными восстанавливаемого бэкапа. Если при распаковке PyVSB натыкается на файл с типом "жесткая ссылка", то она ищет в extern_files.bz2 контрольную сумму данного файла, по контрольной сумме находит в списке уникальных файлов группы, в каком бэкапе лежит файл с данной контрольной суммой, извлекает этот файл с теми атрибутами, которые записаны в архиве восстанавливаемого бэкапа, и переходит к следующему файлу.

Процедура восстановления крайне проста и, в принципе, при большом желании вы сможете осуществить ее сами, если у вас не будет при себе PyVSB, или по какой-либо причине вы не сможете ее запустить, что является несомненным плюсом.



example.pyvsb.conf:
# Пример конфигурационного файла PyVSB

# Описание формата конфигурационного файла:
# http://www.voidspace.org.uk/python/configobj.html#the-config-file-format

# Путь к корневой директории бэкапа.
backup_root = "/media/var_data/backup/pyvsb"

# Формат архивов, в которых будут храниться файлы бэкапов.
# Допустимые значения: 7z, bz2, gz, tar.
# Значение по умолчанию - bz2.
backup_format = "7z"

# Как только количество бэкапов в текущей группе достигнет данного
# значения, формирование current группы будет завершено.
# 0 - неограниченное количество.
# Значение по умолчанию - 30.
backups_per_group = 8

# Как только количество групп превысит данное значение, старые группы будут
# удалены, так чтобы осталось groups_per_backup_root групп.
# 0 - неограниченное количество.
# Значение по умолчанию - 0.
groups_per_backup_root = 2


# Отправлять отчеты при завершении бэкапа на email адрес.
# Значение по умолчанию - no.
send_email_report = yes
# Email, на который будут отсылаться отчеты.
mail_to = "Konishchev Dmitry <to@mail.com>"
# Поле From отправляемого письма (необязательная опция, если ваша программа
# отправки email сама заполняет это поле).
mail_from = "Konishchev Dmitry <from@mail.com>"
# Программа, с помощью которой будет отправляться email сообщение.
# Значение по умолчанию - "".
mail_program = "msmtp --read-recipients"


# Синтаксис определения элементов бэкапа:
# -->
    # Элемент бэкапа "/bin"
#   [ "/bin" ]
        # Программа, которая будет запущена перед бэкапом данного элемента.
#       start_before = ""
        # Программа, которая будет запущена после бэкапа данного элемента.
#       start_after = ""
    
        # Фильтры, которые будут применены к файлам и директориям данного
        # элемента бэкапа. Каждый фильтр должен начинаться с символов "+",
        # "-" или "#". Все остальные символы - это регулярное выражение
        # (или комментарий в случае "#"). Синтаксис регулярных выражений -
        # http://docs.python.org/lib/re-syntax.html.
        #
        # Перед бэкапом файла (директории) его путь сравнивается с данными
        # регулярными выражениями. Выполняется команда первого фильтра, под
        # который подошел путь:
        #  "+" - сделать резервную копию
        #  "-" - пропустить файл (директорию)
        # Сравнение с регулярным выражением происходит не по полному пути к
        # файлу (директории), а по пути относительно данного элемента
        # бэкапа. К примеру, если элемент бэкапа - /home/user, а файл -
        # /home/user/firefox/cache, то регулярное выражение будет
        # сравниваться с user/firefox/cache.
#       filters = ""

        # Действие, которое будет применено к файлу, если он не подойдет ни
        # под один фильтр.
#       filter_default_policy = "+"
# <--


[ "/etc" ]
[ "/root/scripts" ]
[ "/var/spool/cron/crontabs" ]


[ "/home/dmitry" ]
    filters = """
        # Отфильтровываем директории, которые занимают много места и не
        # нуждаются в резервном копировании.
        -^dmitry/.cache$
        -^dmitry/.gvfs$
        -^dmitry/.kde/share/apps/ktorrent$
        -^dmitry/.liferea
        -^dmitry/.mozilla/firefox/[^/]+/Cache$
        -^dmitry/.mozilla/plugins$
        -^dmitry/.stardict$
        -^dmitry/.thumbnails$
        -^dmitry/.VirtualBox$
        -^dmitry/.wine$
    """


[ "/my_files/scripts"]


[ "/my_files/other/private" ]
    filters = """
        # Бэкапим только /my_files/other/private/*_backup
        +^private(/[^/]+_backup(/.+){0,1}){0,1}$
    """

    filter_default_policy = "-"


[ "/my_files/dirs_tree" ]
    # Если бэкапить директорию нет возможности вследствие ее большого
    # размера, то можно сохранить хотя бы список всех ее файлов и
    # каталогов, чтобы в случае повреждения можно было восстановить их по
    # именам, если эти файлы не уникальны (скачать в интернете и т. п.).
    start_before = "tree -a --dirsfirst /my_files > /my_files/dirs_tree"

    start_after = "rm -f /my_files/dirs_tree"

Напоследок вынужден предупредить, что программа практически не тестировалась, так что вполне возможно, что она содержит какие-либо ошибки (думаю, не критические), к тому же это моя первая программа на Python. Также PyVSB - не лучший выбор, если вам нужно бэкапить сотни тысяч файлов, т. к. все-таки быстродействие и требования к памяти у Python программ гораздо выше, чем у аналогичных, написанных на C/C++. Профилированием я не занимался, т. к. меня скорость ее работы вполне устраивает. Теперь дома использую только ее, через неделю-другую поставлю на работе.

Если же она вас заинтересовала, то исходные тексты и deb пакет можно скачать тут.

7 комментариев:

Unknown комментирует...

по описанию понравилось. попробую использовать на ноутбуке

Анонимный комментирует...

Давно искал простую систему бэкапа для домашнего компа. Уже подумывал сам писать. На днях попробую поиграться с PyVSB.

Pilat комментирует...

Каждый раз считать контрольные суммы - долго, если бэкапить работающую систему, получаем бэкап неизвестно чего. Конечно, использование снапшотов может спасти, но от загрузки машины не спасёт, и загрузки сильно и надолго. На практике ценность такого бэкапирования сомнительная. rdiff-backup и оттестированная, и продвинутая система, умеющая делать всё описанное и многое другое.

Dmitry Konishchev комментирует...

> Каждый раз считать контрольные суммы
> - долго, если бэкапить работающую
> систему, получаем бэкап неизвестно
> чего. Конечно, использование снапшотов
> может спасти, но от загрузки машины
> не спасёт, и загрузки сильно и надолго.
Pilat66, но я же не собираюсь бэкапить весь "/". Все, что мне нужно - это сделать резервные копии всех конфигурационных файлов, исходных текстов моих скриптов и программ, каталога SVN и еще некоторых файлов, которыми я особо дорожу. Такой бэкап у меня производится довольно быстро и совершенно не напрягает.

> На практике ценность такого
> бэкапирования сомнительная.
Дома у меня первый бэкап, обжатый 7z'ом, весит 358 Мб, а все остальные - 7 Мб. На работе соответственно 150 и 4 Мб. На мой взгляд, ценность очень даже очевидная. При таких размерах я могу делать бэкапы очень часто, не заботясь о том, сколько места они займут. Лично я очень доволен таким результатом, не думал, что получится настолько хорошо.

> rdiff-backup и оттестированная, и
> продвинутая система, умеющая делать всё
> описанное и многое другое.
Не спорю и верю, что это так. Я отлично понимаю, что изобрел колесо, но я же и хотел это сделать. Моей задачей было не создание системы бэкапа, которая будет лучше всех остальных или станет чем-то революционым. Все, что я хотел - это написать какую-нибудь программу, получив при этом опыт программирования на Python'е, которая также будет мне полезна в повседневной работе. Я считаю, что мне это удалось. На мой взгляд, получилось довольно неплохо. Выложил же я ее сюда потому, что, возможно, в интернете найдутся люди вроде меня, которые тоже сочтут ее неплохим решением для себя и будут использовать.

Сергей комментирует...

Спасибо, отличный скрипт, буду пробовать :)

Анонимный комментирует...

Потестим на BSD...как там? еще в портах не появился?

Dmitry Konishchev комментирует...

BSD никогда не пользовался, так что пока нет.