воскресенье, 26 июля 2015 г.

Миграция Redmine с MySQL на PostgreSQL

На этот раз рассмотрим перенос данных из MySQL в PostgreSQL, пригодный, пожалуй, для любых приложений, предоставляющих выбор из этих двух СУБД.

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

Рассмотрим эту стратегию миграции на примере Redmine.

Получение схемы базы данных

Схема базы данных - это её структура, то есть таблицы, индексы, ключи, без данных.

Установим дополнительно пакет, отвечающий за работу Redmine совместно с СУБД PostgreSQL:
# apt-get install redmine-pgsql
Для начала перейдём в каталог /etc/redmine/, в котором есть подкаталоги, соответствующие экземплярам redmine:
# cd /etc/redmine/
Скопируем каталог одного из экземпляров в другой каталог, создав таким образом новый экземпляр. В данном случае копируем экземпляр default и создаём на его основе экземпляр pgsql:
# rsync -rogp default/ pgsql/
Открываем файл /etc/redmine/pgsql/database.yml и заменяем в нём имя пользователя в поле username, имя базы данных в поле database и тип базы данных в поле adapter. У меня получился такой файл:
production:
  adapter: postgresql
  database: redmine_pgsql
  host: localhost
  port:
  username: redmine_pgsql
  password: password
  encoding: utf8
Теперь нужно создать соответствующего пользователя и базу данных:
# su - postgres
$ createuser -P redmine_pgsql
$ createdb -E UTF-8 -O redmine_pgsql redmine_pgsql
$ exit
Пустая база данных и пользователь созданы. Теперь нам нужно наполнить эту базу данных структурой и первичными данными. Для этого переходим в каталог /usr/share/redmine/, в котором находится Redmine, и запускаем команду миграции экземпляра pgsql, которая сделает всё необходимое:
# cd /usr/share/redmine
# rake db:migrate RAILS_ENV=production X_DEBIAN_SITEID=pgsql
Теперь, если всё прошло успешно, создаём резервную копию этой базы данных, но без собственно данных:
# su - postgres
$ pg_dump -s -d redmine_pgsql > redmine_pgsql.sql
Теперь можно удалить этого пользователя и его базу данных, от них нам больше ничего не нужно:
$ dropdb redmine_pgsql
$ dropuser redmine_pgsql
$ exit
Теперь можно удалить и каталог с настройками экземпляра pgsql:
# cd /etc/redmine
# rm -R pgsql
Как стало ясно в процессе дальнейших попыток воспользоваться дампом, его нужно откорректировать:
  • Удалить комментарии и пустые строки, т.к. pgloader ожидает, что в каждой строчке будет указан какой-то запрос, который что-то изменяет,
  • Удалить запрос, добавляющий описание языка PL/PgSQL,
  • Удалить двойные кавычки вокруг имён полей, совпадающих с зарезервированными словами,
  • Сменить владельца базы данных с redmine_pgsql на владельца базы данных, которую будем переносить из MySQL в PostgreSQL.
Сделать это можно вот так:
$ cat redmine_pgsql.sql | sed -e 's/^--.*$//g; s/^COMMENT .*$//; /^$/d; s/"//g; s/TO redmine_pgsql/TO redmine_default/g' > redmine_default.sql
Теперь нужно поделить файл redmine_default.sql на две части. Первый должен создать структуру базы данных без ограничений и внешних ключей, а второй - добавлять их. К счастью, стандартный дамп, созданный pg_dump, можно легко поделить на нужные нам части. Первая часть заканчивается запросами вида "ALTER TABLE ONLY ... ALTER COLUMN ... SET DEFAULT ...", а вторая часть начинается запросами вида "ALTER TABLE ONLY ... ADD CONSTRAINT ...". В результате должны получиться файлы redmine_default1.sql и redmine_default2.sql

