Налаштування MySQL Multi‐Source Replication за допомогою GTID
Привіт усім! Мене звати Ігор Стецюк, я DevOps-інженер в компанії Futurra Group. Ми працюємо у сфері EdTech, зокрема розробляємо та просуваємо сервіс MathMaster — застосунок, що надає математичні консультації 24/7.
У цій статті я хочу поділитися технічним рішенням, що значно полегшило робочий флоу та дозволило забезпечити надійність та доступність наших даних, а саме — налаштування MySQL Multi-Source Replication на ZFS-файловій системі. Цю схему ми використовуємо для потреб команди аналітиків та розробників, коли потрібно перевірити поведінку коду в робочому середовищі на живих даних, а також для створення резервних копій, що є критично важливим для продукту, адже ми робимо знімки всіх баз даних щогодини.
У чому була проблема
У міру розвитку продукту в нашій роботі з MySQL-базами даних в хмарі DigitalOcean накопичувалася низка питань, що потребували вирішення.
По-перше, був запит від команди аналітиків — треба прискорити важкі вибірки, а також хотілось отримувати всі необхідні дані з єдиного місця.
По-друге, треба було пришвидшити процес створення резервних копій з read-only серверів, та й хотілося робити їх частіше. Для вирішення цих задач спробували вертикально масштабувати read-only сервер, проте це все ще не покривало всіх наших потреб, адже, наприклад, той же mysqldump
постійно перекачує базу, а єдиного місця з усіма БД усе ще не було.
І, звісно, бажання одразу протестувати все на проді без шкоди реальним даним завжди було і буде актуальним.
Тож я запропонував ідею поєднання Multi-Source та ZFS і швидко перейшов до реалізації цієї схеми. Далі детально та покроково розповідаю, як саме я діяв.
Для початку трошки теорії
Спочатку розглянемо, як працює механізм реплікації, а також інструменти та поняття, які будуть застосовані в процесі.
MySQL Multi-Source Replication — це механізм, який дозволяє реплікувати дані з кількох джерел у єдину БД. Це особливо корисно у випадку, коли є декілька баз, які містять пов’язані дані і ви хочете мати єдину точку доступу до них. Саме за допомогою Multi-Source Replication ви можете сконфігурувати таку точку та забезпечити автоматичну реплікацію даних між джерелами.
GTID (Global Transaction Identifier) в MySQL — це унікальний ідентифікатор для транзакцій у реплікаційній топології. Використовуючи GTID, ви можете однозначно ідентифікувати кожну транзакцію в БД, незалежно від того, на якому сервері вона відбулася.
GTID складається з двох основних частин:
- UUID — унікальний ідентифікатор для кожного сервера БД у реплікаційній топології.
- Sequence Number — порядковий номер транзакції, який відображається на конкретному сервері. Кожен сервер зберігає цей локальний лічильник для відстеження номерів транзакцій, які відбулися на цьому сервері.
ZFS файлова система дає можливість дешево створювати знімки файлової системи, використовуючи ZFS Snapshot, а також відновлювати стан файлової системи та видаляти застарілі знімки.
Розглянувши основні концепти, пов’язані з налаштуванням Multi-Source Replication на ZFS-файловій системі, перейдемо до конкретних прикладів, щоб відстежити, як можна використовувати цей механізм реплікації для убезпечення даних.
Схема інфраструктури
Так історично склалося, що наші кластери баз даних перебувають в DigitalOcean. Цей провайдер дає можливість легко створювати та налаштовувати інфраструктуру, має зручний API та детальну документацію.
Крім того, ми обрали саме цього провайдера, адже він має Terraform провайдер, який активно розвивається та підтримується в належному стані. Також DigitalOcean має досить привабливу цінову політику, що дозволяє зменшити витрати на утримання інфраструктури.
Ми маємо дві БД як сервіс DigitalOcean, окремий сервер з базою даних та сам сервер реплікації.
DB1 — DBaaS (digitalocean)
DB2 — DBaaS (digitalocean)
DB3 — self-hosted (Percona MySQL Server 8.0)
mysql-slave — self-hosted (Percona MySQL Server 8.0). Розгорнуто на dedicated server.
На схемі зображено два незалежних один від одного кластери БД mysql з master (RW) ⇒ slave (RO) реплікаціями, окрема БД (DB3), що перебуває у віртуальній машині, та Multi-Source Replication node — сервер, який буде реплікувати всі наші кластери.
Практика
Налаштовуємо Multi-Source Replication node
OS Ubuntu 22.04.3 LTS
Для ZFS нам потрібен окремий диск. У прикладі я використав /dev/sda
. Для встановлення та підготовки ZFS зробимо такі кроки:
echo "deb http://archive.ubuntu.com/ubuntu $(lsb_release -cs) main universe" | tee /etc/apt/sources.list.d/zfs.list apt update && apt install -y zfsutils-linux zpool create -m none zp_mysql /dev/sda zfs set compression=lz4 atime=off logbias=throughput zp_mysql zfs create -o recordsize=128K -o atime=off -o compression=lz4 zp_mysql/mysql zfs create -o mountpoint=/mysql_data/log -o recordsize=128K -o atime=off -o compression=lz4 zp_mysql/mysql/log zfs create -o mountpoint=/mysql_data/data -o recordsize=16k -o primarycache=metadata -o atime=off -o compression=off zp_mysql/mysql/data
Переносимо MySQL до нового сховища
Спочатку змінимо datadir
налаштування в [mysqld]
-секції конфігураційного файлу mysql:
[mysqld] datadir = /mysql_data/data/
Внесемо інші налаштування до mysql та запустимо сервіс:
[mysqld] # logs log_bin = /mysql_data/log/binlog relay_log = /mysql_data/log/relaylog relay_log_index = /mysql_data/log/relaylog.index binlog_expire_logs_seconds = 604800 max_binlog_size = 100M relay_log_recovery = on master_info_repository = 'TABLE' relay_log_info_repository = 'TABLE' # GTID Based Replication Parameter server-id=19 gtid_mode=ON enforce-gtid-consistency=1 log_slave_updates=1 read-only=1 super_read_only=1 # For ZFS innodb_doublewrite = 0 # ZFS is transactional innodb_use_native_aio = 0 innodb_flush_method = fsync innodb_read_io_threads = 10 innodb_write_io_threads = 10
read-only=1
та super_read_only=1
— ці параметри необхідно ввімкнути, щоб захистити дані від внесення змін, навіть тим користувачам, що мають адміністративні права.
Переносимо дані mysql до нового сховища:
systemctl stop mysql rsync -auxHAXSv --chown=mysql:mysql /var/lib/mysql/* /mysql_data/data/ systemctl start mysql
Налаштовуємо DB-3 node
OS Ubuntu 22.04.3 LTS
Додамо такі налаштування в конфігурацію сервера:
[mysqld] bind-address=0.0.0.0 log_bin=/var/lib/mysql/binlog # GTID Based Replication Parameter server-id=3 gtid_mode=ON enforce-gtid-consistency=1
server-id=3
— цей параметр встановлює ідентифікатор сервера для реплікації. Кожен сервер повинен мати унікальний ідентифікатор в системі реплікації.
gtid_mode=ON
— цей параметр вмикає режим GTID (Global Transaction Identifier) для реплікації. GTID дозволяє ідентифікувати та відстежувати транзакції в реплікаційному процесі.
enforce-gtid-consistency=1
— цей параметр вимагає, щоб транзакції на репліках були відповідно синхронізовані з джерелом за допомогою GTID. Він забезпечує консистентність даних у реплікаційному середовищі.
Змінну enforce-gtid-consistency
слід встановлювати на ON у всіх середовищах реплікації MySQL, де важлива стійкість даних. Це запобігатиме виконанню операцій, які можуть пошкодити дані на реплікованому сервері.
Підготуємо DigitalOcean кластери для реплікації
Отримуємо ідентифікатори (ID) наших кластерів
export DIGITALOCEAN_TOKEN="dop_v1_1d05faa83e157d93ddbe2d7a0b7e4c9497679dce5745761995d70d641b387f2a" curl -sX GET \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ "https://api.digitalocean.com/v2/databases" | jq --sort-keys '.databases[]|{id, name, region, status}' # Output { "id": "29ad047f-9691-4ba3-96f4-4653a2c6400c", "name": "db-1", "region": "fra1", "status": "online" } { "id": "8689da6d-b09c-4423-b4da-1d38498dabc8", "name": "db-2", "region": "fra1", "status": "online" }
Нам необхідно змінити binlog_retention_period
:
curl -sX PATCH \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ -d '{"config": {"binlog_retention_period": 86400}}' \ "https://api.digitalocean.com/v2/databases/29ad047f-9691-4ba3-96f4-4653a2c6400c/config" curl -sX PATCH \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ -d '{"config": {"binlog_retention_period": 86400}}' \ "https://api.digitalocean.com/v2/databases/8689da6d-b09c-4423-b4da-1d38498dabc8/config"
Перевіримо зміни:
curl -sX GET \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ "https://api.digitalocean.com/v2/databases/29ad047f-9691-4ba3-96f4-4653a2c6400c/config" | jq '.config.binlog_retention_period' curl -sX GET \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ "https://api.digitalocean.com/v2/databases/8689da6d-b09c-4423-b4da-1d38498dabc8/config" | jq '.config.binlog_retention_period'
Саме час налаштувати реплікацію
Підмикаємося до наших кластерів (вихідні сервери) та створюємо обліковий запис для реплікації:
#DB-RW-1 node CREATE USER 'replication_user'@'64.225.109.74' IDENTIFIED WITH mysql_native_password BY '1DPpYPOKYi4DLPskGdYh'; GRANT REPLICATION SLAVE ON *.* TO 'replication_user'@'64.225.109.74'; FLUSH PRIVILEGES; #DB-RW-2 node CREATE USER 'replication_user'@'64.225.109.74' IDENTIFIED WITH mysql_native_password BY 'ILce3w929XGNA1ayURRa'; GRANT REPLICATION SLAVE ON *.* TO 'replication_user'@'64.225.109.74'; FLUSH PRIVILEGES; #DB-3 node CREATE USER 'replication_user'@'64.225.109.74' IDENTIFIED WITH mysql_native_password BY '6yKHQtoqewzxek8HTkul'; GRANT REPLICATION SLAVE ON *.* TO 'replication_user'@'64.225.109.74'; FLUSH PRIVILEGES;
Нам необхідно налаштувати реплікацію з наступних баз:
parrot
таfish
з серверу DB-1;dogs
з серверу DB-2;cats
з серверу DB-3.
Робимо mysqldump
баз, щоб не навантажувати головний сервер; backup можна виконувати з будь-якої ноди кластеру для читання.
# DB-1-RO-1 mysqldump --single-transaction \ --quick \ --add-drop-database \ --add-drop-table \ --triggers \ --routines \ --events \ --source-data \ --databases parrots fish > db-1.sql # DB-2-RO-2 mysqldump --single-transaction \ --quick \ --add-drop-database \ --add-drop-table \ --triggers \ --routines \ --events \ --source-data \ --databases dogs > db-2.sql # DB-3 mysqldump --single-transaction \ --quick \ --add-drop-database \ --add-drop-table \ --triggers \ --routines \ --events \ --source-data \ --databases cats > db-3.sql
--single-transaction
— цей параметр забезпечує виконання резервного копіювання в одній транзакції, що дозволяє уникнути блокування таблиць на час створення резервної копії;
--quick
— дозволяє використовувати швидший метод для вибірки даних, що може бути корисним для великих таблиць;
--add-drop-database
— параметр, що додає до SQL-файлу команди для видалення БД перед її створенням;
--add-drop-table
— цей параметр додає до SQL-файлу команди для видалення таблиць перед їхнім створенням, допомагаючи уникнути конфліктів під час відновлення резервної копії;
--triggers
— параметр, що додає в резервну копію тригери, які визначені в БД;
--routines
— цей параметр додає в резервну копію збережені процедури та функції, які визначені в БД;
--events
— параметр, що додає в резервну копію заплановані події (events), які визначені в БД;
--source-data
— цей параметр додає в резервну копію дані з таблиць;
--databases
— тут вказані назви баз даних, які потрібно додати в резервну копію.
Далі нам необхідно отримати GTID-значення з кожного файлу резервної копії. Зберігаємо їх та видаляємо з дамп-файлу ці рядки. Якщо ж дамп дуже великий, можна виконати RESET MASTER після кожного відновлення з дампу. Якщо цього не зробити, ми отримаємо помилку:
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the added gtid set must not overlap with @@GLOBAL.GTID_EXECUTED
Отримуємо GLOBAL.GTID_PURGED
head -n 50 db-1.sql | less -N # Output db-1 34 SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '4d7dbef6-3777-11ee-807c-0ae964d12771:1-33'; head -n 50 db-2.sql | less -N # Output db-2 34 SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '9784567e-3777-11ee-bc2e-ea23c8e356f2:1-30'; head -n 50 db-3.sql | less -N # Output db-3 34 SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '0289f920-376d-11ee-a2ef-363cbae02daa:1-27';
Видаляємо ці рядки з файлів резервної копії, у нашому випадку це рядок 34.
sed -i 34'{/./d;}' db-1.sql sed -i 34'{/./d;}' db-2.sql sed -i 34'{/./d;}' db-3.sql
Якщо потрібно видалити декілька рядків, вказуємо початковий номер рядку та кінцевий через кому: sed -i 34,44'{/./d;}' db-1.sql
Ці три рядки GLOBAL.GTID_PURGED об’єднуємо в одну команду та виконуємо її у випадку, якщо ми вирізали
SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '4d7dbef6-3777-11ee-807c-0ae964d12771:1-33,9784567e-3777-11ee-bc2e-ea23c8e356f2:1-30,0289f920-376d-11ee-a2ef-363cbae02daa:1-27';
Усі наші дампи завантажуємо в mysql:
mysql < db-1.sql mysql < db-2.sql mysql < db-3.sql
Та нарешті створюємо репліки на сервері multi-slave:
CHANGE REPLICATION SOURCE TO SOURCE_HOST='db-1-do-user-9604885-0.b.db.ondigitalocean.com', SOURCE_PORT= 25060, SOURCE_USER='replication_user', SOURCE_PASSWORD='1DPpYPOKYi4DLPskGdYh', SOURCE_AUTO_POSITION=1 FOR CHANNEL 'db-1'; CHANGE REPLICATION SOURCE TO SOURCE_HOST='db-2-do-user-9604885-0.b.db.ondigitalocean.com', SOURCE_PORT= 25060, SOURCE_USER='replication_user', SOURCE_PASSWORD='ILce3w929XGNA1ayURRa', SOURCE_AUTO_POSITION=1 FOR CHANNEL 'db-2'; CHANGE REPLICATION SOURCE TO SOURCE_HOST='157.230.97.56', SOURCE_PORT= 3306, SOURCE_USER='replication_user', SOURCE_PASSWORD='6yKHQtoqewzxek8HTkul', SOURCE_AUTO_POSITION=1 FOR CHANNEL 'db-3';
Запускаємо слейв та перевіряємо статус:
mysql> START SLAVE; Query OK, 0 rows affected, 1 warning (0.13 sec) mysql> SELECT CHANNEL_NAME,SERVICE_STATE,LAST_ERROR_NUMBER,LAST_ERROR_MESSAGE -> FROM performance_schema.replication_connection_status; +--------------+---------------+-------------------+--------------------+ | CHANNEL_NAME | SERVICE_STATE | LAST_ERROR_NUMBER | LAST_ERROR_MESSAGE | +--------------+---------------+-------------------+--------------------+ | db-1 | ON | 0 | | | db-2 | ON | 0 | | | db-3 | ON | 0 | | +--------------+---------------+-------------------+--------------------+ 3 rows in set (0.00 sec)
Як бачимо, все добре. Детальніше статус реплік можна подивитись командою SHOW SLAVE STATUS FOR CHANNEL ’db-2’\G
ZFS snapshots & rollback
ZFS Snapshot (знімок) — це віртуальна копія файлової системи або об’єктного сховища на конкретний момент часу. Основна ідея полягає в тому, що знімки зберігають лише зміни між початковим станом файлової системи та поточним знімком, що дозволяє ефективно використовувати простір на диску. Знімки використовуються для резервного копіювання, відновлення даних, створення тестових середовищ та багатьох інших завдань.
Коли ви робите знімок в ZFS, він зберігає інформацію про стан об’єктів у файловій системі на момент створення. Знімки не вимагають створення фактичних копій даних, тому вони генеруються майже миттєво та займають мінімальний об’єм місця.
ZFS Rollback (відновлення) — це операція відновлення файлової системи до стану, що був на момент створення знімку.
Під час виконання rollback ZFS забезпечує консистентність даних: це означає, що дані повертаються до попереднього стану без ризику втрати або пошкодження. Ця операція є миттєвою, навіть на великих об’ємних накопичувачах.
Важливо зауважити, що знімки та rollback у ZFS — це операції, доступні на рівні файлової системи і вони працюють незалежно від операційки. Це робить їх потужними інструментами для керування даними та забезпечення надійності та доступності інформації.
Для створення знімків я використовую скрипт, який запускається в кроні погодинно та виконує ротацію застарілих знімків. Переглянути можна за посиланням: zfs_snapshot_rotate.sh
Нижче наведено приклад знімків з реального сервера:
chip@mysql-slave:~$ zfs list -t snapshot NAME USED AVAIL REFER MOUNTPOINT zp_mysql/mysql/data@2023-08-28_10-00-01 417M - 110G - zp_mysql/mysql/data@2023-08-28_11-00-01 190M - 110G - zp_mysql/mysql/data@2023-08-28_12-00-01 199M - 110G - zp_mysql/mysql/data@2023-08-28_13-00-01 212M - 110G - zp_mysql/mysql/data@2023-08-28_14-00-01 233M - 110G - zp_mysql/mysql/data@2023-08-28_15-00-01 244M - 110G - zp_mysql/mysql/data@2023-08-28_16-00-01 250M - 110G - zp_mysql/mysql/data@2023-08-28_17-00-01 258M - 110G - zp_mysql/mysql/data@2023-08-28_18-00-01 260M - 110G - zp_mysql/mysql/data@2023-08-28_19-00-01 247M - 110G - zp_mysql/mysql/data@2023-08-28_20-00-02 251M - 110G - zp_mysql/mysql/data@2023-08-28_21-00-01 270M - 110G - zp_mysql/mysql/data@2023-08-28_22-00-01 293M - 111G - zp_mysql/mysql/data@2023-08-28_23-00-01 301M - 111G - zp_mysql/mysql/data@2023-08-29_00-00-01 314M - 111G - zp_mysql/mysql/data@2023-08-29_01-00-01 315M - 111G - zp_mysql/mysql/data@2023-08-29_02-00-01 307M - 111G - zp_mysql/mysql/data@2023-08-29_03-00-01 281M - 111G - zp_mysql/mysql/data@2023-08-29_04-00-01 271M - 111G - zp_mysql/mysql/data@2023-08-29_05-00-01 282M - 111G - zp_mysql/mysql/data@2023-08-29_06-00-01 238M - 111G - zp_mysql/mysql/data@2023-08-29_07-00-01 219M - 111G - zp_mysql/mysql/data@2023-08-29_08-00-01 195M - 111G - zp_mysql/mysql/data@2023-08-29_09-00-01 177M - 111G -
Для того, щоб відкотитися до останньої копії, вам необхідно виконати команду zfs rollback <name>
. У нашому випадку це zfs rollback zp_mysql/mysql/data@2023-08-29_08-00-01
.
Проте якщо ви плануєте повернутися до історичної попередньої копії, необхідно видалити всі копії, що йдуть за нею. Це можна зробити автоматично, виконавши команду з ключем -r: zfs rollback -r zp_mysql/mysql/data@2023-08-29_06-00-01
.
Якщо ви перевірите стан ваших знімків, то побачите, що копій zp_mysql/mysql/data@2023-08-29_08-00-01
та zp_mysql/mysql/data@2023-08-29_07-00-01
уже не існує.
Також ми можемо, наприклад, відправити знімки на інший сервер та розгорнути необхідну нам копію БД. Можна також створити архів знімку та використовувати його для різних пісочниць розробників. Усі ці маніпуляції виконуються набагато швидше, ніж у випадку використання mysqldump
.
Висновок
Очевидно, що знімки та rollbacks у ZFS є потужними інструментами для керування даними та забезпечення надійності й доступності інформації. Знімки використовуються для резервного копіювання, відновлення даних, створення тестових середовищ та багатьох інших завдань. Rollback дозволяє скасувати всі зміни, які були зроблені після створення знімку і повернутися до попереднього стану.
За допомогою MySQL Multi-Source Replication можна налаштувати реплікацію з декількох серверів на один. Зокрема, ми розглянули налаштування реплікації з трьох серверів, а також процес налаштування знімків та rollbacks в ZFS. Описану в статті схему зручно використовувати для створення резервних копій, аналітики, «тестанути, як воно буде на проді» і водночас не завдати шкоди продакшн-даним.
Використовуючи ці інструменти, можна забезпечити надійність та доступність даних, що є критично важливим для бізнесу.
9 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів