Пошук уроків, статей та іншого контенту
Розглянете синхронну й асинхронну реплікацію, топології реплік та вплив затримок на надійність системи.
Реплікація даних — це підтримання кількох копій одних і тих самих даних на різних вузлах системи.
Зазвичай один вузол приймає записи, а інші зберігають копії та обслуговують читання. Реплікація використовується для:
підвищення доступності;
розподілу навантаження на читання;
швидшого доступу до даних у різних регіонах;
зменшення ризику втрати даних у разі відмови одного вузла.
Реплікація не означає, що всі копії завжди однакові. Важливо розуміти:
коли зміна вважається успішно збереженою;
коли вона стає доступною на репліках;
що станеться, якщо основний вузол відмовить до завершення реплікації.
У топології primary-replica:
primary — вузол, який приймає операції запису;
replica — вузол, який отримує копії змін;
реплікація — процес передавання змін від primary до реплік.
Інколи замість primary використовують терміни leader, master або source, а замість
followerslavestandbyЗміни часто передаються не як повний стан бази, а як послідовність операцій або записів журналу:
клієнт надсилає запис на primary;
primary фіксує зміну у власному журналі;
журнал передається реплікам;
репліки застосовують зміни у себе.
Послідовність записів зазвичай має важливе значення. Якщо репліка застосує зміни в неправильному порядку, її стан може відрізнятися від primary.
За синхронної реплікації primary підтверджує запис лише після того, як одна або кілька реплік підтвердили отримання або збереження зміни.
Спрощений процес:
клієнт надсилає запис;
primary записує зміну;
primary передає її репліці;
репліка підтверджує операцію;
primary відповідає клієнту про успіх.
Точна гарантія залежить від реалізації. Підтвердження може означати:
запис у пам’ять репліки;
запис у журнал;
запис на стабільне сховище.
Тому під час проєктування потрібно чітко визначати, що саме означає «репліка отримала дані».
менше вікно потенційної втрати даних;
сильніша узгодженість між вузлами;
передбачуваніше перемикання на репліку після відмови primary.
більша затримка запису;
тимчасова недоступність записів, якщо необхідна репліка не відповідає;
залежність доступності primary від мережі між вузлами.
Якщо primary розташований в одному регіоні, а синхронна репліка — в іншому, кожен запис може чекати на міжрегіональну мережеву затримку.
За асинхронної реплікації primary підтверджує запис, не чекаючи на завершення реплікації.
Процес виглядає так:
клієнт надсилає запис;
primary зберігає зміну;
primary одразу відповідає клієнту;
зміна передається реплікам пізніше.
Між станом primary та репліки виникає реплікаційна затримка — replication lag.
менша затримка запису;
primary може продовжувати роботу, навіть якщо репліка тимчасово недоступна;
зручно розміщувати репліки на значній відстані від primary.
після відмови primary частина підтверджених записів може бути відсутня на репліці;
читання з репліки може повернути застарілі дані;
під час перемикання потрібен контроль відставання репліки.
Асинхронна реплікація не означає ненадійну систему. Вона означає, що система свідомо обмінює частину гарантій узгодженості на меншу затримку та вищу доступність.
втрата підтвердженого запису неприйнятна;
система працює з фінансовими або критичними операціями;
прийнятна додаткова затримка запису;
репліки мають стабільне мережеве з’єднання з primary.
важливі низька затримка та доступність;
невелике вікно потенційної втрати даних допустиме;
репліки обслуговують переважно читання;
вузли розташовані в різних регіонах.
На практиці часто використовують змішану схему: одну локальну синхронну репліку для швидкого відновлення та додаткові асинхронні репліки для географічної надлишковості.
Реплікаційна затримка — це час між появою зміни на primary та її застосуванням на конкретній репліці.
Вона може виникати через:
повільну мережу;
перевантаження primary;
повільний запис на диски репліки;
велику кількість змін;
блокування або довгі операції на репліці;
тимчасову недоступність репліки.
Припустімо, користувач змінив ім’я профілю, а наступний запит читає профіль із репліки. Якщо репліка ще не отримала зміну, користувач побачить старе ім’я.
Це приклад неузгодженого читання після запису — read-after-write inconsistency.
Інші можливі ефекти:
користувач бачить старий статус замовлення;
список містить об’єкт, який уже видалено;
два послідовні читання повертають різні версії даних;
агрегати або лічильники тимчасово не відповідають записам.
Система може:
читати критично важливі дані з primary;
після запису певний час спрямовувати читання цього користувача на primary;
читати з репліки лише після досягнення потрібної позиції журналу;
встановлювати максимальну допустиму затримку репліки;
вилучати репліку з пулу читання, якщо вона відстає надто сильно.
Важливо не приховувати затримку без потреби. Якщо репліка відстає, система має або коректно обробити це, або перестати використовувати її для критичних читань.
Для асинхронної реплікації затримка визначає вікно потенційної втрати даних.
Якщо primary відмовив у момент, коли репліка відставала на 20 секунд, то зміни за останні 20 секунд можуть бути відсутні на цій репліці. Це не означає, що будуть втрачені рівно всі записи за 20 секунд: фактичний результат залежить від журналу, буферів і способу перемикання.
Для оцінювання використовують:
RPO — скільки даних система може втратити;
RTO — скільки часу може тривати відновлення роботи.
Реплікація допомагає зменшити RTO, оскільки готова копія вже існує. Але асинхронна реплікація не гарантує нульовий RPO.
Слід моніторити щонайменше:
поточну затримку кожної репліки;
останню підтверджену позицію журналу;
час без успішної синхронізації;
помилки застосування змін;
доступність репліки;
кількість реплік, які можуть прийняти роль primary.
У найпростішій схемі один primary передає зміни безпосередньо кільком реплікам.
┌─────────┐
│ Replica │
└────▲────┘
│
┌─────────┐ │
│ Primary ├────┘
└────┬────┘
│
▼
┌─────────┐
│ Replica │
└─────────┘Переваги:
проста модель запису;
легко розподіляти читання;
відмова однієї репліки не зупиняє інші.
Недоліки:
primary стає центром передавання всіх змін;
велика кількість реплік може збільшити навантаження на primary;
після відмови primary потрібен процес вибору нового вузла.
У ланцюгу одна репліка передає зміни наступній:
Primary → Replica A → Replica B → Replica CЦе зменшує кількість прямих з’єднань із primary, але збільшує шлях проходження даних.
Якщо кожен вузол додає затримку, кінцева репліка може відставати значно сильніше. Відмова вузла посередині також може перервати реплікацію для всіх наступних вузлів, якщо система не має альтернативного маршруту.
Primary і репліки можуть бути розташовані в різних зонах доступності або регіонах.
Це захищає від локальних відмов:
проблем із сервером;
відмови зони;
мережевого інциденту в одному регіоні.
Але міжрегіональна реплікація зазвичай має більшу затримку. Тому її часто виконують асинхронно, якщо система не може дозволити собі чекати на віддалене підтвердження кожного запису.
У топології multi-primary кілька вузлів можуть приймати записи.
Перевага — записи можуть оброблятися ближче до користувача або продовжуватися під час відмови одного вузла.
Основна складність — конфлікти. Наприклад, два вузли одночасно змінюють те саме поле:
Вузол A: status = "paid"
Вузол B: status = "cancelled"Система повинна мати визначене правило розв’язання конфліктів. Можливі підходи:
забороняти одночасний запис одного об’єкта;
використовувати порядок версій або часові мітки;
призначати відповідальний вузол для конкретного ключа;
застосовувати спеціальне правило злиття.
Multi-primary не слід обирати лише для того, щоб «збільшити доступність». Вартість конфліктів і складність відновлення мають бути враховані заздалегідь.
Наступний приклад на Node.js моделює primary та репліку. Запис одразу доступний на primary, але з’являється на репліці лише після затримки.
class AsyncReplicaStore {
constructor(replicationDelayMs) {
this.primary = new Map();
this.replica = new Map();
this.replicationDelayMs = replicationDelayMs;
}
write(key, value) {
// Запис на primary підтверджується одразу
this.primary.set(key, value);
setTimeout(() => {
// Репліка застосовує зміну пізніше
this.replica.set(key, value);
}, this.replicationDelayMs);
}
readFromPrimary(key) {
return this.primary.get(key);
}
readFromReplica(key) {
return this.replica.get(key);
}
}
const store = new AsyncReplicaStore(500);
store.write("order:42", "paid");
console.log("Одразу з primary:", store.readFromPrimary("order:42"));
console.log("Одразу з replica:", store.readFromReplica("order:42"));
setTimeout(() => {
console.log("Через 600 мс з replica:", store.readFromReplica("order:42"));
}, 600);Типовий результат:
Одразу з primary: paid
Одразу з replica: undefined
Через 600 мс з replica: paidЦе лише модель поведінки, а не реалізація повноцінної реплікації бази даних. Вона демонструє ключову властивість: успішний запис на primary не гарантує, що та сама зміна вже доступна на асинхронній репліці.
Якщо primary відмовляє, система може виконати failover — призначити одну з реплік новим primary.
Під час failover потрібно визначити:
яка репліка має найсвіжіші дані;
чи завершила вона застосування журналу;
чи можна безпечно приймати на неї записи;
як клієнти дізнаються про новий primary;
що робити зі старим primary після його повернення.
Найсвіжіша репліка не завжди є найкращим вибором. Вона може бути перевантажена, пошкоджена або розташована в недоступному регіоні.
Після перемикання старий primary не можна просто під’єднати назад як звичайну репліку без перевірки. Він міг прийняти записи, яких немає в нового primary, що призведе до розходження даних.
Розподіл читань між репліками може збільшити пропускну здатність, але потребує правил.
Не слід спрямовувати запити на репліку, якщо:
її затримка перевищує допустиму;
вона не застосувала необхідну версію даних;
вона має помилки сховища;
її стан невідомий через проблеми моніторингу.
Для критичних операцій корисно розділяти:
читання, де потрібні найсвіжіші дані;
читання, для яких допустима невелика застарілість.
Наприклад, сторінка балансу користувача може читатися з primary, а популярні товари або публічну статистику — з асинхронної репліки.
Репліка часто повторює помилкові або видалені дані primary. Якщо застосунок випадково видалив записи, видалення також може потрапити на репліку.
Для захисту від цього потрібні окремі резервні копії та можливість відновлення до попереднього моменту.
Після запису наступний запит може потрапити на репліку, яка ще не отримала зміну. Це створює помилкове враження, що запис не відбувся.
Потрібно визначити політику read-after-write: читання з primary, прив’язка користувача до вузла або очікування потрібної позиції реплікації.
Вибір першої доступної репліки може призвести до втрати більшої кількості підтверджених записів.
Перед перемиканням потрібно враховувати актуальність даних репліки.
Три репліки в одній зоні можуть одночасно втратити доступ через одну локальну проблему. Надійність залежить не лише від кількості копій, а й від їхнього розміщення, незалежності та процесу відновлення.
Система може працювати без помітної затримки під час тестів, але відставати в години пікового навантаження. Lag потрібно вимірювати та контролювати в реальних умовах.
Реплікація підтримує кілька копій даних і підвищує доступність системи.
Синхронна реплікація зменшує ризик втрати даних, але збільшує затримку та залежність від доступності реплік.
Асинхронна реплікація швидша й стійкіша до тимчасових проблем реплік, але допускає застарілі читання та втрату частини останніх записів.
Реплікаційна затримка впливає на узгодженість читань, RPO та безпеку failover.
Primary-replica є простою топологією, ланцюги зменшують кількість з’єднань, а multi-primary створює проблему конфліктів.
Реплікація не замінює резервне копіювання.
Надійна система має моніторити lag, мати чітку політику вибору репліки та перевірений процес перемикання.