Skip to content

Instantly share code, notes, and snippets.

@CHIP0K
Last active August 31, 2023 07:47
Show Gist options
  • Save CHIP0K/b265b3ed656f72f2a661a6c3729f9357 to your computer and use it in GitHub Desktop.
Save CHIP0K/b265b3ed656f72f2a661a6c3729f9357 to your computer and use it in GitHub Desktop.
MySQL Multi-Source Replication
#!/usr/bin/env bash
source /etc/environment
SNAPSHOT_TIME=$(date +'%Y-%m-%d_%H-%M-%S')
ROTATE_HOURS="24"
LOGFILE=/var/log/zfs.log
ZPOOL_NAME="zp_mysql"
ZFS_DATASET="mysql/data"
checkLock() {
if [[ $(pgrep -fc "${0##*/}") -gt 1 ]]; then
echo "Script ${0##*/} is running"
exit 1
fi
}
# Function to create a snapshot with the current date and time
create_snapshot() {
local snapshot_name="${ZPOOL_NAME}/${ZFS_DATASET}@${SNAPSHOT_TIME}"
zfs snapshot "$snapshot_name"
echo "${SNAPSHOT_TIME} | created zfs snapshot: $snapshot_name" >>${LOGFILE}
}
# Function to delete snapshots older than 24 hours
delete_old_snapshots() {
local cutoff_time=$(date -d "${ROTATE_HOURS} hours ago" +"%s")
zfs list -H -t snapshot -o name,creation -r "$ZPOOL_NAME/$ZFS_DATASET" |
while read -r snapshot_name creation_time; do
local snapshot_time=$(date -d "$creation_time" +'%s')
if ((snapshot_time < cutoff_time)); then
zfs destroy "$snapshot_name"
echo "${SNAPSHOT_TIME} | destroyed zfs snapshot: $snapshot_name" >>${LOGFILE}
fi
done
}
main() {
checkLock
create_snapshot
delete_old_snapshots
}
main

Як налаштувати MySQL Multi-Source Replication за допомогою GTID на ZFS файловій системі

Привіт усім! Мене звати Ігор Стецюк, я DevOps-інженер в компанії Futurra Group. Ми працюємо у сфері EdTech, зокрема розробляємо та просуваємо математичний сервіс MathMaster. Це застосунок, що розв’язує математичні задачі за допомогою алгоритму авторозпізнання, а також надає можливість користувачам отримати математичну консультацію в онлайн-чаті з математичними експертами.

У цій статті я розповім про налаштування MySQL Multi-Source Replication на ZFS файловій системі. Даний матеріал можна використовувати для різних цілей, ми дану схему використовуємо для потреб команди аналітиків, розробників, коли потрібно перевірити поведінку коду в робочому середовищі на живих даних, для створення резервних копій, оточень розробників (fitcha environments), і т.д. Перед тим, як перейти до прикладів використання, розглянемо детальніше, як працює цей механізм реплікації та інструменти, що будемо застосовувати.

Для початку трішки теорії

MySQL Multi-Source Replication - це механізм, який дозволяє реплікувати дані з кількох джерел у одну єдину базу даних. Це особливо корисно у випадку, коли у вас є декілька баз, які містять пов'язані дані, і ви хочете мати єдину точку доступу до них. Саме за допомогою Multi-Source Replication ви можете сконфігурувати єдину точку доступу та забезпечити автоматичну реплікацію даних між джерелами.

GTID (Global Transaction Identifier) в MySQL - це унікальний ідентифікатор для транзакцій у реплікаційній топології. Використовуючи GTID, ви можете однозначно ідентифікувати кожну транзакцію в базі даних, незалежно від того, на якому сервері вона відбулася. GTID складається з двох основних частин:

  1. UUID - унікальний ідентифікатор для кожного сервера бази даних у реплікаційній топології.
  2. Sequence Number - порядковий номер транзакції, який відображається на конкретному сервері. Кожен сервер зберігає цей локальний лічильник для відстеження номерів транзакцій, які відбулися на цьому сервері.

ZFS файлова система дає нам можливість дешево створювати знімки файлової системи, використовуючи (zfs snapshot), а також відновлювати стан файлової системи та видаляти застарілі знімки.

