Як ми у Спортмайстрі обирали систему кешування. Частина 1

Вітання! Мене звуть Олексій П'янков, я розробник у компанії Спортмайстер. В цьому пості я розповів, як розпочиналася робота над сайтом Спортмайстер у 2012 році, які ініціативи вдалося «проштовхнути» і навпаки, які граблі ми зібрали.

Сьогодні я хочу поділитися думками, які йдуть за іншим сюжетом – вибір системи кешування для java-бэкенда в адмінці сайту. Цей сюжет має особливе значення для мене – хоча історія розгорталася лише 2 місяці, але ці 60 днів ми працювали по 12-16 годин і без жодного вихідного. Ніколи раніше не думав і не уявляв, що можна так багато працювати.

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

Як ми у Спортмайстрі обирали систему кешування. Частина 1

Коли нова версія сайту Спортмайстер була запущена в продакшн, дані надходили способом, м'яко кажучи, не дуже зручним. Основою служили таблиці, підготовлені для минулої версії сайту (Bitrix), які треба було затягнути в ETL, привести до нового вигляду та збагатити різними чарочками ще з десятка систем. Щоб нова картинка чи опис товару опинилися на сайті, потрібно було почекати до наступного дня – оновлення лише вночі, 1 раз на добу.

Спочатку було стільки турбот від перших тижнів виходу в харч, що такі незручності контент-менеджерів були дрібницею. Але як тільки все втряслося, розвиток проекту продовжився — за кілька місяців, на початку 2015 року ми почали активно розробляти адмінку. У 2015 і 2016 все йде добре, ми регулярно релізимо, адмінка охоплює все більшу частину підготовки даних і ми готуємося до того, що незабаром нашій команді довірять найважливіше та найскладніше – товарний контур (повна підготовка та ведення даних по всіх товарах). Але влітку 2017 року, якраз перед запуском товарного контуру, проект опиниться у дуже складній ситуації – саме через проблеми з кешуванням. Про цей епізод я хочу розповісти в другій частині цієї двосерійної публікації.

Але в цьому пості почну здалеку, подібні до деяких думок – уявлення про кешування, прокрутити які перед великим проектом заздалегідь було б гарним кроком.

Коли виникає завдання кешування

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

На перших етапах ми не думаємо про оптимізацію та продуктивність коду. Головне – функціональність, швидко викочувати пілот та перевіряти гіпотези. І якщо навантаження зростає – ми прокачуємо залізо. Збільшуємо рази на два-три, у п'ять, нехай у 10 разів. Десь тут – фінанси більше не дозволять. А у скільки разів зросте кількість користувачів? Це буде не те що 2-5-10, але у разі успіху — це буде від 100-1000 до 100 тисяч разів. Тобто рано чи пізно, але оптимізацією зайнятися доведеться.

Допустимо, деяка частина коду (назвемо цю частину функцією) працює непристойно довго, і ми хочемо скоротити час виконання. Функція – це може бути доступ до бази даних, можливо виконання якоїсь складної логіки – головне, що виконується довго. Наскільки можна скоротити час виконання? У межі – можна скоротити до нуля, не далі. А як можна скоротити час виконання до нуля? Відповідь: взагалі виключити виконання. Натомість – відразу повернути результат. А як дізнатися про результат? Відповідь: або вирахувати, або десь підглянути. Обчислити – це довго. А підглянути – це, наприклад, запам'ятати той результат, який функція видала минулого разу під час виклику з такими самими параметрами.

Тобто реалізація функції нам не має значення. Достатньо знати, від яких параметрів залежить результат. Тоді, якщо значення параметрів подати у вигляді об'єкта, який можна використовувати як ключ у деякому сховищі, то результат обчислення можемо зберегти і при наступному зверненні вважати його. Якщо ці запис-зчитування результату проходять швидше ніж виконання функції – маємо прибуток за швидкістю. Величина профіту може досягати і 100, і 1000, і 100 тисяч разів (10^5 - це швидше виняток, але у випадку з порядною базою - цілком можливо).

Основні вимоги до системи кешування

Перше, що може стати вимогою до системи кешування – швидка швидкість читання та трохи меншою мірою – швидкість запису. Це так, але тільки доти, доки ми не викочуємо систему у продакшн.

Розіграємо такий кейс.

Допустимо, ми забезпечили залізом поточне навантаження і тепер потроху впроваджуємо кешування. Небагато приростає користувачів, зростає навантаження – трохи додаємо кешів, прикручуємо то тут, то там. Так триває деякий час, і ось великовагові функції вже практично не викликаються - все основне навантаження лягає на кеш. Кількість користувачів за цей час зросла у N разів.

І якщо початковий запас по залізу міг бути 2-5 разів, то за допомогою кешу ми могли підтягнути продуктивність разів у 10 або, у хорошому випадку разів у 100, місцями, можливо, й у 1000. Тобто, на тому ж залозі – обробляємо у 100 разів більше запитів. Чудово, заслужили на пряник!

Але тепер, одного разу, випадково, система дала збій і кеш впав. Нічого особливого – адже кеш обрали на вимогу «висока швидкість читання та запису, решта не має значення».

Щодо стартового навантаження, запас по залізу у нас був 2-5 разів, а навантаження за цей час підросло в 10-100 разів. За допомогою кешу ми виключали виклики для важких функцій, і тому все літало. А тепер, без кешу – скільки разів у нас система просяде? Що в нас станеться? Система ляже.

Навіть якщо у нас кеш не впав, а тільки очистився на деякий час – його потрібно буде прогрівати, і це займе деякий час. І на цей час – основне навантаження ляже на функціонал.

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

муки вибору

У проекті з адмінкою вибір пройшов так: спочатку поставили Hazelcast, т.к. вже були знайомі із цим продуктом з досвіду основного сайту. Але, тут такий вибір виявився невдалим - під наш профіль навантаження Hazelcast працює не просто повільно, а повільно. А під термінами виведення в продукти ми на той момент уже підписалися.

Спойлер: як саме склалися обставини, що ми пропустили таку плюху та отримали гостру та напружену ситуацію – я розповім у другій частині – і як виявились, і як вибралися. Але зараз - скажу тільки, що це був сильний стрес, і "думати - якось не думається, трясемо пляшку". «Трясемо пляшку» — це теж спойлер, про це трохи далі.

Що ми зробили:

  1. Складаємо список з усіх систем, які нагадує google та StackOverflow. Трохи більше ніж 30
  2. Пишемо тести з навантаженням, характерним для продакшн. Для цього записали дані, які проходять через систему в продакшн-оточенні - такий собі сніффер для даних не в мережі, але всередині системи. У тести запускали ці дані
  3. Усією командою кожен вибирає наступну систему зі списку, налаштовує, проганяє тести. Не проходить тест, не тягне навантаження – викидаємо, переходимо до наступної черги.
  4. На 17 системі стало зрозуміло, що все безнадійно. Досить «трусити пляшку», настав час серйозно подумати.

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

Змоделюємо такий варіант (складно уявити, що міддл+ розробник живе у вакуумі, і на момент вибору ще не оформив перевагу, який продукт пробувати в першу чергу - тому подальші міркування це, швидше за теоретика/філософія/про джуніора).

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

Якщо ви тільки починаєте і гуглитимете, то плюс-мінус черговість, але в цілому, орієнтири будуть такі. В першу чергу ви натрапите на Redis, він скрізь на слуху. Потім дізнаєтеся, що є EhCache як найдавніша та перевірена система. Далі буде написано про Tarantool — вітчизняну розробку, яка має унікальний аспект рішення. І також Ignite, тому що він зараз на піднесенні популярності та користується підтримкою СберТеха. В кінці ще Hazelcast, тому що в enterprise-світі він часто миготить серед великих компаній.

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

Redis

Читаємо, що пишуть на офіційному сайті.
Redis - Opensource-проект. Пропонує in-memory сховище даних, можливість збереження on-disk, авторозбивку на партиції, високу доступність та відновлення після розривів мереж.

Начебто все добре, можна брати і прикручувати - все що потрібно, він робить. Але подивимося просто заради інтересу до інших кандидатів.

EhCache

EhCache - «Кеш для Java, що найбільш широко використовується» (переклад гасла з офіційного сайту). Теж Opensource. І тут розуміємо, що Redis не під java, а загальний, і для взаємодії з ним потрібна обгортка. А EhCache буде зручніше. Що ще обіцяє система? Надійність, перевіреність, повнофункціональність. Ну і ще вона найпоширеніша. І кешує терабайти даних.

Redis забутий, я готовий вибрати EhCache.

Але почуття патріотизму мене штовхає подивитися, чим гарний Tarantool.

Tarantool

Tarantool — зустрічає позначення «Платформа інтеграції даних у режимі реального часу». Дуже складно звучить, тому читаємо сторінку докладно та знаходимо гучну заяву: «Кешує 100% даних в оперативній пам'яті». Це має викликати питання — адже даних може бути значно більше, ніж пам'яті. Розшифровка в тому, що мається на увазі, що для запису даних на диск з пам'яті Tarantool не проганяє серіалізацію. Натомість – використовує низькорівневі особливості системи, коли пам'ять просто мепиться на файлову систему з дуже хорошими показниками I/O. Загалом, зробили якось чудово та класно.

Подивимося на впровадження: Mail.ru корпоративна магістраль, Авіто, Білайн, Мегафон, Альфа-Банк, Газпром...

Якщо залишалися ще якісь сумніви з приводу Tarantool, то кейс впровадження Mastercard мене добиває. Беру Tarantool.

Але все таки…

Запалати

… є ще Запалати, заявлений як "in-memory обчислювальна платформа… in-memory швидкості на петабайтах даних". Тут теж багато плюсів: розподілений in-memory кеш, найшвидше key-value сховище та кеш, горизонтальне масштабування, висока доступність, сувора цілісність. Загалом, виявляється, найшвидший це Ignite.

Впровадження: Ощадбанк, American Airlines, Yahoo! Japan. А потім я ще дізнаюся, що Ignite не просто в Ощадбанку впроваджено, а команда ОщадТеха своїх людей відправляє в команду самого Ignite, доопрацьовувати продукт. Це повністю купує та я готовий взяти Ignite.

Цілком незрозуміло навіщо, я дивлюся на п'ятий пункт.

Ліщина

Заходжу на сайт Ліщиначитаю. І виявляється, найшвидше рішення для розподіленого кешування - це Hazelcast. Він на порядок швидше за всіх інших рішень і взагалі він - лідер в області in-memory data grid. На тлі цього взяти щось інше — не поважати себе. А ще використовує надмірне зберігання даних безперервної роботи кластера без втрат даних.

Все, я готовий взяти Hazelcast.

Порівняння

Але якщо подивитися, всі п'ять кандидатів так розписані, що кожен з них – найкращий. Як вибрати? Можемо подивитися, який із них найпопулярніший, пошукати порівняння, і головний біль минеться.

Знаходимо такий огляд, вибираємо наші 5 систем.

Як ми у Спортмайстрі обирали систему кешування. Частина 1

Тут вони відсортовані: нагорі Redis, на другому місці Hazelcast, набирають популярність Tarantool і Ignite, EhCache як був, так і залишається.

Але подивимося на метод розрахунку: посилання на вебсайти, загальний інтерес до системи, job offers — чудово! Тобто коли в мене впаде система, я скажу: «Ні, вона ж надійна! Ось багато пропозицій щодо роботи…». Таке просте порівняння не підійде.

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

Добре, не здаємось, знайдемо пряме порівняння систем. Візьмемо два верхні варіанти - Redis і Hazelcast. Нас цікавить швидкість, за цим параметром їх і можна порівняти.

Hz vs Redis

Знаходимо таке порівняння:
Як ми у Спортмайстрі обирали систему кешування. Частина 1

Синій – це Redis, червоний Hazelcast. Hazelcast скрізь виграє, і цьому дано обґрунтування: він є багатопотоковий, високо оптимізований, кожен потік працює зі своєю партицією, тому немає блокувань. А Redis — однопотоковий, від сучасних багатоядерних CPU він виграш не бере. Hazelcast має асинхронне I/O, Redis-Jedis — блокуючі socket'и. Зрештою Hazelcast використовує бінарний протокол, а Redis орієнтований на текст, тобто він неефективний.

Про всяк випадок звернемося до ще одного джерела порівняння. Що він нам покаже?

Redis vs Hz

Ще одне порівняння:
Як ми у Спортмайстрі обирали систему кешування. Частина 1

Тут навпаки, червоний – це Redis. Тобто Redis за продуктивністю виграє у Hazelcast. У першому порівнянні вигравав Hazelcast, у другому - Redis. Тут же дуже точно пояснили, чому у попередньому порівнянні виграв Hazelcast.

Виявляється, результат першого фактично підтасували: Redis взяли в базовій коробці, а Hazelcast заточили під тестовий випадок. Тоді виходить: по-перше, нікому не можна вірити, по-друге, коли ми таки виберемо систему, нам ще потрібно її правильно налаштувати. Ці настройки включають десятки, майже сотні параметрів.

Трясемо пляшку

І весь процес, який ми зараз проробили, я можу пояснити такою метафорою «Трясемо пляшку». Тобто зараз можна не програмувати, зараз головне — вміти читати stackoverflow. І в мене в команді є людина, професіонал, яка саме так і працює у критичні моменти.

Що він робить? Він бачить непрацюючу штуковину, бачить stack trace, бере якісь з нього слова (які саме - це його експертиза в програмі), шукає в кути, знаходить stackoverflow серед відповідей. Не читаючи, не вдумуючись, серед відповідей на запитання — він вибирає щось найбільш схоже на пропозицію «зробити те й те» (вибрати таку відповідь — це її талант, тому не завжди це саме та відповідь, яка зібрала більше лайків), застосовує , Дивиться: якщо щось змінилося, то добре. Якщо не змінилося – відкочуємо. І повторюємо запуск-перевірку-пошук. І таким інтуїтивним способом він досягає того, що через якийсь час код працює. Він не знає, чому, він не знає, що він зробив, він не може пояснити. Але! Ця зараза працює. І «пожежа згашена». Ось тепер розуміємо, що ми зробили. Коли програма працює — це значно легше. І значно заощаджує час.

Ось цей спосіб дуже добре пояснюється такому прикладі.

Колись було дуже популярно збирати вітрильник у пляшці. При цьому вітрильник великий і тендітний, а шийка у пляшки дуже вузенька, його всередину пропхати не можна. Як його зібрати?

Як ми у Спортмайстрі обирали систему кешування. Частина 1

Є такий метод дуже швидкий і дуже ефективний.

Корабель складається з купи дрібниць: паличок, мотузок, вітрил, клею. Все це кладемо у пляшку.
Беремо пляшку двома руками, і починаємо трясти. Ми її трясемо-трясемо. І зазвичай – виходить повна фігня, звісно. Але інколи. Іноді виходить корабель! Точніше щось схоже на корабель.

Ми це щось показуємо комусь: «Сергю, бачиш!?». Здалеку – корабель. Але далі це пускати не можна.

Є ще спосіб. Використовують хлопці просунутіші, такі хакери.

Дав такому хлопцеві завдання, він усе зробив і пішов. І дивишся – начебто зроблено. А через якийсь час, коли треба доопрацювати код — там таке починається через нього… Добре, що він уже встиг далеко відбігти. Це такі хлопці, які на прикладі пляшки зроблять так: бачите, там, де денце – скло згинається. І не зовсім зрозуміло, прозоро воно чи ні. Тоді «хакери» спилюють це денце, вставляють туди корабель, денце потім приклеюють наново, і начебто так і треба.

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

Єдине місце, ключове, з яким справді нічого не можна вдіяти, це корпус. І корпус корабля якраз у шийку проходить. Тоді як корабель збирається поза пляшки. Але це не просто зібрати корабель, це справжнє ювелірне ремесло. У складові додаються спеціальні важелі, які дозволяють потім їх підняти. Наприклад, складаються вітрила, акуратно заносяться всередину, і потім за допомогою пінцету дуже ювелірно, точно їх підтягують і піднімають. В результаті виходить витвір мистецтва, який можна з чистою совістю та гордістю подарувати.

І якщо ми хочемо, щоб проект був успішним – у команді має бути хоча б одна людина-ювелір. Той, хто дбає про якість продукту та враховує всі аспекти, не жертвуючи жодним навіть у моменти стресу, коли обставини вимагають зробити термінове на шкоду важливому. Всі успішні проекти, які є стійкими, які витримали перевірку часом, вони побудовані на цьому принципі. Вони мають щось дуже точне і унікальне, щось, що використовує всі доступні можливості. У прикладі з кораблем у пляшці – обігрується те, що корпус корабля проходить через шийку.

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

Де шукати bottle-neck

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

Як ми у Спортмайстрі обирали систему кешування. Частина 1

Якщо система розподілена, то в нас буде кілька серверів (6). Допустимо, чотири (зручно розмістити на картинці, але, звичайно, їх може бути скільки завгодно). Якщо сервери на різних вузлах, значить, на них усіх крутиться певний код, який відповідає за те, щоб ці вузли утворили кластер і у разі розриву – з'єднувалися, впізнавали один одного.

Ще потрібен код-логіка (2), яка власне для кешування. З цим кодом за деяким API взаємодіють клієнти. Клієнтський код (1) може бути як у рамках цієї JVM, так і звертатися до нього по мережі. Логіка, реалізована всередині – це рішення, які об'єкти в кеші залишати, які викидати. Для зберігання кеша використовуємо пам'ять (3), але, якщо потрібно, частину даних можемо і на диску зберегти (4).

Подивимося, у яких частинах виникатиме навантаження. Власне, навантажуватимуться кожна стрілочка і кожен вузол. По-перше, між клієнтським кодом та api, якщо це мережна взаємодія, просідання може бути досить помітним. По-друге, в рамках самого api — перестаравшись зі складною логікою, можемо впертись у CPU. І добре, щоб логіка не ганяла пам'ять зайвий раз. І залишається взаємодія з файловою системою – у звичайному варіанті це серіалізувати/відновити та записати/рахувати.

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

Тепер, з одного боку - ми можемо уявити, "які шестерні будуть крутитися" в кеш-системі при обробці запитів від нашого коду, і з іншого боку - ми можемо прикинути, які і скільки запитів наш код до цієї системи згенерує. Цього достатньо, щоб зробити більш-менш тверезий вибір – підібрати систему під наш варіант використання.

Ліщина

Подивимося, як таке розкладання застосувати до нашого списку. Наприклад, Hazelcast.

Для того щоб покласти/взяти дані з Hazelcast, код клієнта звертається (1) до api. Hz дозволяє запустити сервер як embedded, і в цьому випадку звернення до api - це виклик методу всередині JVM можна вважати безкоштовно.

Щоб відпрацювала логіка (2), Hz спирається на хеш від байт-масиву серіалізованого ключа - тобто, серіалізація ключа відбудеться в будь-якому випадку. Це є неминучий overhead для Hz.
Eviction-стратегії реалізовані добре, але для особливих випадків можна підключати свої. За цю частину можна не перейматися.

Сховище (4) можна підключати. Чудово. Взаємодія (5) для embedded можна вважати миттєвим. Обмін даними між вузлами у кластері (6) – так, він є. Це внесок на користь відмовостійкості ціною швидкості. Знизити ціну дозволяє Hz-фіча Near-cache – дані, отримані з інших нод кластера, будуть закешовані.

Що можна за таких умов зробити для підвищення швидкості?

Наприклад, щоб уникнути серіалізації ключа в (2) – поверх Hazelcast прикрутити ще один кеш, для гарячих даних. У Спортмайстер для цього вибрали Caffeine.

Для підкрутки на рівні (6), у Hz запропоновано два типи зберігання: IMap та ReplicatedMap.
Як ми у Спортмайстрі обирали систему кешування. Частина 1

Варто сказати, як Hazelcast потрапив у стек технологій Спортмайстер.

У 2012 році, коли ми працювали над першим пілотом майбутнього сайту, саме Hazelcast виявився першим посиланням, яке видав пошуковик. Знайомство зав'язалося «з першого разу» — нас підкупило те, що через дві години, коли ми прикрутили Hz в систему — він працював. І працював добре. До кінця дня дописали кілька тестів, пораділи. І цього запасу бадьорості вистачило, щоб подолати сюрпризи, які Hz підкидав з часом. Зараз команда Спортмайстер не має підстав для того, щоб від Hazelcast відмовлятися.

Але такі аргументи, як «перше посилання у пошуковику» та «швидко зібрали HelloWorld» — це, звичайно, виняток та особливість моменту, в якому відбувався вибір. Справжні випробування для обраної системи починаються з виходом у прод, і саме на цей етап варто звернути увагу при виборі будь-якої системи, у тому числі кеша. Власне, у нашому випадку можна сказати, що вибрали Hazelcast випадково, але потім виявилось, що вибрали правильно.

Для продакшн набагато важливіше: моніторинг, обробка збоїв на окремих вузлах, реплікація даних, вартість масштабування. Тобто варто звернути увагу на завдання, які виникнуть якраз у супроводі системи – коли навантаження у десятки разів перевищить заплановане, коли випадково заллємо щось не те й не туди, коли потрібно викотити нову версію коду, замінити дані та зробити це непомітно для клієнтів.

Для всіх цих вимог, Hazelcast, безумовно підходить.

Буде продовжено

Але Hazelcast – це не панацея. У 2017 році ми вибрали Hazelcast для кеша в адмінці, просто спираючись на добре враження від минулого досвіду. Це відіграло ключову роль у дуже злій жарті, через що ми опинилися в складній ситуації і «героїчно» вибиралися з неї 60 днів. Але про це у наступній частині.

А поки що… Happy New Code!

Джерело: habr.com

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