Приведу ссылки на эти файлы: redmine_default1.sql и redmine_default2.sql Они соответствуют версии Remine, поставляющейся в репозиториях Debian Jessie (версии пакетов - 3.0~20140825-5). Если у вас именно эта версия Redmine, можно пропустить весь этот раздел и сразу воспользоваться этими файлами.

Собственно перенос данных

Если pgloader ещё не установлен, установим его:
# apt-get install pgloader
Теперь создадим файл redmine_default.sql с настройками миграции:
LOAD DATABASE
  FROM mysql://redmine_default:password@localhost/redmine_default
  INTO postgresql://redmine_default:password@localhost/redmine_default

WITH include no drop,
     truncate,
     create no tables,
     create no indexes,
     no foreign keys,
     reset sequences,
     data only

SET maintenance_work_mem TO '128MB',
    work_mem to '12MB'

BEFORE LOAD EXECUTE redmine_default1.sql
Теперь отключаем Redmine, чтобы данные не менялись в процессе переноса в новую СУБД:
# /etc/init.d/uwsgi stop redmine
Открываем файл с настройками подключения к базе данных мигрируемого экземпляра default. В данном случае это файл /etc/redmine/default/database.yml, который в данный момент настроен на использование MySQL. Нужно создать точно такого же пользователя и базу данных в PostgreSQL:
# su - postgres
$ createuser -P redmine_default
$ createdb -E UTF-8 -O redmine_default redmine_default
$ exit
Запускаем миграцию:
$ pgloader redmine_default.load
После миграции открываем снова файл с настройками подключения к базе данных мигрируемого экземпляра default. Это файл /etc/redmine/default/database.yml, заменяем в нём тип базы данных с mysql2 на postgresql.

Теперь можно запустить uwsgi, чтобы Redmine вновь стал доступен для пользователей. Тестируем, всё ли правильно работает. Дальше можно перенести другие экземпляры Redmine и по окончании миграции удалить пакет Redmine для работы с MySQL:
# apt-get purge redmine-mysql

воскресенье, 19 июля 2015 г.

Чиним Redmine после обновления Debian до Jessie

После обновления Debian с Wheezy до Jessie перестал работать Redmine, настроенный по заметке Установка Redmine в Debian Wheezy.

Во-первых, поскольку в дистрибутиве Jessie вместо плагинов uwsgi_rack_ruby18 и uwsgi_rack_ruby191 имеется только плагин uwsgi_rack_ruby21, использовать теперь нужно именно его.

Во-вторых, в каталоге, на который указывала опция rails, появился файл config.ru, поэтому uwsgi предлагает воспользоваться опцией rack, которой и нужно указать этот файл. Однако, одной этой опции rack недостаточно для правильной работы, нужно ещё указать опцию chdir, указав в ней путь к каталогу, где находится приложение.

Опция post-buffering теперь не обязательна, поэтому её можно убрать.

В результате получился такой вот файл /etc/uwsgi/apps-available/redmine.ini:
[uwsgi]

procname = uwsgi-redmine
procname-master = uwsgi-redmine-master
  
plugin = uwsgi_rack_ruby21
env = RAILS_RELATIVE_URL_ROOT=/redmine
env = RAILS_ENV=production
chdir = /usr/share/redmine
rack = /usr/share/config.ru
processes = 2
Это всё было довольно быстро исправлено, но этого оказалось не достаточно. Осталась ещё одна проблема, которая заключалась в том, что redmine наотрез отказывался принимать во внимание переменную окружения RAILS_RELATIVE_URL_ROOT, которая позволяла получить доступ к приложению не через корневой каталог сервера, а поместить его в подкаталог. В данном случае этим каталогом являлся каталог /redmine/.

Вмешиваться в код приложения не хотелось, поэтому я искал другой способ исправить ситуацию. Чего я только не пробовал... В конце концов приспособил комментарий habermann24 commented on 13 May и создал файл /etc/redmine/config.ru со следующим содержимым:
require ::File.expand_path('/usr/share/redmine/config/environment', __FILE__)
map ENV['RAILS_RELATIVE_URL_ROOT'] || '/' do
  run RedmineApp::Application