Розглянувши основні концепти, пов'язані з налаштуванням Multi-Source Replication на ZFS файловій системі, ми можемо перейти до конкретних прикладів, щоб продемонструвати, як ви можете використовувати цей механізм реплікації та убезпечення ваших даних.

Схема інфраструктури

Так історично склалося, що наші кластери баз даних знаходяться в DigitalOcean. Цей провайдер дає можливість легко створювати та налаштовувати інфраструктуру, має зручний API та детальну документацію. Крім того, критерієм вибору DigitalOcean стало ще те, що провайдер має Terraform, який активно розвивається та підтримується в належному стані. Також 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.

Untitled

На схемі зображено два незалежних один від одного кластери баз даних mysql з master (RW) ⇒ slave (RO) реплікаціями, окрема база даних (DB3), що знаходиться на віртуальній машині та Multi-Source Replication node - сервер, котрий буде реплікувати всі наші кластери.

Операційна система Ubuntu jammy.

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

Додамо наступні налаштування в конфігурацію сервера:

[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_1d06fea83e157d93ddba2d7a0b7e4c9497679dce5745761995d70d641b387f2a"

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 наших баз, щоб не навантажувати головний сервер, бекап можна виконувати на будь-якій ноді для читання.

# DB-1
mysqldump --single-transaction \
        --quick \
        --add-drop-database \
        --add-drop-table \
        --triggers \
        --routines \
        --events \
        --source-data \
        --databases parrots fish > db-1.sql

# DB-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-файлу команди для видалення бази даних перед її створенням, що дозволяє створити базу даних from scratch. --add-drop-table: Цей параметр додає до SQL-файлу команди для видалення таблиць перед їх створенням, допомагаючи уникнути конфліктів під час відновлення резервної копії. --triggers: Параметр, що включає в резервну копію тригери, які визначені в базі даних. --routines: Цей параметр додає в резервну копію збережені процедури та функції, які визначені в базі даних. --events: Параметр, що включає в резервну копію заплановані події (events), які визначені в базі даних. --source-data: Цей параметр додає в резервну копію дані з таблиць. --databases: Тут вказані назви баз даних, які потрібно включити в резервну копію. У нашому випадку, база даних "cats".

Далі нам необхідно отримати 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

  db-1 34 SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '4d7dbef6-3777-11ee-807c-0ae964d12771:1-33';
  db-2 34 SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '9784567e-3777-11ee-bc2e-ea23c8e356f2:1-30';
  db-3 34 SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '0289f920-376d-11ee-a2ef-363cbae02daa:1-27';
# Видаляємо ці рядки з файлу
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 об’єднуємо в одну команду та виконуємо її у випадку, якщо ми вирізали 34-ий рядок з кожного дампу, або після завантаження всіх дампів. Якщо щось пішло не так і вам потрібно записати нові значення, виконайте RESET MASTER;

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 Snapshots (знімки) Знімок в ZFS - це віртуальна копія файлової системи або об'єктного сховища на конкретний момент часу. Основна ідея полягає в тому, що знімки зберігають лише зміни між початковим станом файлової системи та поточним знімком, що дозволяє ефективно використовувати простір на диску. Знімки використовуються для резервного копіювання, відновлення даних, створення тестових середовищ та багатьох інших завдань.

Коли ви створюєте знімок в ZFS, він зберігає інформацію про стан об'єктів у файловій системі на момент створення. Знімки не вимагають створення фактичних копій даних, тому вони генеруються майже миттєво та займають мінімальний об'єм місця.

ZFS Rollback (відновлення) Ролбек в ZFS - це операція відновлення файлової системи до стану на момент створення знімку.

Під час виконання ролбеку ZFS забезпечує консистентність даних, що означає, що дані повертаються до попереднього стану без ризику втрати або пошкодження. Ця операція також є миттєвою, навіть на великих об'ємних накопичувачах.

Важливо зауважити, що знімки та ролбеки в ZFS — це операції, доступні на рівні файлової системи, і вони працюють незалежно від операційки. Це робить їх потужними інструментами для керування даними та забезпечення надійності і доступності інформації.

Для створення знімків я використовую скрипт який запускається в кроні погодинно та виконує ротацію застарілих знімків.

https://gist.github.com/CHIP0K/b265b3ed656f72f2a661a6c3729f9357

Нижче наведено приклад знімків з реального сервера:

chip@mysql-slave:~$ zfs list -t snapshot
NAME                                      USED  AVAIL     REFER  MOUNTPOINT
zp_mysql/mysql/data@2023-08-17_09-00-01   163M      -      103G  -
zp_mysql/mysql/data@2023-08-17_10-00-01   165M      -      103G  -
zp_mysql/mysql/data@2023-08-17_11-00-01   178M      -      103G  -
zp_mysql/mysql/data@2023-08-17_12-00-01   184M      -      103G  -
zp_mysql/mysql/data@2023-08-17_13-00-01   180M      -      103G  -
zp_mysql/mysql/data@2023-08-17_14-00-01   197M      -      103G  -
zp_mysql/mysql/data@2023-08-17_15-00-02   203M      -      103G  -
zp_mysql/mysql/data@2023-08-17_16-00-01   197M      -      103G  -
zp_mysql/mysql/data@2023-08-17_17-00-01   203M      -      103G  -
zp_mysql/mysql/data@2023-08-17_18-00-01   200M      -      103G  -
zp_mysql/mysql/data@2023-08-17_19-00-01   196M      -      103G  -
zp_mysql/mysql/data@2023-08-17_20-00-01   197M      -      103G  -
zp_mysql/mysql/data@2023-08-17_21-00-01   202M      -      103G  -
zp_mysql/mysql/data@2023-08-17_22-00-01   218M      -      103G  -
zp_mysql/mysql/data@2023-08-17_23-00-01   230M      -      103G  -
zp_mysql/mysql/data@2023-08-18_00-00-01   238M      -      103G  -
zp_mysql/mysql/data@2023-08-18_01-00-01   235M      -      103G  -
zp_mysql/mysql/data@2023-08-18_02-00-01   223M      -      103G  -
zp_mysql/mysql/data@2023-08-18_03-00-01   222M      -      103G  -
zp_mysql/mysql/data@2023-08-18_04-00-01   222M      -      103G  -
zp_mysql/mysql/data@2023-08-18_05-00-01   253M      -      104G  -
zp_mysql/mysql/data@2023-08-18_06-00-01   222M      -      104G  -
zp_mysql/mysql/data@2023-08-18_07-00-01   196M      -      104G  -
zp_mysql/mysql/data@2023-08-18_08-00-01   169M      -      104G  -

Для того, щоб відкотитись до останньої копії, вам необхідно виконати команду zfs rollback . В нашому випадку це zfs rollback zp_mysql/mysql/data@2023-08-18_08-00-01. Проте, якщо ви плануєте повернутись до історичної попередньої копії, необхідно видалити всі копії, що йдуть за нею. Це можна зробити автоматично, виконавши команду з ключем -r: zfs rollback -r zp_mysql/mysql/data@2023-08-18_06-00-01.

Якщо ви перевірите стан ваших знімків, то побачите, що копій zp_mysql/mysql/data@2023-08-18_08-00-01 та zp_mysql/mysql/data@2023-08-18_07-00-01 вже не існує.

Також ми можемо, наприклад, відправити знімки на інший сервер та розгорнути необхідну нам копію бази даних. Можна також створити архів знімку та використовувати його для різних пісочниць розробників для прикладу. Всі ці маніпуляції виконуються набагато швидше, ніж у випадку з mysqldump.

Висновок

У статті я намагався якнайдетальніше описати процес налаштування MySQL Multi-Source Replication з використанням GTID. Очевидно, що знімки та ролбеки в ZFS є потужними інструментами для керування даними та забезпечення надійності і доступності інформації. Знімки використовуються для резервного копіювання, відновлення даних, створення тестових середовищ та багатьох інших завдань. Ролбек дозволяє скасувати всі зміни, які були зроблені після створення знімку, і повернутися до цього стану.

За допомогою MySQL Multi-Source Replication можна налаштувати реплікацію з декількох серверів на один. Зокрема, ми розглянули налаштування реплікації з трьох серверів. Крім того, ми розглянули процес налаштування знімків та ролбеків в ZFS. Описану схему зручно використовувати для створення резервних копій, аналітики, “тестанути, як воно буде на проді” і при цьому не завдати шкоди продакшн-даним.

Використовуючи ці інструменти, можна забезпечити надійність та доступність даних, що критично важливо для бізнесу.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment