MySQL падал каждую неделю: Galera Cluster для строительного портала с 2 млн визитов

Задача клиента: строительный портал, который не выдерживал собственного роста

В феврале 2026 года к нам в АйТи Фреш обратилась компания СтройПортал — один из крупнейших строительных порталов Москвы. Платформа агрегирует каталоги стройматериалов, услуги подрядчиков, калькуляторы смет и форум для профессионалов. Ежемесячная аудитория — свыше 2 миллионов уникальных посетителей, пиковая нагрузка приходится на утренние часы с 9:00 до 12:00 по будням.

Проблема, с которой пришёл клиент, была болезненно конкретной: MySQL падал минимум раз в неделю. Каждый инцидент означал от 15 минут до 2 часов простоя, потерю заказов и жалобы пользователей. Логи были завалены ошибками Too many connections и Lock wait timeout exceeded.

«Мы растём быстрее, чем наша инфраструктура. Каждую пятницу я жду звонка от мониторинга: MySQL опять лёг. Нам нужно решение, которое не просто переживёт нагрузку, а позволит масштабироваться без страха» — технический директор СтройПортал.

Руководство поставило чёткие требования: нулевой простой при падении одного сервера, горизонтальное масштабирование чтения и прозрачная для приложения миграция с одиночного MySQL на кластерную архитектуру.

Аудит текущей инфраструктуры

Наши инженеры подключились к серверам и провели детальный аудит:

  • СУБД — MySQL 8.0.35, одиночный сервер на выделенной машине (32 ГБ RAM, 8 vCPU, NVMe SSD 500 ГБ)
  • Приложение — PHP 8.2 (Laravel), 4 веб-сервера за Nginx-балансировщиком
  • Нагрузка на БД — до 3 000 запросов в секунду в пиках, 80% SELECT, 20% INSERT/UPDATE
  • Размер БД — 47 ГБ данных, крупнейшая таблица products — 12 млн строк
  • Резервное копирование — mysqldump по cron раз в сутки в 3:00 (блокировал таблицы на 8–12 минут)
  • Репликация — отсутствует, single point of failure

Анализ slow query log показал, что 40% медленных запросов — это сложные JOIN по каталогам с полнотекстовым поиском. Ещё 30% — массовые INSERT при импорте прайс-листов от поставщиков (ежедневно около 500 000 строк).

# Типичная картина в пиковые часы
mysqladmin -u root -p status
Uptime: 432001  Threads: 287  Questions: 891203847
Slow queries: 14523  Opens: 89123  Open tables: 1024

# max_connections = 300, а в пике 287 — на грани
SHOW STATUS LIKE 'Max_used_connections';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 298   |
+----------------------+-------+

Выбор решения: почему Galera Cluster

Мы рассмотрели несколько вариантов кластеризации:

РешениеТип репликацииMulti-MasterСовместимость с MySQLСложность
MySQL InnoDB ClusterПолусинхронная (Group Replication)ДаНативнаяСредняя
MariaDB Galera ClusterСинхронная (wsrep)ДаВысокая (drop-in)Средняя
Percona XtraDB ClusterСинхронная (wsrep)ДаВысокаяСредняя
MySQL + ProxySQL + ReplicaАсинхроннаяНетНативнаяНизкая

Выбор пал на MariaDB Galera Cluster по нескольким причинам:

  • Синхронная репликация — все три ноды всегда содержат одинаковые данные, нет задержки реплики
  • Multi-Master — запись возможна на любую ноду, что упрощает архитектуру
  • Автоматический failover — при падении ноды кластер продолжает работать без ручного вмешательства
  • Совместимость с MySQL — приложение на Laravel не потребовало изменений кода
  • Зрелость решения — Galera используется в production более 10 лет, включая крупные инсталляции

Архитектура, которую мы спроектировали:

