Як перестати турбуватися та почати жити без моноліту

Як перестати турбуватися та почати жити без моноліту

Усі ми любимо історії. Нам подобається, сидячи біля вогнища, розповідати про свої колишні перемоги, битви або просто про свій досвід роботи.

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

Колись давним-давно в нашій компанії була пара «монолітів» і одна на всіх «стеля», до якої ці моноліти повільно, але чітко наближалися, обмежуючи політ нашої компанії, наш розвиток. І було однозначне розуміння: якось ми жорстко впораємося в цю стелю.

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

На сьогоднішній день для внесення змін є купа інструментів та засобів у вигляді CI/CD, K8S тощо. У «монолітний» час нам не потрібно так багато всяких іноземних слів. Достатньо було просто поправити «хранимочку» в базі.

Але час йшов вперед, і кількість запитів йшла вперед разом з ним, вистрілюючи RPS часом вище за наші можливості. З виходом на ринок країн СНД навантаження на процесорі БД першого моноліту не опускалося нижче 90%, а RPS трималися на рівні 2400. І це були не просто маленькі селектики, а здорові запити з купою перевірок та JOIN'ів, які могли пробігтися мало не по половині даних і натомість великого IO.

Коли ж на сцені почали з'являтися повноцінні розпродажі на «Чорну п'ятницю», а Wildberries почав проводити їх одним з перших у Росії, то ситуація стала зовсім сумною. Адже навантаження у такі дні зростає втричі.
Ох, ці «монолітні часи»! Впевнений, що і ви стикалися з подібним, і досі не можете зрозуміти, як могло статися з вами.

Що тут вдієш — мода властива і технологіям. Ще років 5 тому нам довелося переосмислити одну з таких мод у вигляді наявного сайту на .NET і MS SQL-сервера, який дбайливо зберігав у собі всю логіку роботи самого сайту. Зберігав настільки дбайливо, що розпилювати такий моноліт виявилося довгим та зовсім непростим задоволенням.
Невеликий відступ.

На різноманітних заходах я кажу: «якщо ви не розпилювали моноліт, значить ви не росли!» Цікава ваша думка щодо цього, напишіть її, будь ласка, у коментарях.

І гримнув грім

Повернемося до нашого «вогнище». Щоб розподілити навантаження «монолітної» функціональності, ми вирішили поділити систему на мікросервіси, що базуються на opensource-технологіях. Тому що, як мінімум, їх масштабування дешевше. А розуміння того, що масштабувати доведеться (і чимало), у нас було на всі 100%. Адже вже на той момент вдалося вийти на ринки сусідніх країн, і кількість реєстрацій, так само як і кількість замовлень, почала зростати ще сильніше.

Проаналізувавши перших претендентів на виліт із моноліту до мікросервісів, ми зрозуміли, що у 80 % запис у них на 99 % йде з back office-систем, а читання — з передової. Насамперед це стосувалося пари важливих для нас підсистем — даних користувача та системи обчислення кінцевої вартості товарів на підставі інформації про додаткові клієнтські знижки та купони.

На правах відступу. Зараз страшно уявити, але крім вищезгаданих підсистем, з нашого моноліту так само були винесені товарні каталоги, корзина користувача, система пошуку товарів, система фільтрації товарних каталогів і різного роду рекомендаційні системи. Для роботи кожного з них існують окремі класи вузько заточених систем, але колись всі вони жили в одному "теремці".

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

Внаслідок цього у нас народилася схема, що добре поєднується з Tarantool.

На той час для роботи мікросервісів було обрано схеми роботи з кількома ЦОДами на віртуальних та апаратних машинах. Як показано на малюнках, були використані варіанти реплікацій Tarantool як у режимі master-master, так і master-slave.

Як перестати турбуватися та почати жити без моноліту
Архітектура. Варіант 1. Сервіс користувачів

На даний час це 24 шарди, у кожному з яких по 2 інстанси (по одному на кожен ДЦ), все в режимі майстер-майстер.

Поверх БД знаходяться програми, які звертаються до реплік БД. Програми працюють із Tarantool через нашу кастомну бібліотеку, яка реалізує інтерфейс Go-драйвера Tarantool. Вона бачить усі репліки і може працювати з майстром на читання та запис. По суті вона реалізує модель replica set, до якої додано логіку вибору реплік, виконання повторних спроб, circuit breaker і rate limit.

При цьому є можливість змінити політику вибору репліки в розрізі шарди. Наприклад, раундробіном.

Як перестати турбуватися та почати жити без моноліту
Архітектура. Варіант 2. Сервіс розрахунку кінцевої вартості товару

Кілька місяців тому більшість запитів щодо розрахунку кінцевої вартості товарів пішла на новий сервіс, який в принципі працює без баз даних, але якийсь час тому всі 100% обробляв сервіс з Tarantool під капотом.

Бд сервісу це 4 майстри, у яких синхронізатор збирає дані, і кожен із цих майстрів з реплікації роздає дані на readonly-репліки. У кожного майстра приблизно по 15 таких реплік.

Що в першій, що в другій схемі, за недоступності одного ДЦ, додаток може отримувати дані в другому.

Варто зазначити, що в Tarantool реплікація є досить гнучкою і конфігурується в runtime. В інших системах бувало складнощі. Наприклад зі зміною параметрів max_wal_senders і max_replication_slots в PostgreSQL потрібно перезапуск майстра, що у деяких випадках може призвести до розрив з'єднань між додатком і СУБД.

Шукайте і знайдете!

Чому ми не зробили «як нормальні люди», а вибрали нетиповий спосіб? Дивлячись, що вважати нормальним. Багато хто взагалі робить кластер з Mongo і розмазує його за трьома георозподіленими ДЦ.

На той час ми вже мали два проекти на Redis. Перший - кеш, а другий був persistent-сховище для не дуже критичних даних. Ось із ним доводилося досить складно, частково з нашої вини. Іноді досить великі обсяги лежали в ключі, і іноді сайту ставало погано. Цю систему ми використали у master-slave варіанті. І було багато випадків, коли щось діялося з майстром і реплікація ламалася.

Тобто Redis хороший для stateless-завдань, а не для stateful. В принципі, він дозволяв вирішити більшість завдань, але тільки якщо це були key-value-рішення з парою індексів. Але у Redis на той момент було досить сумно з персистентністю та реплікацією. До того ж були нарікання на продуктивність.

Думали про MySQL та PostgreSQL. Але перший якось не прижився у нас, а другий сам по собі досить накручений продукт, і будувати на ньому прості сервіси було б недоцільно.
Пробували Riak, Cassandra, навіть графову БД. Усе це досить нішеві рішення, які підходили роль загального універсального інструменту до створення сервісів.

Кінець кінцем зупинилися на Tarantool.

Ми звернулися до нього, коли він був у версії 1.6. Нас зацікавив у ньому симбіоз key-value та функціональності реляційної БД. Є вторинні індекси, транзакції та спейси, це як таблички, але непрості, можна зберігати у них різну кількість колонок. Але кілер-фічею Tarantool були вторинні індекси у поєднанні з key-value та транзакційністю.

Також свою роль зіграло чуйне російськомовне співтовариство, яке готове в чаті прийти на допомогу. Ми цим активно користувалися та прямо жили в чаті. І не варто забувати про пристойне persistent без явних ляпів і косяків. Якщо подивитися нашу історію з Tarantool, у нас було багато болю та факапів з реплікацією, але ми жодного разу не втратили дані з його вини!

Впровадження почалося важко

На той момент у нас основним стеком розробки був .NET, до якого не було конектора Tarantool. Ми одразу почали щось робити на Go. З Lua теж виходило непогано. Головна проблема на той момент була з налагодженням: у .NET з цим все шикарно, а після цього поринути у світ embedded Lua, коли в тебе, окрім ліг, ніякого дебагу немає, було важко. До того ж реплікація чомусь періодично розвалювалася, довелося вникати у пристрій двигуна Tarantool. У цьому допоміг чат, меншою мірою документація, іноді дивилися код. На той момент документація була така собі.

Так протягом кількох місяців вдалося набити шишок і отримувати гідні результати роботи з Tarantool. Оформили в git еталонні напрацювання, які допомагали зі становленням нових мікросервісів. Наприклад, коли виникало завдання: зробити черговий мікросервіс, то розробник дивився на вихідники еталонного рішення в репозиторії, і створення нового йшло не більше тижня.

Це були особливі часи. Умовно тоді можна було підійти до адміна за сусіднім столом і попросити: «Дай мені віртуалку». Хвилин за тридцять машина була вже в тебе. Ти сам підключався, все встановлював і тобі заводили на неї трафік.

Сьогодні так уже не вийде: треба накрутити на сервіс моніторинг, журналування, покрити тестами функціональність, замовити віртуалку чи постачання до Кубера тощо. Загалом, так буде краще, хоча довше і клопітніше.

Розділяй і володарюй. Яка ситуація з Lua?

Була серйозна дилема: деякі команди не мали надійно викочувати зміни в сервісі з великою кількістю логіки на Lua. Найчастіше це супроводжувалося непрацездатністю сервісу.

Тобто розробники готують якусь зміну. Tarantool починає робити міграцію, а репліка ще зі старим кодом; туди прилітає по реплікації якийсь DDL ще щось, і код просто розвалюється, тому що це не враховано. В результаті процедура оновлення у адмінів була розписана на лист А4: зупинити реплікацію, оновити це, увімкнути реплікацію, вимкнути тут, оновити там. Жах!

Зрештою, зараз ми найчастіше намагаємося на Lua нічого не робити. Просто за допомогою iproto (бінарний протокол для взаємодії з сервером) і все. Можливо, це брак знань розробників, але з цього погляду система складна.

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

Де зараз є Tarantool?
Tarantool використовується в сервісі розрахунку кінцевої вартості товарів з урахуванням купонів на знижку, він же Промотайзер. Як уже говорив раніше, зараз він відходить від справ: його замінює новий каталожний сервіс із розрахованими цінами, але ще півроку тому всі розрахунки робилися у «Промотайзері». Раніше половина його логіки була написана Lua. Два роки тому з сервісу зробили сховище, а логіку переписали на Go, тому що механіка роботи знижок трохи змінилася і сервісу не вистачало продуктивності.

Один із найкритичніших сервісів – це профіль користувача. Тобто всі користувачі Wildberries зберігаються в Tarantool, а їх близько 50 млн. Шардована за ID користувача система, рознесена за декількома ДЦ з обв'язкою на Go-сервісах.
За RPS колись лідером був "Промотайзер", що доходило до 6 тисяч запитів. Якоїсь миті у нас було 50-60 примірників. Зараз же лідер по RPS — профілі користувачів, приблизно під 12 тис. У цьому сервісі застосовується кастомне шардування з розбиттям по діапазонах ID користувача. Сервіс обслуговує понад 20 машин, але це дуже багато, плануємо зменшити виділені ресурси, тому що йому достатньо потужностей 4-5 машин.

Сервіс сесій – це наш перший сервіс на vshard та Cartridge. Налаштування vshard та оновлення Cartridge зажадали від нас певних трудовитрат, але у результаті все вийшло.

Сервіс для відображення різних банерів на сайті та в мобільному додатку був одним із перших, випущених одразу на Tarantool. Цей сервіс примітний тим, що йому років 6-7, він досі у строю і жодного разу не перезавантажувався. Застосовувалась реплікація master-master. Ніколи нічого не ламалося.

Є приклад використання Tarantool для функціональності швидких довідників у складській системі, щоб швидко перевіряти ще раз інформацію в деяких випадках. Пробували використовувати під цю справу Redis, але дані в пам'яті займали більше місця, ніж Tarantool.

Сервіси листа очікування, передплати клієнтів, модних нині сторіс і відкладених товарів також працюють з Tarantool. Останній сервіс у пам'яті займає близько 120 ГБ. Це найоб'ємніший сервіс із вищеперелічених.

Висновок

Завдяки вторинним індексам у поєднанні з key-value та транзакційністю Tarantool відмінно підходить для архітектур на основі мікросервісів. Однак ми зіткнулися з труднощами, коли викочували зміни у сервісах із великою кількістю логіки на Lua – сервіси часто переставали працювати. Перемогти це нам не вдалося, і згодом ми дійшли різних комбінацій Lua і Go: знаємо, де варто використовувати одну мову, а де — іншу.

Що ще почитати на тему

Джерело: habr.com

Додати коментар або відгук