Асинхронна реплікація без цензури



Олег Царьов ( zabivator
Є майстер, майстер несподівано впав, але система продовжує працювати. Клієнти мігрують на другу базу. Потрібно робити резервні копії бази. Якщо робити резервні копії на основній базі, ми можемо отримати якісь проблеми продуктивності, збільшення часу відгуку. Це погано. Тому досить поширений приклад асинхронної реплікації — це зняття резервної копії з слэйва. Інший приклад — це міграція важких запитів з майстра на слэйв, з основної бази на другу. Наприклад, побудова звітів.

Іноді буває необхідно, щоб програма могла отримувати всі оновлення з бази і бажано в режимі реального часу. Цим займається ореп source бібліотека, яка називається libslave.



Ця стаття — розшифровка доповіді Олега Царьова ( zabivator ), через рік після прочитання Олег опублікував ще одну статтю на цю тему   PostgreSQL vs MySQL.
Сходіть посиланням на слайді, почитайте — відмінна стаття.

Якщо зібрати всі разом, ми отримаємо приблизно таку картинку:



У нас є один майстер і купа слэйвов — seilover для резервування, якщо майстер впав, слэйв для бекапів, слэйв для побудови звітів, і кілька слэйвов, які ретранслюють зміни на штуку, яка називається bannerd (це назва демона, і він працює через libslave). Їх багато, тому коштують такі ось проксі.

У нас досить великий проект з досить великою БД, при цьому ми завжди працюємо, наш сервіс не падає. Ми роздаємо рекламу, і у нас достатньо серйозне навантаження, реплікація у нас використовується повсюдно.

Основна властивість БД — те, що вона гарантує принцип «все або нічого», тобто зміни відбуваються або цілком, або не відбуваються взагалі. Як це робиться, як база гарантує цілісність даних?



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

Проблема в тому, що принцип «все або нічого» на сучасному «залізі» не представляється можливим. Це фізичні обмеження світу. Навіть з оперативною пам'яттю — транзакційна пам'ять з'явилася лише нещодавно у Intel'а. І незрозуміло, як з цим жити… Рішенням є журнал:



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

Цей алгоритм називається Point In Time Recovery або PITR. Пропоную ознайомитися з інформацією за посиланнями:



Це дуже пізнавально.

Головні питання, які постають перед розробником будь БД:

  • як організовувати журнал?
  • як його писати?
  • як писати його менше?
  • як зробити так, щоб це працювало швидше?
  • при чому тут реплікація?
Прямий спосіб зробити реплікацію — це скопіювати журнал з майстра на слэйв і застосувати його на слэйв. Журнал використовується для гарантії консистентности даних. Той же самий механізм можна застосувати до слэйву. І ми отримуємо реплікацію, по суті, майже нічого не додаючи в нашу систему.

PostgreSQL працює саме так. Журнал називається Write-Ahead Log, і в нього потрапляють фізичні зміни, тобто оновлення сторінок. Є сторінка в пам'яті, на ній лежать якісь дані, ми з нею щось зробили — ось цю різницю ми записуємо в журнал, а потім він їде на слэйв.

Скільки журналів в MySQL? Давайте розбиратися. Спочатку в MySQL не було ніяких журналів, взагалі. Був движок MyISAM, а в ньому журналу немає.

На малюнку ви можете бачити штуку, яка називається Storage Engines:



Storage Engine — це така сутність, яка займається питаннями, як писати дані на диск і як нам їх звідти читати, як за ним шукати тощо

Потім прикрутили реплікацію. Реплікація — це одна строчка в самому лівому верхньому квадратику — Management Services&Utilites.

Для реплікації знадобився журнал. Його почали писати. Він називається Binary Log. Ніхто не думав про те, щоб його використати якось інакше, просто зробили.

Приблизно в цей же час MySQL подарували новий Storage Engine, який називається InnoDB. Це широко використовувана штука, і в InnoDB свій журнал. Вийшло два журнали — InnoDB і Binary Log. Цей момент став точкою неповернення, після чого з'явилися проблеми, які вирішити дуже важко.

Binary Log не використовується для Point In Time Recovery, а InnoDB Undo/Redo Log не використовується в реплікації. Вийшло, що в PostgreSQL журнал один, а в MySQL їх, як би, два, але у Binary Log, який потрібен для реплікації, є два або три формату (типу).

Перший тип, який з'явився, який було простіше всього зробити, це Statement-based Binary Log. Що це таке? Це просто файл, в який послідовно пишуться транзакція за трансакцією. Це виглядає приблизно так:



В угоді вказується БД, на якій відбуваються ці оновлення, вказується timestamp часу початку транзакції, і далі йде сама транзакція.

Другий тип називається Row-based реплікація. Це журнал, в який пишуться не самі запити, а ті рядки, які вони замінюють. Він складається з двох частин — BEFORE image і AFTER image:



На картинці BEFORE image зверху, а AFTER image — внизу.

У BEFORE image поміщаються ті рядки, які були до виконання транзакції. Червоним кольором позначені рядки, які видаляються:



Вони з BEFORE image нагорі, але їх немає внизу — AFTER image, значить, вони видаляються.

На наступній картинці зеленим позначені рядки, які додалися:



Сині UPDATE ' и є і в BEFORE image, і в AFTER image. Це оновлення.

Проблема такого рішення пов'язана з тим, що до недавнього часу в Row-based реплікації писалися в log всі колонки, навіть якщо ми оновили одну. В MySQL 5.6 це полагодили, і з цим має стати легше.

Є ще один тип Binary Log'а — Mixed-based. Він працює або як Statement-based, або як Row-based, але він широко не поширена.

Який з цих журналів краще?

Спочатку поговоримо про реляційних таблицях. Часто думають, що реляційна таблиця — це масив. Деякі навіть думають, що це двовимірний масив. Насправді, це набагато більш складна штука. Це мультимножество — набір певного сорту кортежів, над яким не встановлено порядку. У SQL-таблиці немає порядку. Це важливо. І, як результат, коли ви робите SELECT* з БД (просканувати всі записи), результат виконання запиту може змінюватися — рядки можуть бути в одному порядку, а можуть і в іншому. Про це треба пам'ятати.

Ось приклад запиту, який Statement-based реплікації буде працювати некоректно:



Ми з таблиці видалили primary_key і додали новий — авто-інкрементний. На майстрі і слэйве різний порядок рядків. Так ми отримали неконсистентные дані. Це особливість Statement-based реплікації, і з цим можна зробити не так вже багато.

Це цитата з офіційної MySQL-документації:



Потрібно створювати ще одну таблицю, в неї переливати дані, а потім її перейменовувати. Ця особливість може «вистрілити» в найнесподіваніших місцях.

Напевно, наступний слайд — один з найважливіших у доповіді, про те, як реплікацію можна класифікувати:



Робота на рівні сховища, як робить PostgreSQL, називається фізичної реплікацією — ми працюємо безпосередньо зі сторінками. А Row-based реплікація, де ми зберігаємо набір кортежів до і після транзакції — це логічна.

А Statement-based реплікація, взагалі, на рівні запитів. Так не роблять, але так зроблено… Звідси випливає важливе цікаву властивість: коли у нас працює Row-based реплікації, тобто логічна реплікація, вона не знає, як саме дані зберігаються на диску. Виходить, для того щоб реплікація працювала, потрібно здійснювати якісь операції в пам'яті.

Також виходить, що фізична реплікація (PostgreSQL, InnoDB) впирається, в основному, в диск, а MySQL-реплікація впирається, в основному, в слэйв, причому обидві — Row-based і Statement-based. Row-based потрібно просто знайти рядки і зробити оновлення, а з Statement-based все набагато гірше — для неї потрібно виконати запит. Якщо запит на майстрі виконувався, наприклад, півгодини, то він і на слэйве буде виконуватися півгодини. Це реплікація, але досить невдала.

Крім того, PostgreSQL пише на диск в два місця — в сховище даних в журнал. У MySQL таких місць три — сховище (tablespace), журнал (undo/redo log), і Binary Log, який використовується в реплікації, тобто писати на диск потрібно в 1,5 рази більше. MySQL — відмінна архітектура, але з нею часто бувають проблеми.

Багато бачили відстаючі репліки MySQL. Як знайти причину гальмування репліки? Діагностувати важко. Є засіб діагностики в MySQL, називається log повільних запитів. Ви можете відкрити, знайти топ найважчих запитів і виправити їх. З реплікацією це не працює. Потрібно проводити статистичний аналіз — вважати статистику — які таблиці стали частіше використовуватися. Вручну це зробити дуже важко.

В MySQL 5.6 / 5.7 з'явилася SLAVE PERFORMANCE SCHEMA, на базі якої таку діагностику провести простіше. Зазвичай ми відкриваємо лог комітів в puppet і дивимося, що ж ми викотили в той час, коли реплікація почала відставати. Іноді навіть це не допомагає, доводиться ходити по всім розробникам і питати, що вони зробили, вони зламали реплікацію. Це сумно, але з цим доводиться жити.

Асинхронної реплікації є майстер, куди ми пишемо, і є слэйв, з якого тільки читаємо. Слэйв не повинен впливати на майстра. І в PostgreSQL він не впливає. В MySQL це, на жаль, не так. Для того щоб Statement-based реплікація, яка повторює запити, працювала коректно, є спеціальний прапорець. У InnoDB, зауважте, тобто у нас архітектура поділяє реплікацію вище, а storage engine нижче. Але storage engine, для того, щоб реплікація працювала, повинен, грубо кажучи, уповільнювати insert'и в таблицю.

Інша проблема полягає в тому, що майстер виконує запити паралельно, тобто одночасно, а слэйв їх може застосовувати послідовно. Виникає питання — а чому слэйв не може застосовувати їх паралельно? Насправді, з цим все непросто. Є теорема про серіалізації транзакцій, яка розповідає, коли ми можемо виконувати запити паралельно, а коли послідовно. Це окрема складна тема, розберіться в ній, якщо вам цікаво і потрібно, наприклад, почитавши за посиланням — http://plumqqz.livejournal.com/387380.html.

В PostgreSQL реплікація впирається, в основному, в диск. Диск не параллелится, і нас не хвилює один потік, все одно, ми сидимо, в основному, в диску. CPU ми майже не споживаємо.

В MySQL реплікація впирається в процесор. Це прекрасна картинка — великий, потужний сервер, 12 ядер. Працює одне ядро, заодно включена реплікацією. З-за цього репліка задихається. Це дуже сумно.

Для того щоб виконувати запити паралельно існує угруповання запитів. У InnoDB є спеціальна опція, яка управляє тим, як саме ми групуємо транзакції, як саме ми їх пишемо на диск. Проблема в тому, що ми можемо їх згрупувати на рівні InnoDB, а рівнем вище — на рівні реплікації — цієї функціональності не було. У 2010 р. Крістіан Нельсен з MariaDB реалізував таку фічу, яка називається Group Binary Log Commit — ми поговоримо про неї трохи пізніше. Виходить, ми журнал (а це досить складна структура даних) повторюємо на двох рівнях — Storage Engine і реплікації, і нам потрібно тягати фічі з одного рівня на інший. Це складний механізм. Більше того, нам потрібно одночасно консистентним писати відразу в два журнали — two-phase-commit. Це ще гірше.

На наступній картинці ми бачимо два графіка:



Синій графік демонструє те, як масштабується InnoDB, коли ми йому додаємо треди. Накидаємо треди — число транзакцій, які він обробляє, зростає.

Червона лінія показує ситуацію, коли включена реплікація. Ми включаємо реплікацію і втрачаємо масштабованість. Тому що лог в Binary Log пишеться синхронно, і Group Binary Log Commit це вирішує.

Сумно, що доводиться так робити із-за поділу — Storage Engine внизу, реплікація нагорі. З цим все погано. В MySQL 5.6 і 5.7 ця проблема вирішена — Group є Binary Log Commit, і майстер тепер не відстає. Тепер це намагаються використовувати для паралелізму реплікації, щоб на слэйве запити з однієї групи запустити паралельно. Тут я написав, що з цього потрібно крутити:



З жовтня 2013 р., оскільки у нас багато, реплікація постійно відстає, всі засмучуються, я намагався цей паралелізм побачити. Можливо, я щось ще не зрозумів, що щось не то налаштував, було багато спроб, і результати виглядають приблизно так:



Блакитний графік — це MySQL 5.5.

По осі Y — споживання процесора на слэйве. По осі Х — час.

На даному графіку ми можемо бачити, коли реплікація почала наздоганяти з майстра і коли вона закінчила. Виходить цікава картинка — що 5.5 в один потік працює приблизно так само, як паралельна реплікація в 5.7 в чотири потоки. Тобто процесора споживається більше (зелена лінія), а працює по часу так само. Там працює чотири треда, чотири потоки. Якщо ж зробити один потік 5.7, він буде працювати гірше. Це якась регресія, в 5.7.5 її хотіли полагодити, але я перевірив — проблема поки актуальна. На моїх бенчмарках це так, це так на тестах з продакшеном, це даність. Я сподіваюся, що це виправлять.

У чому ще проблема — для того, щоб мігрувати, не зупиняючи сервіс, у нас в один момент часу на майстрі буде запущений MySQL 5.5, а на слэйве — 5.7. В 5.5 немає Group Binary Log Commit, значить 5.7 зможе працювати тільки в один потік. Це означає, що наша репліка на 5.7 почне відставати і не догонится ніколи. Поки є регресія з однопотокового 5.7 реплікацією, ми смигрировать не зможемо, ми сидимо на 5.5, у нас немає вибору.

Тепер найцікавіша частина — я підсумую все, що розповів, що залишилося за рамками доповіді через обмеження у часі (у мене матеріалу години на три).

По-перше, архітектурно є три типи журналів, є реплікація на фізичному рівні і на логічному рівні. Фізичний рівень — це сторінки. PostgreSQL сильний тим, що через його журнал йде, взагалі, оновлення таблиць, створення тригерів, створення збережених процедур, — і тому з ним проблем виникає менше. У MySQL, в залежності від того, який тип реплікації у нас включений, ми отримуємо або логічну реплікацію, або реплікацію на рівні запитів.

В принципі, будь-якого із цих журналів є свої плюси і мінуси, тому вибирати потрібно обачно.

Чим вони сильні/слабкі:



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

В MySQL є два серйозних пенальті, треда, як на майстра впливає реплікація:

  1. опція в InnoDB для того, щоб працювала Statement-based реплікація;
  2. без Group Binary Log Commit ми не отримуємо масштабування.
Row-based реплікація в MySQL працює краще, але й тут є свої проблеми.

Далі, слэйв. PostgreSQL впирається в диск, MySQL — в процесор.

З точки зору споживання дисків тут цікавіше. Наприклад, в Row-based реплікації в MySQL (в PostgreSQL буде приблизно так само) виходять десятки терабайт логів в день, у нас просто немає такої кількості дисків, щоб це все зберігати, тому ми сидимо на Statement-based. Це теж буває важливо — якщо репліка відстала, нам потрібно десь зберігати журнал. У цьому сенсі PostgreSQL, порівняно з Statement-based реплікацією виглядає гірше.

З процесором слэйва нам важливо побудувати хороші індекси на слэйве для того, щоб рядки легко перебували, щоб запити працювали добре. Це досить дивна метрика. Ми оптимізуємо слэйв з точки зору ефективності роботи реплікації, тобто ми хочемо слэйв для того, щоб будувати звіти, а нам доводиться ще налаштовувати, щоб слэйв не тільки звіти будував, але і наздоганяти встигав. MySQL parallel slave 5.6/5.7 — ми дуже чекаємо, коли він стане працювати добре, поки він не виправдовує сподівань.

Інша важлива тема — консистентним даних.

PostgreSQL репліка — це бінарна копія майстра. Тобто буквально — якщо ви зупиніть запис на майстер, дасте реплікації доїхати до кінця на слэйве, зупиніть процес на майстрі і слэйве і зробите бінарне порівняння PostgreSQL-майстра і PostgreSQL-слэйва, ви побачите, що вони однакові. В MySQL це не так. Row-based реплікація, яка працює з логічним поданням, з кортежами — у ній все update ' и, insert'и і delete'и працюють коректно, з ними все добре.

У Statement-based реплікації це вже не так. Якщо ви неправильно налаштували майстра і запустіть певні хитрі запити, ви можете отримати різні результати. З запитами, які працюють зі схемою бази, створення таблиць, побудова індексів тощо, — все ще сумніше, вони завжди йдуть як сирі запити… Потрібно постійно пам'ятати про особливості роботи Statement-based реплікації.

З mixed-based історія ще цікавіше — вона така, або інша, все треба дивитися.

Гнучкість. MySQL на даний момент дійсно краще тим, що реплікація в ньому більш гнучка. Ви можете побудувати різні індекси на майстрі і слэйве, можете поміняти навіть схему даних — іноді це буває потрібно, а в PostgreSQL зараз такої можливості немає. Крім того, в MySQL є libslave — це дуже потужна штука, ми її дуже любимо. Наші демони прикидаються MySQL-слэйвами і вони постійно в режимі реального часу отримують оновлення. У нас затримка становить приблизно 5 сек. — користувач побачив баннер або клікнув по ньому, демон це все заагрегировал, записав у базу, через 5 сек. демон, який роздає банери, про це дізнався. В PostgreSQL такого засобу немає.

Однак PostgreSQL планує наступне. По-перше, є така штука як Logical Log Streaming Реплікація — це спосіб трансформувати Write-Ahead Log. Наприклад, ми не хочемо повторити всі таблиці з даної бази, а хочемо копіювати тільки частина. Logical Log Streaming Replication дозволяє майстру пояснити, що з таблиць буде їхати на слэйв.

Також є ще Logical Decoding — спосіб візуалізувати те, що знаходиться в PostgreSQL Write-Ahead Log. Насправді, якщо ми можемо надрукувати в якомусь вигляді те, що у нас відбувається на слэйве, точніше, що нам прийшло через Write-Ahead Log, це означає, що ми можемо програмно реалізувати все те, що робить libslave. Отримали insert, update, delete, у нас «сіпнувся» потрібний callback, ми дізналися про зміни. Це і є Logical Decoding.

Висновок з цього досить цікавий — краще всього зробити нормальний журнал. У PostgreSQL журнал нормальний, туди потрапляють всі оновлення даних, зміни схеми, взагалі все. Це дає купу булочок, наприклад, коректно працює реплікацію, реплікацію, яка впирається тільки в диск, а не в процесор. Маючи такий журнал, вже можна дописати деякий набір патчів сам движок, для майстра, для слэйва, який дозволяє підвищити гнучкість, тобто фільтрувати таблиці, мати інші індекси.

А в MySQL в силу історичних причин вийшов журнал поганий, тобто MySQL — заручник свого історичного розвитку. Щоб вирішити виниклі в MySQL проблеми з продуктивністю, коректністю, потрібно переписати всю архітектуру, яка пов'язана зі Storage Engine, а це нереально.

Пройде зовсім небагато часу, я думаю, і PostgreSQL з фічами наздожене MySQL.

І, наостанок. Навіть якщо ви багато чого не зрозуміли з усього доповіді або вам потрібно розбиратися, але в будь-якому випадку запам'ятайте два головних висновки:

  1. Реплікація не є копією (резервних копій).
  2. Таблиця — це не двовимірний масив, а гомогенне мультимножество кортежів. Так коректно з точки зору computer science.
Цей доповідь мені допомагали робити багато людей:



Контакти
zabivator

Ця доповідь — розшифровка одного з кращих виступів на конференції розробників високонавантажених систем HighLoad++. Зараз ми активно готуємо конференцію 2016 року — у цьому році HighLoad++ пройде в Сколково, 7 і 8 листопада.

Тема реплікації вічна :) В цьому році ми будемо так чи інакше зачіпати її в двох доповідях.


Також деякі з цих матеріалів використовуються нами в навчальному онлайн-курс по розробці високонавантажених систем HighLoad.Guide — це ланцюжок спеціально підібраних листів, статей, матеріалів, відео. Вже зараз у нашому підручнику понад 30 унікальних матеріалів. Підключайтеся!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.