# Целевая архитектура
┌──────────────────────────────────────────────────────┐
│                    HAProxy (2 шт.)                    │
│              Virtual IP: 10.0.1.100:3306             │
│         (Keepalived VRRP для отказоустойчивости)      │
└───────────┬──────────────┬──────────────┬────────────┘
            │              │              │
     ┌──────▼──────┐ ┌────▼──────┐ ┌─────▼─────┐
     │   Node 1    │ │  Node 2   │ │  Node 3   │
     │ MariaDB     │ │ MariaDB   │ │ MariaDB   │
     │ 10.0.1.11   │ │ 10.0.1.12 │ │ 10.0.1.13 │
     │ (Write)     │ │ (Read)    │ │ (Read)    │
     └─────────────┘ └───────────┘ └───────────┘
            │              │              │
            └──────── Galera wsrep ───────┘
                 (Синхронная репликация)

Установка и настройка MariaDB Galera Cluster

Мы развернули кластер на трёх выделенных серверах с идентичной конфигурацией: Ubuntu 22.04 LTS, 32 ГБ RAM, 8 vCPU, NVMe SSD 500 ГБ. Нечётное количество нод — фундаментальное требование Galera для корректного разрешения split-brain через кворум.

Установка MariaDB на все ноды

На каждой из трёх нод выполнили одинаковую установку:

# Добавляем репозиторий MariaDB 11.4 LTS
sudo apt install -y apt-transport-https curl
curl -fsSL 'https://mariadb.org/mariadb_release_signing_key.asc' | sudo gpg --dearmor -o /usr/share/keyrings/mariadb-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/mariadb-keyring.gpg] https://dlm.mariadb.com/repo/mariadb-server/11.4/repo/ubuntu jammy main" | sudo tee /etc/apt/sources.list.d/mariadb.list

sudo apt update
sudo apt install -y mariadb-server mariadb-client galera-4 mariadb-backup

# Останавливаем MariaDB до конфигурирования кластера
sudo systemctl stop mariadb

# Проверяем версию
mariadbd --version
# mariadbd  Ver 11.4.2-MariaDB for debian-linux-gnu on x86_64

Также на каждой ноде открыли необходимые порты в файрволе:

# Порты Galera Cluster
sudo ufw allow from 10.0.1.0/24 to any port 3306  # MySQL
sudo ufw allow from 10.0.1.0/24 to any port 4567  # Galera replication
sudo ufw allow from 10.0.1.0/24 to any port 4568  # IST (Incremental State Transfer)
sudo ufw allow from 10.0.1.0/24 to any port 4444  # SST (State Snapshot Transfer)
sudo ufw reload

Конфигурация wsrep и параметры кластера

Ключевой файл конфигурации — одинаковый на всех нодах с единственным отличием в адресе и имени ноды:

# /etc/mysql/mariadb.conf.d/60-galera.cnf (Node 1)
[mysqld]
binlog_format=ROW
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_size=20G
innodb_log_file_size=1G
innodb_file_per_table=1

# Galera Provider Configuration
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name="stroyportal_cluster"
wsrep_cluster_address="gcomm://10.0.1.11,10.0.1.12,10.0.1.13"

# Galera Node Configuration
wsrep_node_address="10.0.1.11"
wsrep_node_name="galera-node1"

# Galera Synchronization Configuration
wsrep_sst_method=mariabackup
wsrep_sst_auth=sst_user:S3cureSST_Pass!

# Galera Performance Tuning
wsrep_slave_threads=8
wsrep_certify_nonPK=1
wsrep_max_ws_rows=0
wsrep_max_ws_size=2G
wsrep_retry_autocommit=3

# Connection limits
max_connections=500
wait_timeout=600
interactive_timeout=600

# Query cache (отключён для Galera)
query_cache_size=0
query_cache_type=0

Разберём критически важные параметры:

  • binlog_format=ROW — обязательно для Galera, построчная репликация гарантирует консистентность
  • innodb_autoinc_lock_mode=2 — чередующийся режим автоинкремента, необходим для multi-master
  • innodb_flush_log_at_trx_commit=2 — компромисс между производительностью и надёжностью (Galera сама обеспечивает консистентность через wsrep)
  • wsrep_sst_method=mariabackup — неблокирующий State Snapshot Transfer при добавлении ноды
  • wsrep_slave_threads=8 — параллельное применение транзакций с других нод (по числу ядер)

Запуск кластера и инициализация

Инициализация кластера начинается строго с первой ноды — она становится «донором» для остальных:

