Мы продолжаем экспериментировать с форматами проведения митапов. Недавно на боксерском ринге мы сталкивали централизованную шину данных и Service Mesh. В этот раз решили попробовать нечто более миролюбивое — StandUp, то бишь открытый микрофон. Темой выбрали in-memory базы данных.



В каких случаях стоит переходить на in-memory? Как и зачем масштабировать? И на что стоит обратить внимание? Ответы в выступлениях спикеров, которые мы осветим в этом посте.

Но для начала представим спикеров:

  • Андрей Трушкин, руководитель центра инноваций и перспективных технологий Промсвязьбанка
  • Владислав Шпилевой, разработчик Tarantool
  • Артем Шитов, архитектор решений GridGain

Переход на in-memory


Современные тенденции финансового рынка предъявляют гораздо более строгие требования по времени отклика и работе средств автоматизации процессов в целом. К тому же, практически все крупнейшие финансовые институты стремятся сегодня к построению собственных экосистем.

В связи с этим мы видим для себя два основных применения in-memory решений. Во-первых, это кэширование интеграционных данных. По классическому сценарию в крупных компаниях существует нескольких автоматизированных систем, которые обеспечивают предоставление данных по запросу пользователя. Либо внешней системы — но в этом случае инициатором в большинстве случаев является пользователь. Традиционно эти системы хранили структурированные определенным образом данные в БД, осуществляя доступ к ней по запросу.

Сегодня такие системы уже не удовлетворяют требованиям в части нагрузки. Здесь не стоит забывать и про удаленные вызовы указанных систем системами-потребителями. Отсюда вытекает необходимость пересмотра подходов к хранению и представлению данных — пользователям, автоматизированным системам или отдельным сервисам. Логичный выход — хранение актуальных данных, используемых сервисами, на уровне слоя in-memory; на рынке есть немало подобных успешных кейсов.

Это был первый кейс. Второй — это эффективное, с технической точки зрения, управление бизнес-процессами. Традиционные BPM-системы автоматизируют выполнение тех или иных операций в соответствии с заранее определенным алгоритмом. И во множестве случаев возникают вопросы: почему же эти системы работают недостаточно эффективно и недостаточно быстро?

Как правило, подобные системы пишут каждый свой шаг (или небольшой набор шагов, оформленный в виде бизнес-транзакции) в базу данных. Так что они завязаны на время отклика и взаимодействия с данными системами. Сейчас количество экземпляров бизнес-процессов, выполняющихся одновременно в режиме реального времени, на порядки больше чем 10 лет назад. Так что современные системы управления бизнес-процессами должны иметь существенно более высокую производительность и обеспечивать исполнение децентрализованных приложений. Тем более что сегодня все компании движутся к формированию большого микросервисного окружения. Задача в том, чтобы различные экземпляры бизнес-процессов могли разделять и эффективно использовать оперативные данные. В рамках оркестровки имеет смысл хранить их в in-memory решении.

Проблема согласования


Предположим, что у нас огромное количество узлов и сервисов, что выполняется ряд бизнес-процессов, действия которых реализованы в виде микросервисов. Чтобы повысить производительность, каждый из них начинает писать свое состояние в локальный экземпляр памяти. Мы получаем большое количество локальных экземпляров. Как обеспечить актуальность и согласованность для всех?

Мы используем зонирование in-memory области. Например, в зависимости от бизнес-домена. Когда мы нарезаем бизнес-домен, то определяем, чтобы те или иные микросервисы/бизнес-процессы работали только в рамках той зоны, которая отвечает за соответствующий домен. Так мы можем ускорить обновление кэша и всю работу in-memory решения.

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

Часто возникают естественные вопросы о надежности in-memory решений. Да, туда можно класть не все. У нас с целью обеспечения надежности рядом с in-memory всегда остаются базы данных. Например, для важных вопросов с отчетностью, которую нужно сводить вместе, что бывает сложно на большом количестве узлов. Так что наше видение на сегодняшний день: синергия двух подходов.

Также стоит отметить, что эти два подхода тоже не совсем правильно только противопоставлять. И в то же время замыкаться на них. Производители и контрибьюторы современных систем виртуализации в режиме контейнеров, такие как Kubernetes, уже обеспечивают нам варианты для долговременного надежного хранения. Уже появились хорошие промышленные кейсы по внедрению решений, в которых хранение осуществляется в подобном виртуализированном формате.

Одна из крупнейших газет США предоставляет возможность своим читателям в режиме онлайн получить любой номер, который выходил с момента старта выпуска этой газеты в 19 веке. Можем представить уровень нагрузки. Хранение реализовано ими посредством платформы Apache Kafka, развернутой в Kubernetes. Вот еще один вариант хранения информации и предоставления к ней доступа под большой нагрузкой большому количеству клиентов. При проектировании новых решений на этот вариант также стоит обратить внимание.

Масштабирование in-memory баз данных с Tarantool


