Как перестать беспокоиться и начать жить без монолита

h7s5zeywf-mf9wch6fws7um_t5s.jpeg

Все мы любим истории. Нам нравится, сидя у костра, рассказывать о своих былых победах, сражениях или просто о своем опыте работы.
Сегодня как раз такой день. И пусть вы сейчас не у костра, но зато у нас есть история для вас. История о том, как мы начали работать с хранилищем на 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.

vtcchh55ar-9ttwmjljc5crukoq.jpeg
Архитектура. Вариант 1. Сервис пользователей

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

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

При этом имеется возможность конфигурировать политику выбора реплики в разрезе шарды. К примеру, раундробином.

ikkmo2ws4qcc6totks02cwekmdu.jpeg
Архитектура. Вариант 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: знаем, где стоит использовать один язык, а где — другой.

Что еще почитать по теме

© Habrahabr.ru