# === Node 1 (10.0.1.11): инициализация кластера ===
sudo galera_new_cluster

# Проверяем статус кластера
mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 1     |
+--------------------+-------+

# Создаём пользователя для SST
mysql -u root -p -e "
  CREATE USER 'sst_user'@'localhost' IDENTIFIED BY 'S3cureSST_Pass!';
  GRANT RELOAD, LOCK TABLES, PROCESS, REPLICATION CLIENT ON *.* TO 'sst_user'@'localhost';
  FLUSH PRIVILEGES;
"

# === Node 2 (10.0.1.12): присоединение к кластеру ===
sudo systemctl start mariadb
# Node 2 автоматически запросит SST от Node 1
# В логе: WSREP: SST succeeded

# === Node 3 (10.0.1.13): присоединение к кластеру ===
sudo systemctl start mariadb

# Проверяем с любой ноды
mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_%';" | grep -E 'cluster_size|cluster_status|connected|ready|state_comment'
+----------------------------+----------------+
| wsrep_cluster_size         | 3              |
| wsrep_cluster_status       | Primary        |
| wsrep_connected            | ON             |
| wsrep_ready                | ON             |
| wsrep_local_state_comment  | Synced         |
+----------------------------+----------------+

Статус Primary означает, что кластер имеет кворум. Все три ноды в состоянии Synced — готовы принимать запросы.

HAProxy: балансировка нагрузки на базу данных

Galera Cluster — это multi-master решение, но направлять все записи на все ноды одновременно неоптимально: возникают конфликты сертификации и откаты транзакций. Оптимальная стратегия — писать в одну ноду, читать из всех. Для этого мы использовали HAProxy с разделением на write- и read-бэкенды.

Установка и конфигурация HAProxy

HAProxy установили на двух отдельных серверах для отказоустойчивости, с Keepalived для общего виртуального IP:

# Установка HAProxy
sudo apt install -y haproxy keepalived

# /etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    maxconn 4096

defaults
    log     global
    mode    tcp
    option  tcplog
    option  dontlognull
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms
    retries 3

# Статистика HAProxy
listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /haproxy-stats
    stats auth admin:H4pr0xy_Stats!
    stats refresh 10s

# Backend для ЗАПИСИ — только одна нода (writer)
frontend mysql_write_front
    bind *:3306
    default_backend mysql_write_back

backend mysql_write_back
    balance roundrobin
    option mysql-check user haproxy_check
    server galera1 10.0.1.11:3306 check weight 1
    server galera2 10.0.1.12:3306 check weight 1 backup
    server galera3 10.0.1.13:3306 check weight 1 backup

# Backend для ЧТЕНИЯ — все три ноды
frontend mysql_read_front
    bind *:3307
    default_backend mysql_read_back

backend mysql_read_back
    balance leastconn
    option mysql-check user haproxy_check
    server galera1 10.0.1.11:3306 check
    server galera2 10.0.1.12:3306 check
    server galera3 10.0.1.13:3306 check

Ключевой момент — разделение портов: 3306 для записи (один активный сервер, остальные — backup), 3307 для чтения (все три ноды с балансировкой по наименьшему числу подключений).

Keepalived для Virtual IP

Чтобы HAProxy сам не стал точкой отказа, мы настроили два экземпляра с общим виртуальным IP через VRRP:

# /etc/keepalived/keepalived.conf (HAProxy Server 1 — MASTER)
vrrp_script chk_haproxy {
    script "/usr/bin/killall -0 haproxy"
    interval 2
    weight 2
}

vrrp_instance VI_MYSQL {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 101
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass Kpalived_Secret
    }
    virtual_ipaddress {
        10.0.1.100/24
    }
    track_script {
        chk_haproxy
    }
}

# На втором HAProxy-сервере — state BACKUP, priority 100

Пользователь для проверки здоровья нод со стороны HAProxy:

# Выполняем на любой ноде Galera (реплицируется автоматически)
mysql -u root -p -e "
  CREATE USER 'haproxy_check'@'10.0.1.%' IDENTIFIED BY '';
  FLUSH PRIVILEGES;
"

