Некоторое время назад я задумался о повышении доступности серверов своего проекта. Всё решалось относительно легко за исключением MySQL базы данных. Репликация типа мастер-мастер создаёт проблемы с синхронизацией индексов, а кластерное решение NDBcluster хоть и стремительно развивается, но до сих под не годится для миграции на него готового проекта ввиду большого списка различий и ограничений.
Но есть альтернативное решение под названием Galera Cluster, на котором основан Percona XtraDB Cluster, об установке, настройке и тестировании которого я и расскажу.
Чем это лучше NDBcluster?
Во-первых: меньшим количеством ограничений (список ограничений NDBcluster: dev.mysql.com/doc/refman/5.5/en/mysql-cluster-limitations.html, список ограничений XtraDB Cluster: www.percona.com/doc/percona-xtradb-cluster/limitation.html).
Мне пришлось только переделать немногочисленные MyISAM таблицы в InnoDB.
Во-вторых: множественными патчами MySQL движка кода от Перконы.
В-третьих: возможностью работы в двухнодовой конфигурации (сразу оговорюсь, что это годно разве что для тестирования).
Ну и наконец, форки MySQL сейчас в тренде :)
Чем это лучше Galera Cluster прикрученного к форку MySQL?
Наличием утилиты xtrabackup, которая позволяет на лету делать бэкапы. Это полезно тем, что при подключении новой ноды к кластеру не придётся останавливать никакую из работающих нод для слива базы с неё.
Установка
Установка под Ubuntu, как водится, элементарна:
gpg --keyserver hkp://keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A
gpg -a --export CD2EFD2A | sudo apt-key add -
sudo apt-get update
sudo apt-get install percona-xtradb-cluster-server-5.5
Иногда ругается на нерешаемые зависимости. Тогда достаточно сначала установить зависимости, а потом сам сервер.
Сначала ничего не меняя в настройках надо зайти в mysql консоль (достаточно на узле, который будут запущен первым) и добавить юзера для бэкапа:
grant RELOAD, LOCK TABLES, REPLICATION CLIENT, FILE on *.* to backup@localhost identified by 'password';
Потом потребуется одна небольшая переделочка. Дело в том что в Убунте симлинк с sh ведёт на dash, а стартовый скрипт кластера заточен под bash. Если нет никаких противопоказаний, можно уладить это общесистемно:
dpkg-reconfigure dash
Ответить «нет».
Так как базы после запуска кластера станут полностью идентичными, то и пароль системного юзера на всех нодах станет как на первой. Поэтому надо скопировать файл /etc/mysql/debian.cnf с сервера, который будет запущен первым на остальные.
Мои конфиги выглядят так:
[mysqld_safe]
wsrep_urls=gcomm://192.168.1.1:3400,gcomm://192.168.1.2:3400,gcomm://
[mysqld]
port=3306
socket=/var/run/mysqld/mysqld.sock
datadir=/var/lib/mysql
basedir=/usr
user=mysql
log_error=error.log
binlog_format=ROW
wsrep_provider=/usr/lib/libgalera_smm.so
wsrep_sst_receive_address=192.168.1.1:3500
wsrep_node_incoming_address=192.168.1.1
wsrep_slave_threads=2
wsrep_cluster_name=cluster0
wsrep_provider_options="gmcast.listen_addr=tcp://192.168.1.1:3400;"
wsrep_sst_method=xtrabackup
wsrep_sst_auth=backup:password
wsrep_node_name=node0
innodb_locks_unsafe_for_binlog=1
innodb_autoinc_lock_mode=2
innodb_buffer_pool_size=5000M
innodb_log_file_size=256M
innodb_log_buffer_size=4M
[client]
port=3306
socket=/var/run/mysqld/mysqld.sock
Проверьте расположение libgalera_smm.so.
Значение параметра wsrep_slave_threads рекомендуется ставить как количество ядер*4.
В wsrep_sst_auth указывается логин и пароль пользователя для бэкапов.
innodb_buffer_pool_size, innodb_log_file_size, innodb_log_buffer_size — влияют на производительность, и подбираются экспериментальным путём. В моём случае на каждом узле стоит 8 гигов ОЗУ.
Для добавления нод надо добавлять их в строку wsrep_urls (в конце строки должна быть пустая запись).
Все ip адреса, встречающиеся в файле (кроме строки wsrep_urls) указывают на адрес текущей ноды. Их и надо менять при распространении этого файла на остальные узлы. Также надо менять имя ноды в wsrep_node_name.
В моей настройке порт 3400 используется для синхронизации, порт 3500 — для заливки дампа, порт 3306 (стандартный) — для подключения клиента.
Можно запустить несколько нод на одной машине, выдав им разные порты. Если решите так сделать, то надо насоздавать несколько конфигурационных файлов в /etc/mysql и запускать сервер например такой командой:
/usr/bin/mysqld_safe --defaults-file=/etc/mysql/my0.cnf
Учтите, что xtrabackup умеет подключаться только по стандартному сокету /var/run/mysqld/mysqld.sock (параметры из конфига игнорирует). Так что в этом случае придётся его не использовать: wsrep_sst_method=rsync
Ну и в завершение, перезапускаем демона:
sudo service mysql restart
Если что-то пошло не так, смотрим в /var/lib/mysql/error.log.
Обычно надо стереть /var/lib/mysql/ib_logfile* из-за изменения размеров лога в конфиге.
Иногда проще стереть вообще весь /var/lib/mysql/ (если в базах нет ничего ценного) и пересоздать дефолтные базы:
mysql_install_db
Ещё некоторые возможные ошибки я привёл в конце статьи.
Количество узлов
Производитель рекомендует не менее трёх, но есть отличия от NDB: изначально все ноды одного ранга и имеют одинаковый функционал. Ничто не мешает сделать и 2 ноды.
С двухнодовой конфигурацией всё просто и грустно: работает пока не пропадёт связь между узлами. В этом случае кластер в целях защиты от split-brain вообще не даёт ничего делать. Есть пара опций для конфига, при которых он будет работать даже при обрыве связи, но, понятно дело, вреда от них больше чем пользы — при появлении связи то мы получим 2 разных копии базы, которые непонятно как объединять.
Вариант с тремя нодами в моём случае не подходил, так как у меня только 2 сервера на колокейшене, и добавленный к ним третий где-нибудь на офисном канале сильно бы подпортил производительность, ведь скорость работы кластера определяется скоростью работы самого медленного его узла.
Но есть решение: Galera Arbitator. Это неполноценный узел, который не хранит баз, и которому не нужен быстрый канал, но пока нода держит с ним связь она будет продолжать работать. Этот узел настолько неполноценный, что он даже конфиг не использует, все параметры передаются ему через командную строку.
Из параметров нужен только адрес одной из нормальных нод и название кластера:
garbd -a gcomm://192.168.1.1:3400 -g cluster0
Тесты
Тестировал стандартной утилитой sql-bench из пакета test сначала узлы по отдельности, а потом кластер. Софт при этом не менялся — percona-xtradb-cluster.
Установка и запуск тестового пакета:
apt-get install percona-server-test-5.5 libdbd-mysql-perl
cd /usr/share/sql-bench/sql-bench/
perl run-all-tests --server=mysql --user=root --password=<password>
Сначала тестировал на двух идентичных быстрых машинах с SSD.
Результаты первого прогона выкинул, так как они были в насколько раз лучше последующих (спишем на чистые SSD).
Это 1 нода:
alter-table: Total time: 17 wallclock secs ( 0.04 usr 0.00 sys + 0.00 cusr 0.00 csys = 0.04 CPU)
ATIS: Total time: 7 wallclock secs ( 2.48 usr 0.16 sys + 0.00 cusr 0.00 csys = 2.64 CPU)
big-tables: Total time: 10 wallclock secs ( 1.87 usr 0.34 sys + 0.00 cusr 0.00 csys = 2.21 CPU)
connect: Total time: 64 wallclock secs (19.80 usr 5.68 sys + 0.00 cusr 0.00 csys = 25.48 CPU)
create: Total time: 548 wallclock secs ( 3.35 usr 1.66 sys + 0.00 cusr 0.00 csys = 5.01 CPU)
insert: Total time: 531 wallclock secs (155.04 usr 19.15 sys + 0.00 cusr 0.00 csys = 174.19 CPU)
select: Total time: 168 wallclock secs (17.93 usr 1.90 sys + 0.00 cusr 0.00 csys = 19.83 CPU)
transactions: Test skipped because the database doesn't support transactions
wisconsin: Total time: 5 wallclock secs ( 1.31 usr 0.18 sys + 0.00 cusr 0.00 csys = 1.49 CPU)
Это кластер:
alter-table: Total time: 21 wallclock secs ( 0.04 usr 0.05 sys + 0.00 cusr 0.00 csys = 0.09 CPU)
ATIS: Total time: 21 wallclock secs ( 2.76 usr 0.30 sys + 0.00 cusr 0.00 csys = 3.06 CPU)
big-tables: Total time: 17 wallclock secs ( 1.98 usr 0.40 sys + 0.00 cusr 0.00 csys = 2.38 CPU)
connect: Total time: 67 wallclock secs (21.13 usr 5.59 sys + 0.00 cusr 0.00 csys = 26.72 CPU)
create: Total time: 597 wallclock secs ( 3.55 usr 1.55 sys + 0.00 cusr 0.00 csys = 5.10 CPU)
insert: Total time: 1710 wallclock secs (164.66 usr 35.25 sys + 0.00 cusr 0.00 csys = 199.91 CPU)
select: Total time: 187 wallclock secs (19.49 usr 2.44 sys + 0.00 cusr 0.00 csys = 21.93 CPU)
transactions: Test skipped because the database doesn't support transactions
wisconsin: Total time: 47 wallclock secs ( 1.62 usr 0.88 sys + 0.00 cusr 0.00 csys = 2.50 CPU)
Падение в 3 раза на операциях записи и серьёзное (если не сказать эпичное) падение на висконсиновском тесте, где по идее должны перемежаться операции чтения и записи.
Не знаю почему ругается на транзакции. На самом деле они работают.
Возникло предположение, что всё упирается в скорость работы сети, поэтому протестировал и на машинках (уже не идентичных) с винчестерами.
Первая нода отдельно:
alter-table: Total time: 55 wallclock secs ( 0.04 usr 0.02 sys + 0.00 cusr 0.00 csys = 0.06 CPU)
ATIS: Total time: 10 wallclock secs ( 2.40 usr 0.14 sys + 0.00 cusr 0.00 csys = 2.54 CPU)
big-tables: Total time: 7 wallclock secs ( 1.23 usr 0.15 sys + 0.00 cusr 0.00 csys = 1.38 CPU)
connect: Total time: 53 wallclock secs (16.31 usr 7.65 sys + 0.00 cusr 0.00 csys = 23.96 CPU)
create: Total time: 3215 wallclock secs ( 2.58 usr 0.83 sys + 0.00 cusr 0.00 csys = 3.41 CPU)
insert: Total time: 541 wallclock secs (142.41 usr 22.53 sys + 0.00 cusr 0.00 csys = 164.94 CPU)
select: Total time: 154 wallclock secs (12.66 usr 1.34 sys + 0.00 cusr 0.00 csys = 14.00 CPU)
transactions: Test skipped because the database doesn't support transactions
wisconsin: Total time: 4 wallclock secs ( 1.15 usr 0.29 sys + 0.00 cusr 0.00 csys = 1.44 CPU)
Вторая нода отдельно:
alter-table: Total time: 59 wallclock secs ( 0.03 usr 0.03 sys + 0.00 cusr 0.00 csys = 0.06 CPU)
ATIS: Total time: 11 wallclock secs ( 2.35 usr 0.23 sys + 0.00 cusr 0.00 csys = 2.58 CPU)
big-tables: Total time: 11 wallclock secs ( 1.92 usr 0.30 sys + 0.00 cusr 0.00 csys = 2.22 CPU)
connect: Total time: 64 wallclock secs (19.67 usr 5.84 sys + 0.00 cusr 0.00 csys = 25.51 CPU)
create: Total time: 4592 wallclock secs ( 3.90 usr 1.39 sys + 0.00 cusr 0.00 csys = 5.29 CPU)
insert: Total time: 581 wallclock secs (148.16 usr 19.80 sys + 0.00 cusr 0.00 csys = 167.96 CPU)
select: Total time: 168 wallclock secs (18.45 usr 2.07 sys + 0.00 cusr 0.00 csys = 20.52 CPU)
transactions: Test skipped because the database doesn't support transactions
wisconsin: Total time: 5 wallclock secs ( 1.18 usr 0.25 sys + 0.00 cusr 0.00 csys = 1.43 CPU)
Кластер:
alter-table: Total time: 110 wallclock secs ( 0.04 usr 0.02 sys + 0.00 cusr 0.00 csys = 0.06 CPU)
ATIS: Total time: 496 wallclock secs ( 1.61 usr 0.17 sys + 0.00 cusr 0.00 csys = 1.78 CPU)
big-tables: Total time: 116 wallclock secs ( 1.02 usr 0.16 sys + 0.00 cusr 0.00 csys = 1.18 CPU)
connect: Total time: 34 wallclock secs (10.98 usr 2.49 sys + 0.00 cusr 0.00 csys = 13.47 CPU)
create: Total time: 4638 wallclock secs ( 2.42 usr 0.91 sys + 0.00 cusr 0.00 csys = 3.33 CPU)
insert: Estimated total time: 43470.8 wallclock secs (106.50 usr 15.34 sys + 0.00 cusr 0.00 csys = 121.84 CPU)
select: Total time: 631 wallclock secs (11.02 usr 1.02 sys + 0.00 cusr 0.00 csys = 12.04 CPU)
transactions: Test skipped because the database doesn't support transactions
wisconsin: Total time: 1576 wallclock secs ( 1.37 usr 0.44 sys + 0.00 cusr 0.00 csys = 1.81 CPU)
Однако предположение не подтвердилось: операция записи на винчестерных компьютерах в кластере замедлилась аж в 10 раз. Чем это объяснить — я не знаю.
Что касается использования это в реальном продакшене — каждый выбирает для себя. Мне такое относительное снижение производительности некритично ввиду малых абсолютных цифр.
Возможные проблемы и методы их решения
1.
WSREP: Process completed with error: wsrep_sst_xtrabackup 'donor' '192.168.1.1:6000/xtrabackup_sst' 'backup:password' '/var/lib/mysql2/' '/etc/mysql/my2.cnf' '9bdd7773-0cb4-11e2-0800-8e876ebc6b70' '0' '0': 22 (Invalid argument)
xtrabackup не смог подключиться к базе. Вы точно не пытаетесь сделать несколько нод на одной машине? Смотрите подробности в innobackup.backup.log.
2.
121002 15:19:54 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql0
/usr/bin/mysqld_safe: 172: [: gcomm://: unexpected operator
Вместо bash используется другой интерпретатор. Либо в mysqld_safe измените первую строку либо правьте симлинки.
3. Если MyISAM таблицы создаются, но не наполняются из дампа, то так и должно быть: поддерживается только InnoDB.
4. Если на все команды пишущие или читающие данные, MySQL клиент выдаёт «Invalid command»: это защита от split-brain при отвале связи между узлами.
Автор: unwrecker