Wéi stoppen ze Suergen an ufänken ouni Monolith ze liewen

Wéi stoppen ze Suergen an ufänken ouni Monolith ze liewen

Все мы любим истории. Нам нравится, сидя у костра, рассказывать о своих былых победах, сражениях или просто о своем опыте работы.

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

Когда-то давным-давно в нашей компании была пара «монолитов» и один на всех «потолок», к которому эти монолиты медленно, но верно приближались, ограничивая полет нашей компании, наше развитие. И было однозначное понимание: однажды мы жестко упремся в этот потолок.

Это сейчас у нас господствует идеология разделения всего и вся, начиная от оборудования и заканчивая бизнес-логикой. Вследствие этого у нас, к примеру, есть два ДЦ, практически независимых на сетевом уровне. А тогда всё было совсем по-другому.

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

Но время шло вперед, и количество запросов шло вперед вместе с ним, выстреливая RPS порой выше наших возможностей. С выходом на рынок стран СНГ нагрузка на процессоре БД первого монолита не опускалась ниже 90 %, а RPS держались на уровне 2400. И это были не просто маленькие селектики, а здоровенные запросы с кучей проверок и JOIN’ов, которые могли пробежаться чуть ли не по половине данных на фоне большого IO.

Когда же на сцене начали появляться полноценные распродажи на «Черную пятницу», — а Wildberries начал проводить их одним из первых в России, — то ситуация стала совсем печальной. Ведь нагрузка в такие дни возрастает в три раза.
Ох уж, эти «монолитные времена»! Уверен, что и вы сталкивались с подобным, и до сих пор не можете понять, как такое могло произойти с вами.

Что тут поделаешь — мода присуща и технологиям. Еще лет 5 тому назад нам пришлось переосмыслить одну из таких мод в виде имеющегося сайта на .NET и MS SQL-сервера, который бережно хранил в себе всю логику работы самого сайта. Хранил настолько бережно, что распиливать такой монолит оказалось долгим и совсем непростым удовольствием.
Eng kleng Digressioun.

На различного рода мероприятиях я говорю: «если вы не распиливали монолит, значит вы не росли!» Интересно ваше мнение по этому поводу, напишите его, пожалуйста, в комментариях.

И грянул гром

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

Проанализировав первых претендентов на вылет из монолита в микросервисы, мы поняли, что в 80 % запись в них на 99 % идет из back office-систем, а чтение — с передовой. В первую очередь это касалось пары важных для нас подсистем — пользовательских данных и системы вычисления конечной стоимости товаров на основании информации о дополнительных клиентских скидок и купонов.

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

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

Вследствие этого у нас родилась схема, хорошо сочетающаяся с Tarantool.

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

Wéi stoppen ze Suergen an ufänken ouni Monolith ze liewen
Архитектура. Вариант 1. Сервис пользователей

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

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

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

Wéi stoppen ze Suergen an ufänken ouni Monolith ze liewen
Архитектура. Вариант 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 ГБ. Это самый объемный сервис из вышеперечисленных.

Konklusioun

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

Wat soss iwwer dëst Thema ze liesen

Source: will.com

Setzt e Commentaire