Зазвичай усе починається з одного сервера бази. Він роками працює, і про нього ніхто не думає. А потім помирає диск або зависає ядро, і ви дізнаєтесь, скільки коштує година, коли сайт лежить. Або бекап є, але ніхто ніколи не пробував із нього піднятися.
Буває й інакше: база жива, але не встигає. Звіти, пошук і аналітика читають ті самі таблиці, у які пише застосунок, і все гальмує разом.
Обидві проблеми лікує реплікація: друга (і третя) копія бази, яка постійно доганяє головну. Але тільки якщо зробити її з розумінням, що вона дає, а чого ні.
Що ми робимо
Під кожен проєкт підбираємо найпростішу схему, яка закриває ризик. Найчастіше це одна з трьох.
PostgreSQL з автоматичним failover:
- три вузли PostgreSQL зі стрімінговою реплікацією: один primary приймає запис, дві репліки безперервно відтворюють його журнал змін (WAL);
- Patroni на кожному вузлі стежить за своїм PostgreSQL і вирішує, хто зараз головний;
- etcd на тих самих трьох вузлах зберігає єдиний факт «хто лідер» і дає кворум;
- HAProxy дає застосунку дві сталі адреси: одну для запису (завжди веде на primary), другу для читання (розподіляє по репліках). За потреби додаємо PgBouncer для пулу з’єднань.
MariaDB Galera:
- три вузли з синхронною реплікацією, кожен технічно приймає запис;
- окремий арбітр не потрібен: кворум вбудований у саму Galera;
- перед кластером стоїть ProxySQL. Він розуміє SQL, тому сам відправляє
SELECTна читачів, а запис на одного писача. Застосунок бачить одну адресу й нічого не знає про топологію.
Простий варіант: один primary, одна асинхронна репліка й нормальні бекапи. Перемикання робимо руками за інструкцією. Для багатьох проєктів цього вистачає з запасом.
Вузли розносимо по різних фізичних серверах, конфігурацію описуємо в Ansible.
Як це працює і де підводні камені
Чому три вузли, а не два. Кворум — це більшість. З двох вузлів, коли один упав, більшості немає, і рішення ухвалити нікому. Два вузли для автоматичного failover гірші за один: складніше, а користі нуль. Три переживають одну відмову, п’ять — дві. Тому кількість завжди непарна. Кворум захищає від split-brain, коли мережа розпалась і обидві половини вирішили, що головні саме вони. Половина без більшості просто відмовляється приймати запис.
Як PostgreSQL перемикається. Лідер постійно оновлює в etcd ключ «я живий». Помер — ключ зникає, репліка з найсвіжішими даними забирає його й стає primary. HAProxy питає в Patroni, хто зараз лідер, і переводить трафік:
listen pg_write
bind *:5000
option httpchk GET /primary
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server db1 db1:5432 check port 8008
server db2 db2:5432 check port 8008
server db3 db3:5432 check port 8008
Від падіння до відновлення роботи минають десятки секунд — і жодна людина для цього не потрібна. Старий primary, коли оживе, сам стає реплікою.
Ціна синхронності. Асинхронна реплікація швидка, але при аварії можна втратити останні мілісекунди транзакцій. Galera їх не втрачає, бо кожен COMMIT чекає підтвердження кластера. Зате запис повільніший, кластер пише зі швидкістю найповільнішого вузла, а через WAN Galera не тягнуть. Є й вимоги до схеми: тільки InnoDB і первинний ключ у кожній таблиці. Таблиця на MyISAM тихо розсинхронізує вузли без жодної помилки. Тому перед міграцією ми перевіряємо схему.
Мультимайстер не прискорює запис. Кожен вузол Galera застосовує всі зміни кластера. Три вузли не пишуть утричі швидше. Писати краще в один вузол, інакше паралельні зміни тих самих рядків дають deadlock аж на COMMIT.
Читання з реплік має пастку. Запис пішов на писача, а наступне читання — на репліку, яка ще не встигла. У Galera без навантаження цього не видно, під навантаженням частина читань мовчки повертає старі дані. Тому wsrep_sync_wait вмикаємо до того, як вмикати розділення читання й запису. А в PostgreSQL читання, яким потрібні щойно записані дані, лишаємо на primary.
Реплікація — не бекап
Це треба сказати прямо. Видалили таблицю — за мить вона зникла й на всіх репліках. Шифрувальник, баг у міграції, чиясь помилка в UPDATE без WHERE — реплікація все це старанно скопіює.
Тому бекапи робимо завжди, паралельно з кластером:
- PostgreSQL — pgBackRest: повні й інкрементні копії плюс архів WAL. Це дає PITR, тобто відновлення на конкретну хвилину, наприклад на 14:32, перед тим як щось зламалось;
- MariaDB — mariabackup плюс binlog для того самого відкату на момент часу;
- бекап знімаємо з репліки, щоб не навантажувати primary;
- копії лежать на окремому сховищі, не на тих самих серверах;
- регулярно перевіряємо відновлення: піднімаємо копію на окремій машині й дивимось, що дані на місці. Бекап, з якого ніхто не відновлювався, — це лише надія.
Моніторинг на Prometheus і Grafana стежить за лагом реплікації, станом кластера (скільки вузлів у кворумі, хто лідер), віком останнього бекапу. Алерт приходить, коли репліка відстає, вузол випав чи бекап не створився вчасно.
Коли кластер не потрібен
Кластер складніший за один сервер: більше вузлів, більше рухомих частин, свої граблі при оновленнях. Якщо година простою вас не вбиває, чесно порадимо один primary, асинхронну репліку й добрі бекапи з PITR. А якщо база не встигає з записом, спершу дивимось на індекси, запити й залізо. Реплікація масштабує читання, але не запис.
Що ви отримуєте
- Робочий кластер PostgreSQL або MariaDB (або простішу схему, якщо її досить) з однією точкою підключення для застосунку.
- Автоматичне перемикання при відмові вузла, перевірене вимкненням вузлів, а не лише на папері.
- Бекапи з PITR на окремому сховищі та регулярну перевірку відновлення.
- Дашборди й алерти: лаг реплікації, стан кворуму, вік бекапу.
- Ansible-ролі й конфіги у вашому репозиторії.
- Документацію: схему, як перевірити стан, як вручну перемкнути primary, як відновитись із бекапу.
- Повні доступи до всього, що розгорнули.
Розкажіть, яка у вас база, скільки даних і що болить: простій, повільні запити чи страх за бекапи. Підкажемо, яка схема вам справді потрібна.