Предположим, у нас есть сервер. Он принимает запросы, хранит данные. Вдруг запросов и данных становится больше, сервер перестает справляться с нагрузкой. Можно загрузить в сервер больше железа, и он будет принимать больше запросов. Но это тупиковый путь сразу по трем причинам: дороговизна, ограниченность в технических возможностях и проблемы с отказоустойчивостью.  Вместо этого существует горизонтальное масштабирование: к серверу приходят «друзья», которые помогают ему выполнять задачи. Два основных типа горизонтального масштабирования — это репликация и шардинг.

Репликация — это когда есть много серверов, они все хранят одни и те же данные и клиентские запросы раскидываются по всем этим серверам. Так масштабируются вычисления, не данные. Это работает, когда данные помещаются на одном узле, но клиентских запросов так много, что один сервер не справляется. Также тут сильно повышается отказоустойчивость.

Для масштабирования данных используется шардинг: делается много серверов, и они хранят разные данные. Так вы масштабируете и вычисления и данные. Но отказоустойчивость в этом случае невысока. Если один сервер откажет, то часть данных потеряется.

Есть третий подход — их скомбинировать. Мы разделяем кластер на подкластеры, называем их реплика-сетами. В каждом из них хранятся одни и те же данные, и между реплика-сетами данные не пересекаются. В итоге получается и масштабирование данных, и вычислений, и отказоустойчивость.



Репликация


Репликация бывает двух типов: асинхронная и синхронная. Асинхронная — это когда клиентские запросы не дожидаются, пока данные разлетятся по репликам: записи на одну реплику достаточно. Как только данные попали на диск, в журнал, транзакция завершается успехом и когда-нибудь в фоне эти данные реплицируются. Синхронная — когда транзакция делится на 2 фазы: prepare и commit. Commit не вернет success, пока данные не отреплицируются на некоторый кворум реплик.

Асинхронная репликация, очевидно, быстрее, потому что ничего не упирается в сеть. Данные в сеть будут посылаться в фоне, а сама транзакция как записалась в журнал, так и завершилась. Но есть проблема: реплики могут друг от друга отставать, появляться рассинхронизации.
Синхронная репликация надежнее, но сильно медленнее и сложнее в реализации. Там действуют сложные протоколы. В Tarantool можно выбирать любой из этих типов репликаций, в зависимости от задач.



Отставание реплик порождает не только рассинхронизацию, но и проблему неосведомленности мастера: он не знает как отдать свои изменения реплике. Изменения обычно отдаются инкрементально — их применили, и в том же виде они улетают на реплику. Но что с ними делать, если реплика недоступна? Например, в Tarantool все можно настраивать, и мастер становится очень гибок.

Еще одна задача: как сделать топологию сложной? В Mail.ru, например, есть топология с сотнями Tarantool. В ней есть tarantool-ядро к которому по кругу прицеплены тарантулы-реплики для бекапов. В Tarantool можно делать совершенно произвольные топологии, репликация с этим отлично живет.

Шардинг


Теперь перейдем к масштабированию данных: шардингу. Он бывает двух типов: диапазонами и хешами. Шардинг диапазонами — это когда все данные сортируются по некоторому ключу шардирования, и эта большая последовательность разбивается на диапазоны так, чтобы в каждом диапазоне было примерно одинаковое количество данных. И каждый диапазон целиком хранится на каком-нибудь одном физическом узле. Но обычно такой шардинг не нужен. К тому же, он всегда очень сложен.

Также есть шардирование хешами. Оно как раз и представлено в Tarantool. Оно гораздо проще в реализации, использовании и почти всегда подходит вместо шардирования диапазонами. Работает так: мы считаем хеш-функцию от записи и она возвращает номер физического узла, в который сохранять. Есть проблемы: во-первых, трудно быстро выполнить сложный запрос.



Во-вторых, существует проблема решардинга. Существует какая-то шард-функция, которая возвращает номер того физического шарда, в который надо сохранить ключ. И когда меняется количество узлов, шард-функция тоже меняется. Это означает, что для всех данных, которые есть в кластере, ее придется заново пересчитать и проверить. Более того, в классическом шардинге некоторые данные переедут не на новую ноду, а просто будут тасоваться между старыми нодами. Бесполезные переносы свести к нулю в классическом шардинге нельзя.



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

Как это решает проблему решардинга? Дело в том, что количество бакетов фиксировано и серьезно превышает количество физических узлов. Таким образом, сколько бы вы ни масштабировали физически свой кластер, бакетов всегда будет достаточно, чтобы хранить данные и равномерно их распределять. А за счет того, что шард-функция неизменна, при изменении состава кластера не придется ее пересчитывать.

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

Как ее решить? Первый способ: просто запретить решардинг. Тогда для решардинга придется создавать новый кластер и все туда переносить. Второй способ: всегда заходить на все узлы. Но в этом нет никакого смысла, поскольку нужно масштабироваться, а вычисления так не масштабируются. Третий вариант: прокси-модуль, который служит своего рода маршрутизатором по бакетам. Вы запускаете его, отправляете туда запрос, указав номер бакета, и он отправит ваш запрос как прокси на нужный физический узел.