Теперь приложение подключается к виртуальному IP 10.0.1.100: порт 3306 для записи, 3307 для чтения. При падении любой ноды Galera или любого HAProxy — сервис остаётся доступным.

Настройка приложения для read/write splitting

В Laravel-приложении СтройПортала настроили два подключения к БД:

// config/database.php (Laravel)
'mysql' => [
    'read' => [
        'host' => '10.0.1.100',
        'port' => 3307,  // HAProxy read backend
    ],
    'write' => [
        'host' => '10.0.1.100',
        'port' => 3306,  // HAProxy write backend
    ],
    'sticky' => true,  // После записи читаем с writer (избегаем race condition)
    'driver' => 'mysql',
    'database' => 'stroyportal',
    'username' => 'app_user',
    'password' => env('DB_PASSWORD'),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
    'strict' => true,
    'engine' => 'InnoDB',
],

Параметр sticky => true критически важен: после любой операции записи все последующие чтения в рамках этого HTTP-запроса идут на writer-ноду, исключая чтение устаревших данных.

Миграция данных с MySQL на Galera Cluster

Миграция 47 ГБ данных с одиночного MySQL на Galera Cluster — самый рискованный этап проекта. Мы разработали план с минимальным простоем.

Подготовка и перенос данных

Для минимизации простоя использовали mariabackup вместо mysqldump — он не блокирует таблицы и работает значительно быстрее:

# 1. Снимаем полный бэкап с продакшен MySQL
mariabackup --backup --target-dir=/backup/full \
  --host=old-mysql.stroyportal.local \
  --user=backup_user --password='BackupPass123'

# 2. Подготавливаем бэкап (apply log)
mariabackup --prepare --target-dir=/backup/full

# 3. Останавливаем MariaDB на Node 1
sudo systemctl stop mariadb

# 4. Очищаем datadir и копируем данные
sudo rm -rf /var/lib/mysql/*
mariabackup --copy-back --target-dir=/backup/full
sudo chown -R mysql:mysql /var/lib/mysql

# 5. Запускаем кластер с импортированными данными
sudo galera_new_cluster

# 6. Проверяем данные
mysql -u root -p -e "SELECT COUNT(*) FROM stroyportal.products;"
+----------+
| COUNT(*) |
+----------+
| 12483291 |
+----------+

# 7. Присоединяем Node 2 и Node 3 — они автоматически получат данные через SST

Переключение трафика и проверка

Переключение выполнялось в ночное окно обслуживания (воскресенье, 3:00):

  1. Перевод сайта в режим обслуживания (php artisan down)
  2. Финальная синхронизация данных (инкрементальный бэкап за последние часы)
  3. Обновление конфигурации приложения на новые подключения
  4. Проверка чтения и записи на каждой ноде
  5. Снятие режима обслуживания

Общий простой составил 12 минут — меньше, чем средний инцидент с падением MySQL в предыдущем режиме.

# Проверка репликации — создаём запись на Node 1, читаем на Node 3
# Node 1:
mysql -u root -p -e "INSERT INTO stroyportal.migration_test VALUES (1, NOW(), 'test');"

# Node 3 (мгновенно):
mysql -u root -p -e "SELECT * FROM stroyportal.migration_test;"
+----+---------------------+---------+
| id | created_at          | payload |
+----+---------------------+---------+
|  1 | 2026-02-16 03:07:45 | test    |
+----+---------------------+---------+

Защита от split-brain и обработка сбоев

Split-brain — главный кошмар любого кластера: две части считают себя основными и принимают противоречивые записи. Galera решает эту проблему через кворум, но правильная конфигурация — наша ответственность.

Механизм кворума в Galera

Galera использует алгоритм виртуальной синхронности с компонентом кворума (PC — Primary Component). При 3 нодах:

  • 3 из 3 работают — полный кворум, все операции доступны
  • 2 из 3 работают — кворум сохранён, кластер продолжает работу
  • 1 из 3 работает — кворум потерян, нода переходит в non-Primary и отклоняет запросы

Дополнительно мы настроили параметры защиты:

# Дополнение к /etc/mysql/mariadb.conf.d/60-galera.cnf

# Защита от split-brain
wsrep_provider_options="pc.weight=1; evs.suspect_timeout=PT10S; evs.inactive_timeout=PT30S; evs.keepalive_period=PT3S; gmcast.peer_timeout=PT10S; pc.recovery=TRUE"

# Автоматическое восстановление после сбоя
wsrep_auto_increment_control=ON
wsrep_cluster_address="gcomm://10.0.1.11,10.0.1.12,10.0.1.13"

Параметр pc.recovery=TRUE критически важен: при перезапуске ноды она считывает сохранённое состояние Primary Component из файла gvwstate.dat и автоматически присоединяется к кластеру.

Скрипт проверки здоровья кластера для HAProxy

Стандартная проверка mysql-check в HAProxy проверяет только доступность MySQL, но не статус кластера. Мы написали расширенный скрипт для xinetd:

#!/bin/bash
# /usr/local/bin/galera_healthcheck.sh
# Проверка здоровья ноды Galera для HAProxy (HTTP check)

MYSQL_HOST="127.0.0.1"
MYSQL_PORT="3306"
MYSQL_USER="haproxy_check"
ERR_FILE="/dev/null"

SYNCED=$(mysql -h ${MYSQL_HOST} -P ${MYSQL_PORT} -u ${MYSQL_USER} \
  --connect-timeout=5 -N -e "SHOW STATUS LIKE 'wsrep_local_state';" 2>${ERR_FILE} \
  | awk '{print $2}')

READY=$(mysql -h ${MYSQL_HOST} -P ${MYSQL_PORT} -u ${MYSQL_USER} \
  --connect-timeout=5 -N -e "SHOW STATUS LIKE 'wsrep_ready';" 2>${ERR_FILE} \
  | awk '{print $2}')

CLUSTER_STATUS=$(mysql -h ${MYSQL_HOST} -P ${MYSQL_PORT} -u ${MYSQL_USER} \
  --connect-timeout=5 -N -e "SHOW STATUS LIKE 'wsrep_cluster_status';" 2>${ERR_FILE} \
  | awk '{print $2}')

if [ "${SYNCED}" = "4" ] && [ "${READY}" = "ON" ] && [ "${CLUSTER_STATUS}" = "Primary" ]; then
    echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nGalera node synced."
    exit 0
else
    echo -e "HTTP/1.1 503 Service Unavailable\r\nContent-Type: text/plain\r\n\r\nGalera node not ready."
    exit 1
fi

Скрипт проверяет три условия: wsrep_local_state = 4 (Synced), wsrep_ready = ON и wsrep_cluster_status = Primary. Только при выполнении всех трёх HAProxy считает ноду здоровой.

Резервное копирование и мониторинг

Кластер Galera обеспечивает высокую доступность, но не заменяет резервное копирование. Ошибка оператора DROP TABLE реплицируется на все ноды мгновенно. Полноценная стратегия бэкапов — обязательна.

Резервное копирование с mariabackup

Мы настроили инкрементальное резервное копирование с одной из read-нод, чтобы не нагружать writer:

#!/bin/bash
# /opt/scripts/galera_backup.sh
# Запуск: ежедневно в 2:00 с Node 3 (read-нода)

BACKUP_DIR="/backup/mariadb"
FULL_DIR="${BACKUP_DIR}/full"
INC_DIR="${BACKUP_DIR}/incremental"
LOG_FILE="/var/log/galera_backup.log"
DAY_OF_WEEK=$(date +%u)  # 1=Пн, 7=Вс
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=14

exec >> "${LOG_FILE}" 2>&1
echo "=== Backup started: $(date) ==="

if [ "${DAY_OF_WEEK}" -eq 7 ]; then
    # Воскресенье — полный бэкап
    echo "Running full backup..."
    rm -rf "${FULL_DIR}"
    mariabackup --backup --target-dir="${FULL_DIR}" \
        --user=backup_user --password='BackupPass123' \
        --galera-info
    echo "Full backup completed: $(du -sh ${FULL_DIR} | awk '{print $1}')"
else
    # Будни — инкрементальный бэкап
    echo "Running incremental backup..."
    mkdir -p "${INC_DIR}/${DATE}"
    mariabackup --backup --target-dir="${INC_DIR}/${DATE}" \
        --incremental-basedir="${FULL_DIR}" \
        --user=backup_user --password='BackupPass123' \
        --galera-info
    echo "Incremental backup completed: $(du -sh ${INC_DIR}/${DATE} | awk '{print $1}')"
fi

# Удаление старых инкрементальных бэкапов
find "${INC_DIR}" -maxdepth 1 -mtime +${RETENTION_DAYS} -exec rm -rf {} \;

echo "=== Backup finished: $(date) ==="

Флаг --galera-info сохраняет позицию wsrep, что позволяет при необходимости восстановить ноду точно в состояние кластера на момент бэкапа.

Мониторинг кластера через Prometheus и Grafana

Для мониторинга кластера мы развернули стек Prometheus + Grafana с mysqld_exporter на каждой ноде:

# Установка mysqld_exporter на каждой ноде
wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.15.1/mysqld_exporter-0.15.1.linux-amd64.tar.gz
tar xzf mysqld_exporter-0.15.1.linux-amd64.tar.gz
sudo mv mysqld_exporter-0.15.1.linux-amd64/mysqld_exporter /usr/local/bin/

# Пользователь для мониторинга
mysql -u root -p -e "
  CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'Exporter_Pass!';
  GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';
  FLUSH PRIVILEGES;
"

# /etc/default/mysqld_exporter
DATA_SOURCE_NAME='exporter:Exporter_Pass!@(localhost:3306)/'

# systemd unit
# /etc/systemd/system/mysqld_exporter.service
[Unit]
Description=MySQL Exporter
After=network.target

[Service]
Type=simple
EnvironmentFile=/etc/default/mysqld_exporter
ExecStart=/usr/local/bin/mysqld_exporter \
  --collect.galera \
  --collect.global_status \
  --collect.info_schema.tables \
  --collect.slave_status \
  --web.listen-address=":9104"
Restart=always

[Install]
WantedBy=multi-user.target

Ключевые метрики, которые мы вынесли на дашборд Grafana:

  • mysql_global_status_wsrep_cluster_size — количество нод в кластере (алерт при < 3)
  • mysql_global_status_wsrep_local_recv_queue_avg — средняя очередь применения (алерт при > 0.5)
  • mysql_global_status_wsrep_local_send_queue_avg — средняя очередь отправки
  • mysql_global_status_wsrep_flow_control_paused — доля времени в flow control (алерт при > 0.1)
  • mysql_global_status_wsrep_cert_deps_distance — параллелизм применения транзакций
  • mysql_global_status_wsrep_local_bf_aborts — конфликты сертификации (откаты)

Настроили алерты в Alertmanager с отправкой в Telegram-канал команды эксплуатации. Время реакции на инцидент сократилось с часов до минут.

Результаты внедрения: цифры, которые говорят сами за себя

Через месяц после запуска Galera Cluster в продакшен мы собрали метрики и сравнили с предыдущим периодом:

МетрикаДо (MySQL single)После (Galera Cluster)Улучшение
Инциденты простоя БД4–5 в месяц0 за 30 дней100%
Среднее время отклика SQL45 мс18 мс60%
Пиковые подключения298 / 300 (предел)180 / 500 (запас)Запас 64%
Время бэкапа8–12 мин (с блокировкой)3 мин (без блокировки)75%
Failover при падении нодыN/A (ручное)Автоматический, <10 сек
Пропускная способность чтения3 000 QPS8 500 QPS183%

Но самый важный результат — ни одного инцидента простоя за первые 30 дней. Мы специально провели учебные тестирования: отключили Node 2 (kill -9 mysqld), и клиенты портала даже не заметили — HAProxy мгновенно перенаправил трафик на оставшиеся две ноды.

«За первый месяц работы кластера я ни разу не получил звонок в три часа ночи. Это стоило каждой потраченной копейки» — технический директор СтройПортал.

Проект занял 2 недели: одна неделя на развёртывание и тестирование инфраструктуры, вторая — на миграцию данных и стабилизацию. Общие затраты на оборудование (3 сервера + 2 HAProxy) окупились за 2 месяца за счёт отсутствия простоев и возросшей конверсии.

Рекомендации для аналогичных проектов

На основе этого проекта мы сформировали набор рекомендаций:

  • Всегда нечётное количество нод — 3 или 5, никогда 2 (невозможно определить кворум)
  • NVMe SSD обязательны — Galera чувствительна к задержкам диска из-за синхронной репликации
  • Разделяйте write и read — не полагайтесь на multi-master для записи на все ноды одновременно
  • Тестируйте failover до продакшена — убедитесь, что приложение корректно обрабатывает переключение
  • Мониторьте flow control — если нода не успевает применять транзакции, она тормозит весь кластер
  • Не забывайте про бэкапы — Galera защищает от падения нод, но не от ошибок оператора

Масштабирование на будущее

Текущая архитектура позволяет горизонтально масштабировать чтение: добавление 4-й и 5-й ноды — вопрос нескольких часов. Для дальнейшего роста мы спроектировали roadmap:

  • Этап 2 — шардирование крупнейших таблиц (products, search_index) через ProxySQL
  • Этап 3 — географически распределённый кластер с нодами в московском и петербургском дата-центрах
  • Этап 4 — переход на MariaDB Xpand для нативного шардирования

СтройПортал получил инфраструктуру баз данных, которая не просто решила текущую проблему, а заложила фундамент для роста до 10 млн посещений в месяц без архитектурных изменений.

Часто задаваемые вопросы

Обычная MySQL-репликация — асинхронная: мастер отправляет изменения реплике с задержкой, данные на реплике могут отставать. Galera использует синхронную репликацию на уровне коммита транзакции: запись подтверждается только после того, как все ноды приняли данные. Это гарантирует одинаковое состояние на всех нодах в любой момент времени. Кроме того, Galera — multi-master, запись возможна на любую ноду, тогда как в стандартной репликации запись идёт только на мастер.

Минимально рекомендуется 3 ноды. При 2 нодах и потере связи между ними возникает split-brain: ни одна нода не имеет большинства (кворума) и обе переходят в non-Primary состояние, отказывая в обслуживании. С 3 нодами при потере одной оставшиеся две имеют большинство (2 из 3) и продолжают работу. Для крупных инсталляций используют 5 нод — это позволяет пережить одновременное падение двух серверов.

В большинстве случаев — да. MariaDB Galera Cluster полностью совместима с MySQL на уровне протокола и SQL-синтаксиса. Однако есть ограничения: все таблицы должны использовать InnoDB (MyISAM не реплицируется), необходимы первичные ключи на всех таблицах (для корректной построчной репликации), и нужно учитывать, что LOCK TABLES работает только локально. Большинство современных ORM и фреймворков (Laravel, Django, Rails) полностью совместимы.

Обновление выполняется методом rolling upgrade: ноды обновляются по одной. Сначала останавливаем и обновляем Node 3 (read), затем Node 2, затем Node 1 (writer). Между обновлениями дожидаемся, пока нода перейдёт в состояние Synced. Кластер остаётся доступным на протяжении всего процесса, так как кворум сохраняется (минимум 2 из 3 нод работают). Важно обновлять на совместимые версии — проверяйте release notes на совместимость wsrep API.

Galera критически зависит от сети: синхронная репликация требует низкой задержки между нодами. Рекомендуемые параметры: RTT менее 5 мс (идеально — менее 1 мс), пропускная способность от 1 Гбит/с. Ноды должны находиться в одном дата-центре или в дата-центрах с выделенным каналом. Работа через публичный интернет не рекомендуется из-за непредсказуемой задержки, которая приведёт к деградации производительности и частым таймаутам.

Нужна помощь с настройкой?

Специалисты АйТи Фреш помогут с внедрением и настройкой — 15+ лет опыта, обслуживание от 15 000 ₽/мес

📞 Связаться с нами
#MariaDB Galera Cluster настройка#высокая доступность MySQL#синхронная репликация MariaDB#HAProxy балансировка баз данных#wsrep конфигурация Galera#mariabackup резервное копирование#split-brain Galera предотвращение#MySQL кластер для highload