Создание резервной копии большой удалённой БД средствами YII2

Недавно мне довелось выполнить один маленький и интересный заказ Его суть заключалась в том, чтобы по нажатию кнопки с удалённого сервера БД скопировать БД (её приблизительный размер составляет почти 800Mb) на тестовый сервер и после выполнить ряд изменений в структурах таблиц.


Казалось бы, что нет ничего проще берём выполняем на удалённом сервере запрос вида:

mysqldump -uLogin -pPassword db_name > db_name.sql

и забрать потом файл, но в моём случае это оказался сервер БД без возможности выкачать оттуда файл, поэтому мне пришлось изловчиться и написать свой механизм экспорта из этой удалённой БД средствами YII2, с которым я был на тот момент не так сильно знаком.


Для начала мной был реализован простой интерфейс, описанный в файле views/db/index.php и представленный на Рисунке 1.


Рисунок 1. – Внешний вид
Рисунок 1. – Внешний вид


Для удобства все действия были разделены на 3 части:


  1. Экспорт БД;
  2. Импорт БД;
  3. Применение изменений;
  4. Удаление файлов бекапа.

Справа разместился блок для вывода информации о ходе выполнения операций.


Далее были описаны JQuery скрипты в файле web/js/common.js


Для определения необходимости в детальном логировании основных данных в консоль, была глобально определена константа DEBUG = false.


Переменные dbExportAll, dbImportAll также были определены глобально, для того чтобы можно было точно определить завершённость процесса импорта и экспорта.


Ещё для удобства были инициализированы константы URL_TABLES, URL_EXPORT, URL_IMPORT, URL_REMOVE, URL_MIGRATE. Из значения описаны в файле views/db/index.php:59


В служебных целях были описаны следующие JS функции:


  1. isTrue – проверяет все ли элементы массивов dbExportAll, dbImportAll равны true;
  2. startDB – предшествует началу обработки нажатий на кнопки, и блокирует все дальнейшие действия на странице;
  3. finishFailDB – вызывается в случае невозможности выполнить действие;
  4. finishSuccessDB — вызывается в случае успешного выполнения всех действий после нажатия на наши кнопки;
  5. count – аналог одноимённой PHP функции;
  6. logMess – выводит в лог массив;
  7. logMessStr – выводит в лог строку.

Экспорт


Наконец мы подошли к тому зачем мы всё затевали, к функции,

$('body').on('click', '#dbExportAll', function () {});

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

DbController()->actionExport()

и

DbWrap::export($table, $date)
.

Пример длительности запросов приведён на Рисунке 2. Из Рисунка 2 видно, что на экспорт такой большой таблицы требуется минимум 134 секунды (2.2 минуты), и это без учёта того времени, которое запрос стоял в очереди на выполнение.


Рисунок 2. – Сведения об экспорте таблицы mis
Рисунок 2. – Сведения об экспорте таблицы mis


Но из лога (Рисунок 3) видно, что на экспорт большинства таблиц требуется очень мало времени, менее секунды.



Рисунок 3. – Лог экспорта
Рисунок 3. – Лог экспорта


А из Рисунка 4 видно, что на экспорт всех таблиц достаточно 7 минут.


Рисунок 4. – Тайминг экспорта
Рисунок 4. – Тайминг экспорта


Импорт


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



Работа импорта также начинается с отработки JS скрипта описанного в функции

$('body').on('click', '#dbImportAll', function () {});

Аналогично экспорту производится получение списка всех таблиц и инициализация переменной dbImportAll. Далее таблицы поштучно отправляются на импорт за который отвечают

DbController()->actionImport()

и

DbWrap::importAll($table)

Логика работы скрипта в этом месте простая, находим самый последний файл и выполняем команду вида «mysql -uroot -pPass db < file.sql» (если пароль пустой то команду отправляем без указания пароля).


Из Рисунка 5 видно, что на выполнение импорта хватает и 5 минут.


Рисунок 5. – Тайминг импорта
Рисунок 5. – Тайминг импорта


Применение изменений


Применение изменений начинается с отработки JS функции

$('body').on('click', '#dbMigrate', function () {...});