Продвинутая In-Memory


Бизнес предъявляет дополнительные требования к базам данных. Он хочет, чтобы это все было отказо- и катастрофоустойчиво. Он хочет высокой доступности: чтобы ничего никогда не терялось, чтобы можно было быстро восстановиться. Также нужна легкая и дешевая масштабируемость, несложная поддержка, доверие платформе и эффективные механизмы доступа.

Все эти идеи не новы. Многие из этих вещей в той или иной степени реализованы в классических СУБД, в частности, репликации между ЦОДами.

In-Memory — это уже не технология-«стартап», это зрелые продукты, которые применяются в крупнейших компаниях по всему миру (Barclays, Citi Group, Microsoft и т.д.). Предполагается, что там все эти требования выполняются.

Так если вдруг случилась катастрофа, должна существовать возможность восстановиться из бэкапа. И если речь идет о финансовой организации, важно чтобы этот бэкап был консистентным, а не просто копией со всех дисков. Чтобы не было ситуации, когда на части узлов данные были восстановлены на момент X, а на другой части — на момент Y. Очень важно иметь Point-in-time Recovery, чтобы даже в ситуации порчи данных или особенно жесткой аварии минимизировать объем потерь.

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


Реакция In-Memory на аварии с включенными компонентами отказоустойчивости GridGain и без них

Отказоустойчивый кластер должен легко масштабироваться горизонтально и вертикально. Не хочется платить за свой сервер и смотреть на то, как половина ресурсов простаивает. Не хочется, чтобы был ад из сотен процессов, которыми нужно управлять. Хочется простую с точки зрения поддержки систему, с легким ввод-выводом узлов из кластера и развитой, зрелой системой мониторинга.

Рассмотрим в этом ракурсе MongoDB. Все, кто работал с MongoDB, знают о большом количество процессов. Если у нас есть шардированная MongoDB из 5-и шардов, то на каждом шарде будет реплика-сет из трех процессов (при коэффициенте избыточности 3). А это уже 15 процессов только на сами данные. Хранение конфигурации кластера — еще плюс 3 процесса, в сумме получает 18, и это без учета роутеров. Хотите 20 шардов — добро пожаловать в ад из 63+ (например, еще 8, итого 71) процессов.  



Сравним с Cassandra. Берем все те же 5 шардов — это 5 процессов и 5 узлов при том же коэффициенте избыточности 3, что намного проще в плане управления. Хочу 20 шардов — это 20 процессов. Я могу масштабировать мой кластер на любое количество узлов, не обязательно кратное 3 (или иному значению коэффициента избыточности). Намного легче и дешевле для внедрения и поддержки, чем реплика-сеты.



Кроме того, нужно доверять системе, понимать, что за люди стоят за каждым отдельным продуктом. В идеале лицензия должна быть open source или open core. Чтобы в случае смерти вендора можно было что-нибудь сделать. Также хорошо, если исходный код управляется независимым сообществом — мы все помним, как MongoDB и Redis сменили лицензии по одному желанию управляющей компании. Как Aerospike в начале года ввели ограничения на «open source» community-редакцию.

Нужен эффективный доступ к данным. Структурированный язык запросов в том или ином виде есть практически у всех. Чаще всего используют SQL, нужно чтобы адаптация с этим языком была максимально легкой. В этом помогут распределенное выполнение запросов, когда не нужно отправлять запрос отдельно на каждый узел, а можно общаться с кластером как с «единым окном». Не задумываясь с точки зрения API, что это набор узлов (вспомните, как тяжело работа с Memcache на больших объемах даже на уровне простейших put/get, без потенциально сложных SQL-запросов), распределенный DDL и гарантии ACID.

И наконец поддержка. Если что-то вдруг не работает, то компания просто теряет деньги. Для некоторых направлений это не критично, но зачастую важно, чтобы за продукт и его работу кто-то нес ответственность. Чтобы можно было в любой момент предъявить претензию, и она была быстро решена.

Этим постом мы завершаем год Промсвязьбанка на Хабре. Собрали новогодние пожелания для хабровчан в небольшом видео:

Комментарии (5)


  1. mr_bag
    28.12.2018 18:46

    После рассмотрения Apache Ignite почему отказались?


  1. algotrader2013
    29.12.2018 01:20
    +1

    Одна из крупнейших газет США предоставляет возможность своим читателям в режиме онлайн получить любой номер, который выходил с момента старта выпуска этой газеты в 19 веке. Можем представить уровень нагрузки.

    Серьезно? Отдача статики преподносится как значительный вызов в статье об in-memory базах?


    1. eefadeev
      29.12.2018 11:30

      Веяние времени…


    1. adictive_max
      29.12.2018 12:04

      Так может там всё биллингом и аналитикой так обмазано, что отдача статики — это мелочь, о которой даже не вспоминают…


  1. Arranticus
    29.12.2018 15:38

    Хранение реализовано ими посредством платформы Apache Kafka

    Разве Kafka — это система хранения? Это ведь очередь сообщений, он для других целей.