end
Этот файл я указал в конфигурации /etc/uwsgi/apps-available/redmine.ini, заменив значение опции rack:
rack = /etc/redmine/config.ru
После добавления такого костыля и перезапуска uwsgi, всё заработало как положено.

К сожалению, подобные недоработки в Debian стали встречаться чаще. Может быть качество объективно падает, а может быть я чаще стал пользоваться всякими маргинальными программами и поэтому подобные недоработки просто стали мне чаще попадаться.

воскресенье, 12 июля 2015 г.

Миграция Zabbix с MySQL на PostgreSQL

Периодически замечал в iotop на своём домашнем компьютере, что самую высокую нагрузку по вводу-выводу создаёт MySQL. В интернете встречал мнения, что PostgreSQL по сравнению с MySQL более стабильно ведёт себя в условиях дефицита производительности дисковой подсистемы. Больше всего MySQL на этом компьютере нагружался Zabbix'ом, поэтому ради эксперимента решил попробовать перевести Zabbix на использование PostgreSQL.

Довольно долго я пытался экспортировать данные из MySQL при помощи штатного инструмента mysqldump в виде, пригодном для последующего экспорта в PostgreSQL. У этой утилиты имеется опция, позволяющая экспортировать в формате, совместимом с PostgreSQL. Несколько дополнительных опций, чтобы отключить директивы, отключить создание таблиц, добавить явные имена колонок в запросы INSERT, позволили получить результат с виду пригодный для импорта в PostgreSQL.

Тут, однако, возникла проблема. Данные таблиц в дампе шли в порядке, соответствюущем алфавитному порядку имён таблиц. Это вызывало проблемы с обработкой внешних ключей. Почему-то на тот момент я не догадался заглянуть в файл scheme.sql и создавал схему базы данных сразу с ограничениями и внешними ключами. Для решения этой проблемы я написал скрипт, который брал информацию о внешних ключах из базы данных information_schema и сортировал таблицы так, чтобы ссылающиеся таблицы шли после тех, на которые они ссылаются. Экспортировал данные я именно в этом порядке.

Однако когда проблема с внешними ключами была решена не самым простым путём, всплыло несколько досадных мелочей:
  • в полученном дампе внутри одинарных кавычек двойные кавычки экранировались обратным слэшем, в то время как PostgreSQL принимал их без экранирования,
  • одинарные кавычки в тех же строках тоже экранировались обратным слэшем, но PostgreSQL ждал, что они просто будут продублированы,
  • двоичные данные сохранялись в виде закавыченной последовательности байтов, в то время как PostgreSQL ждал двоичные данные в виде шестнадцатеричных цифр с префиксом 0x.
Попытки воспользоваться sed'ом для исправления недостатков не привели к успеху и я решил попробовать pgloader.

После продолжительных мучений с документацией pgloader и PostgreSQL в поисках правильной обработки зависимостей внешних ключей, я наконец-то догадался заглянуть в файл schema.sql и поделил его на две части. После этого pgloader отработал без запинки. Получившимся рецептом миграции и спешу поделиться.

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

1. Установка пакетов

Установим СУБД, если она ещё не установлена:
# apt-get install postgresql
Установим пакет pgloader, с помощью которого будем переносить содержимое базы из MySQL в PostgreSQL:
# apt-get install pgloader
Доустановим пакет для работы веб-интерфейса с СУБД PostgreSQL:
# apt-get install php5-pgsql
Чтобы новый модуль можно было использовать из PHP, перезапустим php5-fpm:
# systemctl restart php5-fpm
2. Подготовка СУБД

Правим файл конфигурации аутентификации пользователей /etc/postgresql/9.4/pg_hba.conf, заменяя первую строку на вторую:
local   all             all                                     peer
local   all             all                                     md5
По умолчанию, при подключении к UNIX-сокету, PostgreSQL определяет учётную запись, под которой работает подключившийся процесс и автоматически создаёт подключение от имени одноимённого пользователя из СУБД. Пароль при этом не запрашивается. Меняя эту строчку, мы будем требовать у подключившегося процесса явным образом указать имя пользователя СУБД и его пароль.

После этого перезапустим сервер базы данных, чтобы новые настройки вступили в силу:
# systemctl restart postgresql
Теперь из сеанса пользователя root заходим под пользователем postgres:
# su - postgres
От имени пользователя postgres создаём пользователя базы данных с именем zabbix:
$ createuser -P zabbix
Ключ -P означает, что будет запрошен пароль нового пользователя.

От имени пользователя postgres создаём саму базу данных с именем zabbix, владеть которой будет только что созданный пользователь с именем zabbix:
$ createdb -E UTF-8 -O zabbix zabbix
Опция -E UTF-8 означает, что текстовая информация в базе данных будет храниться в кодировке UTF-8, а опция -O задаёт пользователя, который будет владельцем базы данных.

3. Подготовка к переносу данных

Теперь возьмём файл database/postgresql/schema.sql, имеющийся в дистрибутиве Zabbix и поделим его на две части. В первой части будут запросы, создающие таблицы (CREATE TABLE), а во второй - создающие внешние ключи и ограничения (ALTER TABLE). Назовём эти файлы schema1.sql и schema2.sql

Создадим файл zabbix.load, содержащий настройки для переноса данных:
LOAD DATABASE
  FROM mysql://zabbix:zabbix_password@localhost/zabbix
  INTO postgresql://zabbix:zabbix_password@localhost/zabbix

WITH include no drop,
     truncate,
     create no tables,
     create no indexes,
     no foreign keys,
     reset sequences,
     data only

SET maintenance_work_mem TO '128MB',
    work_mem to '12MB'

BEFORE LOAD EXECUTE schema1.sql

AFTER LOAD EXECUTE schema2.sql;
Подготовим пакет с Zabbix-сервером, работающим с PostgreSQL или подключим репозиторий с этим пакетом. Как это сделать - решайте сами.

4. Перенос данных

Перед переносом данных остановим Zabbix-сервер:
# systemctl stop zabbix-server
Закроем доступ к веб-интерфейсу Zabbix, остановив php5-fpm (можно просто запретить доступ к веб-интерфейсу Zabbix через настройки nginx):
# systemctl stop php5-fpm
Теперь приступим к собственно переносу данных (в текущем каталоге должны быть подготовленные ранее файлы schema1.sql, schema2.sql, zabbix.load):
$ pgload zabbix.load
Пока данные переносятся, удалим старый Zabbix-сервер для MySQL:
# dpkg -r zabbix-server-mysql
Установим Zabbix-сервер для PostgreSQL :
# dpkg -i zabbix-server-pgsql_2.4.5-1+jessie_amd64.deb
Теперь, если данные уже перенеслись, можно запускать новый Zabbix-сервер:
# systemctl start zabbix-server
Отредактируем файл /etc/zabbix/web/zabbix.conf.php, заменив первую строчку на вторую:
$DB['TYPE']     = 'MYSQL';
$DB['TYPE']     = 'POSTGRESQL';
Запускаем php5-fpm (или открываем доступ к веб-интерфейсу Zabbix через настройки nginx):
# systemctl start php5-fpm
5. Проверка результата

Заглядываем в журналы Zabbix-сервера /var/log/zabbix/zabbix_server.log и заходим в веб-интерфейс Zabbix, проверяя, что всё работает нормально.

Кстати, наблюдение за нагрузкой на дисковую подсистему по такому не совсем чёткому параметру как iowait, показало, что нагрузка действительно упала: