Привет, Хабр! Сегодня хочу поделиться с вами текстовой версией доклада, представленного на Tarantool Meetup второго марта 2017-го года в Mail.Ru Group с поправкой на то, что прошёл уже месяц, и кое-что из обещанного уже было реализовано, поэтому текст будет интересен даже тем, кто видел выступление. Я работаю в компании eVote, которая разрабатывает сервис онлайн-опросов. Мы активно применяем Windows и .NET-технологии в наших продуктах, и в этом посте я расскажу про то, как мы добавили к стеку наших технологий СУБД Tarantool.
Большое спасибо коллегам, друзьям и сотрудникам компании Mail.Ru Group, которые помогали написать эту статью.
Выбор СУБД
В жизни каждого проекта рано или поздно возникает переломный момент, когда нужно выбрать СУБД для хранения всех данных. Наш проект с этой точки зрения простой: пользователи, голосования, ответы, какая-то попутно собираемая информация — всё это прекрасно можно держать в key-value хранилище. Поэтому на старте мы рассматривали три варианта: Redis, Tarantool и MySQL с handlersocket. Фаворитом с самого начала был Redis. Он быстро работает, у него замечательный коннектор для .NET, созданный командой Stack Overflow. К слову, сам Stack Overflow написан на .NET, работает на Windows, у них SQL Server от Microsoft, Redis и ещё много интересного. У Redis прекрасная документация. Если мы нанимаем нового программиста, который никогда не работал с Redis, то мы отправляем его туда — и через три дня он знает примерно всё, что ему нужно знать для использования Redis.
Под вторым номером шёл Tarantool. К сожалению, у него не было такого удобного сайта, как у Redis. Как и коннектора для .NET. По скорости он нас полностью устраивал, так как не сильно отличался от Redis. Если вы включаете write ahead logging, то любая запись окажется на диске. И если у вас не глючит контроллер, диск или прочее железо, то получается вполне надёжно. Также в Tarantool есть вторичные индексы. В некоторых случаях это очень важная фича. В Redis её нет, приходится делать вручную.
Handlersocket оказался аутсайдером. Он медленнее, чем Redis и Tarantool. Зато доступна ACID-модель, если ваш движок в MySQL её поддерживает. Доступна вся инфраструктура от MySQL: репликация, мониторинг, эксплейн, бэкапы. Можно строить сложные отчёты на обычном SQL. Коннектора под .NET тоже не было.
В результате мы выбрали Redis и запустили его в свою закрытую альфу. Но через какое-то время выяснилось, что отсутствие вторичных индексов — более серьёзная проблема, чем мы думали. Если нужно выбрать все голосования, созданные каким-либо автором, то приходится заводить дополнительные списки для хранения (можно, конечно, делать полный перебор, но это не наш случай). Есть риск, что в результате разных сбоев данные станут неконсистентными. Попытки исправить это через Lua-скрипты или транзакции привели к тому, что работа Redis замедлилась примерно в три-четыре раза, что перестало нас устраивать. Это не проблема самого Redis, а следствие его нецелевого использования, на мой взгляд.
Tarantool и Windows
Тогда мы решили перейти на Tarantool. И перед нами встали сразу две проблемы. У Tarantool до сих пор нет бинарника под Windows (и неизвестно, когда появится) — раз. Не было коннектора для .NET — два. Вообще говоря, это спорный вопрос, можно ли назвать недостатком отсутствие версии нашего хранилища под Windows. Я считаю это преимуществом. Ведь в production будет, скорее всего, Linux, а отсутствие версии под Windows заставит программиста разбираться в том, как всё работает на самом деле. Программист для отладки и мониторинга будет пользоваться теми же инструментами, что и в production. По моим наблюдениям, это повышает вероятность написания качественного кода без ошибок и уменьшает время простоя в случае катастроф.
Первую проблему мы решили использованием Docker for Windows для разработки. А вот вторая проблема была посложнее.
Коннектор
Поскольку готового коннектора для Tarantool не существовало, мы написали свой. Для этого пришлось решить две задачи. Первая: реализовать сериализацию и десериализацию в msgpack, так как это формат обмена данных в Tarantool. У нас она была решена в рамках проекта MsgPack.Light, так как мы тоже храним данные, упакованные в msgpack. Вторая задача сводилась к буквальной реализации протокола обмена данных с Tarantool iProto. Она решена в рамках проекта progaudi.tarantool.
Мы поддерживаем
- .NET 4.6 и выше,
- новый opensource-фреймворк .NET Core: netstandard 1.4 и выше.
Раньше коннектор назывался tarantool.csharp, но после обратной связи от комьюнити мы его переименовали в progaudi.tarantool. Теперь название не должно вызывать никакой путаницы — можно ли использовать коннектор из F# или нет. Можно было с самого начала.
Благодаря недавним улучшениям в MsgPack.Light, коннектор научился работать с тарантуловским типом данных scalar. В будущем планируется ещё больше упростить работу с коннектором и уйти от явной конвертации объектов пользователя в TarantoolTuple-структуры.
Мы не поддерживаем DDL, потому что он выходит за рамки iProto и должен быть реализован обёртками над EVAL-командой. Лично я считаю, что если программист использует Tarantool, то он должен писать схему в Lua, потому что в таком случае можно шарить её с админами и распространять сразу в работающее окружение. Может быть, я неправ, и мы реализуем возможность делать это из .NET.
На текущий момент у нас есть соединение только с одной нодой Tarantool. Мы не полностью поддерживаем CALL_16, в определённых случаях на стороне Tarantool происходит странная упаковка, и это приводит к ошибкам при десериализации. Новый CALL поддерживается полностью, рекомендуем пользоваться им.
Особенности разработки коннекторов к Tarantool
К сожалению, в Tarantool невозможно попросить сервер выводить все запросы в лог. В некоторых ситуациях это очень мешает при отладке, при этом нам обычно не важна производительность сервера Tarantool. Хотелось бы получить какую-то ручку, которая включает запись всех запросов в лог.
При логировании запросов в лог хочется их как-то отделять друг от друга. Для этого у нас есть connection id (box.session.id) и request id (box.session.sync). К сожалению, box.session.sync расшарен на все запросы в рамках одного соединения, как следствие, он может меняться, если выполнение запроса прерывается из-за достижения yield point (запись в базу, ручная передача управления и так далее). В принципе, это важно только там, где может встретиться несколько точек логирования, например, в хранимых процедурах. В таких случаях следует box.session.sync сохранять до первого yield point в локальную переменную.
Сервер приложений
Важная часть Tarantool — это сервер приложений. К нему существует множество уже готовых модулей, начиная с простых, таких как автоматическая перезагрузка других модулей, заканчивая шардированием, очередями и драйверами к другим СУБД (1, 2 и многое другое). Все модули, которые мы пробовали, прекрасно работают, неплохо документированы и поддерживаются.
Из всего этого разнообразия мы используем tarantool/queue для очередей и tarantool/prometheus для сбора метрик. Также у нас есть немного своей логики на Lua. Немного — потому что наша команда из мира .NET. Мы привыкли, что есть статический анализ кода, есть пошаговая отладка, удобные профайлеры. У Lua с этим проблемы, особенно со статическим анализом кода.
Репликация
Очень хотелось бы получить синхронную репликацию. Мы уже умеем жить без неё, но очень сильно ждём. Пока что мы пользуемся имеющейся master-master асинхронной репликацией. Один из наших сервисов работает с картинками. Там фигурируют случайные ключи, и мы можем писать в любую ноду, потому что вероятность совпадения ключей крайне мала. Нам нужно, чтобы совпали сгенерированный guid для картинок и SHA256. Так как к этому мы пришли не сразу, то коннектор до сих пор не умеет соединяться с несколькими нодами. В нынешнем году мы это обязательно исправим.
Сборка кластера в старых версиях
В очень старом билде 1.7.3-0 может не собраться кластер. Допустим, у вас три мастер-ноды. Указываете им одинаковые конфигурации, запускаете. И пока любая из нод не увидит свои источники, откуда она забирает данные, она не будет принимать запросы от других клиентов. К сожалению, эти источники для них — клиенты. В результате все три ноды не отвечают на запросы и ждут 30 секунд. В это время они ищут свои источники, не находят, пишут в лог: «Источников нет, я выключилась». И кластер не собирается. Приходится собирать вручную. Запускаем одну ноду без общей конфигурации. Она поднимается, подключаем к ней остальные ноды, а потом меняем конфигурацию на единую. В новой версии этот баг уже исправлен.
Версионирование образа для докера tarantool/tarantool
Допустим, версия образа 1.7.3. А какая версия Tarantool внутри? Мы знаем, что 1.7.3, но номер сборки неизвестен. Единственный вариант — посмотреть исходники. Допустим, нужна версия Tarantool 1.7.3-115, потому что в 114 ещё была проблема, которая нас больно бьет, а в 116 — мы не знаем, есть она или нет. Какую версию образа взять?
Мы решили задачу просто: сами собираем свой образ, указываем конкретный номер сборки — и всё прекрасно работает. Но в целом это небольшая проблема. Образ по умолчанию прекрасно работает и покроет бо?льшую часть запросов, туда включены все основные модули, которые нужны для работы с Tarantool: мониторинг, очереди и т. д. Можно брать и пользоваться.
Интерактивные запросы
Чтобы написать интерактивный запрос в Tarantool, нужно знать Lua и уметь пользоваться командной строкой. Наши тестеры и админы умеют обращаться с командной строкой, но они не горят желанием разбираться с Lua. Они хотят быстренько написать SQL-скрипт, проверить какой-нибудь счётчик или timestamp. Поэтому мы очень ждём анонсированную поддержку SQLite-диалекта.
Мониторинг и логирование
С ними всё прекрасно. Есть модуль tarantool/prometheus, который опубликован на сайте Prometheus. Так как мы используем в production docker, то логи мы с него собираем с помощью Fluentd (1 и 2) и складываем их в ElasticSearch.
Выводы
.NET — это далеко не только Windows: .NET Core работает на всех платформах. У нас в production есть .NET-приложение, которое прекрасно функционирует на Linux. Наши opensource-проекты без проблем собираются и работают на Windows, Mac OS и Linux (тестируются только Windows 10, Max OS X, Ubuntu 16.04). При этом вполне возможно для разработки и отладки использовать весь богатый инструментарий, доступный в мире .NET и зачастую бесплатный даже для коммерческой разработки.
Благодаря кроссплатформенности новых .NET-решений, появляется возможность использовать недоступные ранее инструменты, например, Tarantool. Если вам важна скорость работы, вторичные индексы, возможность использования СУБД в качестве сервера приложений или очереди задач, то Tarantool — очень хороший выбор на сегодняшний день.
Комментарии (19)
WondeRu
12.04.2017 09:36+2Насколько бы просело по скорости ваше решение, если бы взяли обычную sql db (mssql или postgresql)? Почему именно key-value?
aensidhe
12.04.2017 10:58+4Когда мы два года назад тестировали MS Sql Server, то я смог выжать примерно такой же перфоманс, но с отключенным хранилищем на движке Hekaton. С классическим табличным движком мы проседали в 5 раз примерно. PostgreSql мы не тестировали, потому что не умеем его настраивать.
Trixon
12.04.2017 11:01-2Из текста следует, что у автора, как минимум, реляционная модель данных, а проблема его сводится к тому как почесать левой рукой правое ухо.
aensidhe
12.04.2017 11:03При всём уважении, мне неизвестны проекты, состоящие из полностью независимых сущностей без связей между ними. Можно примеры?
AlexTheLost
12.04.2017 10:49+1Вывод:
Handlersocket оказался аутсайдером. Он медленнее, чем Redis и Tarantool.
Пояснение:
Зато доступна ACID-модель, если ваш движок в MySQL её поддерживает. Доступна вся инфраструктура от MySQL: репликация, мониторинг, эксплейн, бэкапы. Можно строить сложные отчёты на обычном SQL.
Неплохо так сравнили kev-value хранилище и плагин к MySQL, т.е. то что он медленнее это сильный минус на фоне все его возможностей?)
aensidhe
12.04.2017 10:59+1Вся суть данного плагина в том, что скорость доступа к данным выше, чем через стандартный движок запросов MySQL, поэтому да, скорость на наших нагрузках была определяющим фактором.
К тому же, у Tarantool есть практически всё тоже самое, что и у MySql уже.
kefirr
Рассматривался ли Apache Ignite? Функционал весь на месте и даже больше, полная поддержка .NET (правда, пока без .NET Core).
aensidhe
Нет, не рассматривался, так как на момент выбора хранилища, не существовало его стабильных версий.
Кстати, если верить их репозиторию, .net core support там уже есть.
kefirr
Не, это у них просто главный модуль так называется, ядро.
aensidhe
Спасибо. К сожалению, для простого upvote не хватает кармы.
danikin
А у Игнайта уже есть синхронная запись на диск всех изменений перед ответом пользователю на транзакцию?
kefirr
Всегда была в виде write-through cache store: https://apacheignite.readme.io/docs/persistent-store
danikin
Ну это же не запись в свой родной лог транзакций, это запись в другую СУБД. Что тут же делает работу игнайта на запись не быстрее чем эта СУБД. Плюс, в случае разрыва сети возможны повторные записи (в СУБД запись прошла, сеть порвалась, игнайт про это не узнал — сделал ритрай, получили повторную запись
kefirr
Почему именно СУБД, как CacheStore реализуешь, так и будет писаться. Синхронная запись подразумевает "не быстрее чем то, куда пишем", разве нет?
Это валидный пункт. Вроде как в будущем появится. Изначально Игнайт — in-memory система.
danikin
Ну вот видимо в этом и кроется ответ. Одно дело — СУБД с родным логом и пирогами. Другое тело — набор заготовок и напильник к ним.
kefirr
Зачем же так сразу?
Какие уж тут пироги, если даже SQL нету
danikin
Ну тут дело вкуса уже. Кому то ОК, чтобы был SQL, но без нормального персистенса, а кому то наоборот.