в которой мы отправляем запрос к контроллеру DbController и его методу actionMigrate, который в свою очередь вызывает DbWrap::migrate($mess). Функция migrate описывает транзакцию в рамках которой отправляется ряд запросов на изменение таблиц.


После отработки применения изменений выводится соответствующие сообщение с подробным описанием в случае ошибки (в нашем случае ошибка связана с тем что изменения уже были применены ранее), или с затраченным временем на выполнение (см. Рисунок 6).


Рисунок 6 – Миграция
Рисунок 6 – Миграция


Удаление


А удаление начинается с отработки JS функции

$('body').on('click', '# dbRemove, function () {});

в которой мы отправляем запрос к контроллеру DbController и его методу actionRemove, который в свою очередь вызывает

DbWrap::remove()

Функция m remove описывает определение семейства операционной системы, на которой запущен PHP скрипт и выполняет команды rm –rf или RD /S/q в зависимости от ОС.


Результат


В результате мы получили возможность быстро за 3 клика применить изменения на тестовом сервере, или поднять базу в случае её падения. Кроме этого у нас есть заготовка для выполнения других долгих запросов основным принципом которых является


Разделяй и властвуй

Справедливости ради стоит заметить, что все замеры были произведены на локальном сервере, работающем на PHP7 с относительно мощным процессором и 1.5Gb оперативной памяти. Поэтому производительность данного скрипта скорее всего будет ниже на слабых серверах и хостингах.




Вовремя вспомнил, для тех кто хочет детально ознакомиться с исходным, исходники размещены на Bitbucket




Литература:


  1. http://sitear.ru/material/mysql-backups
  2. http://www.yiiframework.com/doc-2.0/yii-db-connection.html
  3. http://php.net/manual/ru/function.fopen.php
  4. http://php.net/manual/ru/function.fwrite.php
  5. http://php.net/manual/ru/function.shell-exec.php
  6. https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html
  7. http://sc-blog.ru/импорт-экспорт-базы-mysql-консоль/

Средняя зарплата в IT

120 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 7 154 анкет, за 1-ое пол. 2021 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 9

    +4
    мало знаком с yii 2, поэтому скажите, это так модно сейчас отдавать json ответ через exit?
    +9
    но в моём случае это оказался сервер БД без возможности выкачать оттуда файл
    Но ведь (насколько я понял) к порту mysqld на сервере БД у вас доступ есть?
    mysqldump -uLogin -pPassword --host=remote_host --port=3306 db_name > db_name.sql
      0
      Порты как правило наружу не открыты, порт 3306 светится только внутри. Здесь разве что ssh-тоннель делать, если есть такой доступ к серверу.
        +1
        Простите, а вы внимательно прочитали статью?

        Мой ход мысли:
        У автора нет возможности скачать файл с сервера БД.
        Как следствие — возможности залить файл на сервер БД тоже нет.
        Как следствие — web приложение развернуто на другом сервере.
        Как следствие — web приложение соединяется с удаленным сервером БД.
        Как следствие — для того, чтобы сделать дамп базы, web приложение не нужно
          –2
          Как следствие — для того, чтобы сделать дамп базы, web приложение не нужно

          Да можно было обойтись и без web приложения, но это больше для удобства т.к. бекапы в моём случае делаются только тогда когда нам это будет нужно.
      +3
      Почитал ваш код. Не понял, для чего вообще использован Yii2, зачем DbWrap наследован от ActiveRecord.
      Оформление кода ужасное, в коде полно магических строк, я, например не смогу ваш код просто взять и использовать, тем более, он идет как что-то, написанное на фреймворке, а не модуль к нему.
      Почитайте про механизм миграций в Yii. Там весь ваш велосипед нормально реализован.
      Если $dirBackUp в DbWrap определить в '/', то можно с правами суперпользователя убить сервер.

      И да, файл с сервера всегда можно забрать, если на него как-то можно попасть. По sftp, например…
        +1
        Гораздо проще было бы прикрутить SypexDumper, даже к Yii
        В прочем список литературы намекает что это студенческий проект по фиксированному заданию

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое