12 млрд реквестов в місяць за 120$ на java

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

Цей пост про архітектуру нашої системи, її еволюційного розвитку протягом вже майже 3-х років і компромісах між швидкістю розробки, продуктивністю, вартістю і простотою.

Спрощено завдання виглядала так — потрібно з'єднати мікроконтролер з мобільним додатком через інтернет. Приклад — натискаємо кнопку в додатку запалюється світлодіод на мікроконтролері. Тушкуємо світлодіод на мікроконтролері і кнопка в додатку відповідно змінює статус.

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

Відразу після запуску вся наша архітектура виглядала так:



Це була 1 виртуалка від Digital Ocean за 80$ в міс (4 CPU, 8 GB RAM, 80 GB SSD). Взяли з запасом. Так як «а раптом лоад піде?». Тоді ми дійсно думали, що ось, запустити і тисячі користувачів ринут на нас. Як виявилося — залучити і заманити користувачів та ще завдання і навантаження на сервер — останнє, про що варто думати. З технологій на той момент була лише Java 8 і Netty з нашим власним бінарним протоколом ssl/сокети tcp (так так, без БД, spring, hibernate, tomcat, websphere та інших принад кривавого ентерпрайза).

Всі користувальницькі дані зберігалися просто в пам'яті і періодично скидалися файли:

try (BufferedWriter writer = Files.newBufferedWriter(fileTo, UTF_8)) {
writer.write(user.toJson());
}


Весь процес підняття сервера зводився до однієї рядку:

java -jar server.jar &

Пікове навантаження відразу після запуску склала 40 річок-с. Цього цунамі так і не відбулося.

Тим не менш, ми дуже багато і наполегливо працювали, постійно додавали нові фічі, слухали відгуки наших користувачів. Користувацька база хоч і повільно, але стабільно і постійно росла на 5-10% кожний міс. Так само зростала і навантаження на сервер.

Першою серйозною фичей став репортинг. В момент коли ми почали впроваджувати — навантаження на систему вже становила 1 млрд реквестов в місяць. Причому більшість запитів були реальні дані, такі як показання датчиків температури. Було очевидно, що зберігати кожен запит — дуже дорого. Тому ми пішли на хитрощі. Замість збереження кожного реквеста — ми розраховуємо середнє значення в пам'яті з хвилинної гранулярностью. Тобто, якщо ви послали протягом хвилини числа 10 і 20, то на виході отримаєте значення 15 для цієї хвилини.

Спочатку я піддався хайпи і реалізував даний підхід на apache spark. Але коли справа дійшла до деплоймента, зрозумів що овчинка не варта вичинки. Так звичайно було «правильно» і «энтерпрайзному». Але тепер мені потрібно було деплоить і моніторити 2 системи замість мого затишного монолитика. Крім того додавався оверхед на серіалізацію даних і їх передачу. Загалом я позбувся від спарка і просто підраховую значення в пам'яті і раз у хвилину скидаю на диск. На виході виглядає це так:


Система з одним сервером монолітом відмінно працювала. Але були й цілком очевидні мінуси:

  • Так як сервер був у Нью-Йорку — у віддалених районах, наприклад, Азії були візуально видно лаги при інтерактивному використанні програми. Наприклад, коли ви змінювали рівень яскравості лампи з допомогою слайдера. Нічого критичного і ні один із користувачів на це не скаржився, але ми змінюємо світ, чорт забирай.
  • Деплой вимагав обриву всіх з'єднань і сервер був недоступний на ~5 сек при кожному перезапуску. В активну фазу розробки ми робили близько 6 деплоев в міс. Що забавно — за весь час ось таких рестартов — ні один користувач не помітив недоступність серверів. Тобто рестарты були настільки швидкими (привіт спрінг і томкат), що користувачі взагалі не помічали їх.
  • Відмова одного сервера, датацентри ложив все.
Через 8 міс після запуску — потік нових фіч трохи спав і у мене з'явився час, щоб змінити цю ситуацію. Задача була проста — зменшити затримку в різних регіонах, знизити ризик падіння всієї системи одночасно. Ну і зробити все це швидко, просто, дешево і мінімальними зусиллями. Стартап, все-таки.

Друга версія вийшла такою:





Як Ви, напевно, помітили — я зупинив свій вибір на GeoDNS. Це було дуже швидке рішення — вся настройка 30 хв в Amazon Route 53 на почитати і налаштувати. Досить дешеве — Geo DNS роутинг у амазона коштує 50$ в міс (я шукав альтернативи дешевше, але не знайшов). Досить просте — так як не потрібен був лоад балансер. І вимагало мінімум зусиль — довелося лише трохи підготувати код (зайняло менше дня).

Тепер у нас було 3 монолітних сервера по 20$ (2 CPU, 2 GB RAM, 40 GB SSD) + 50$ за Geo DNS. Вся система коштувала 110$ в міс, при цьому вона мала на 2 ядра більше за ціну на 20$ дешевше. У момент переходу на нову архітектуру навантаження становила 2000 річок-с. А колишня виртуалка була завантажена лише на 6%.



Всі проблеми моноліту вище — вирішувалися, але з'являлася нова — при переміщенні людини в іншу зону — він буде потрапляти на інший сервер і в нього нічого не буде працювати. Це був усвідомлений ризик і ми на нього пішли. Мотивація дуже проста — юзери не платять (на той момент система була повністю безкоштовною), так хай терплять. Так само ми скористалися статистикою, згідно з якою — лише 30% американців хоч раз в житті покидали свою країну, а регулярно переміщуються лише 5%. Тому припустили, що дана проблема торкнеться лише невеликий % наших користувачів. Пророкування справдилося. В середньому ми отримували близько одного листа в 2-3 дні від користувача у якого “Пропали проекти. Що робити? Врятуйте!". З часом такі листи почали дуже сильно дратувати (незважаючи на детальну інструкцію як швидко користувачеві це пофіксити). Тим більше такий підхід навряд би влаштував бізнес, на який ми тільки почали переключатися. Потрібно було щось робити.

Варіантів вирішення проблеми було багато. Я вирішив, що найдешевшим способом це зробити буде направляти мікроконтролери і додатки на один сервер (щоб уникнути оверхед при передачі повідомлень з одного сервера до іншого). Загалом вимоги до нової системи вимальовувалися такими — різні сполуки одного користувача не повинні потрапляти на один сервер і потрібен shared state між такими серверами, щоб знати куди конектить користувача.

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



Так, я нищеброд і наївний чукотський хлопець. Я думав що зможу підняти одну ноду кассандри на найдешевшій виртуалке в ДО за 5$ — 512 MB RAM, 1 CPU. І я навіть прочитав статтю щасливчика, який піднімав кластер на Rasp PI. На жаль, мені не вдалося повторити його подвиг. Хоча я прибрав/урізав всі буфери, як було описано в статті. Підняти одну ноду кассандри мені вдалося лише на 1Гб инстансе, при цьому нода відразу ж впала з OOM (OutOfMemory) при навантаженні 10 річок-с. Більш-менш стабільно кассандра поводилася з 2ГБ. Наростити навантаження однієї ноди касандри до 1000 річок-с так і не вдалося, знову ВОМ. На цьому етапі я відмовився від касандри, так як навіть якщо б вона показала гідний перформанс, мінімальний кластер в одному датацентрі обходився б у 60уе. Для мене це було дорого, враховуючи що наш дохід тоді становив 0$. Так як зробити треба було вчора — я приступив до плану Б.



Старий, добрий постгрес. Він ще ніколи мене не підводив (ну добре, майже ніколи, так, full vacuum?). Постгрес відмінно запускався на найдешевшій виртуалке, абсолютно не їв RAM, вставка 5000 рядків батчем займала 300мс і навантажувала єдине ядро на 10%. Те що треба! Я вирішив не розгортати БД в кожному з датацентрів, а зробити одне загальне сховище. Так як постгрес скейлить/шардить/майстер-слейвить важче, ніж ту ж касандру. Та і запас міцності це дозволяв.

Тепер належало вирішити іншу проблему — направляти клієнта і його мікроконтролери на один і той же сервер. По суті, зробити sticky session для tcp/ssl з'єднань і свого бінарного протоколу. Так як вносити кардинальні зміни в існуючий кластер не хотілося, я вирішив переиспользовать Geo DNS. Ідея була така — коли мобільний додаток отримує IP адресу від Geo DNS, додаток відкриває з'єднання і шле login з цього IP. Сервер у свою чергу або обробляє команду логіна і продовжує працювати з клієнтом у разі якщо це «правильний» сервер або повертає йому команду redirect із зазначенням IP куди він повинен конектится. В гіршому випадку процес з'єднання виглядає так:



Але був один маленький ньюанс — навантаження. Система на момент впровадження обробляла вже 4700 річок-с. До кластеру постійно були підключені ~3к пристроїв. Періодично конектилось ~10к. Тобто при поточному темпі зростання через рік це вже буде 10к річок-с. Теоретично могла виникнути ситуація, коли багато девайсів одночасно підключаються до одного сервера (наприклад при рестарті, ramp up period) і якщо, раптом, всі вони коннектились «не до того» сервера, то могла виникнути занадто велике навантаження на БД, що може призвести до її відмови. Тому я вирішив підстрахуватися і інформацію про user-serverIP виніс у редис. Підсумкова система вийшла такою.



При поточному навантаженні в 12 млрд річок в місяць вся система навантажена в середньому на 10%. Мережевий трафік ~5 Mbps (in/out, завдяки нашому простому протоколу). Тобто в теорії такий кластер за 120$ може витримати до 40к річок-с. З плюсів — не потрібен лоад балансер, простий деплой, обслуговування та моніторинг досить примітивні, є можливість вертикального зростання на 2 порядку (10х за рахунок утилізації поточного заліза і 10х за рахунок більш потужних виртуалок).

Проект опен-сорс. Исходники можна глянути тут.

Ось, власне, і все. Сподіваюся стаття Вам сподобалася. Будь-яка конструктивна критика, поради і питання вітаються.
Джерело: Хабрахабр

0 коментарів

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