Мультитенантность в CUBA с помощью Citus
Вступление
Мультитенантность - это режим работы программного обеспечения, при котором несколько независимых экземпляров одного или нескольких приложений работают в совместно используемой среде. Экземпляры (тенанты) изолированы логически, но физически - интегрированы.
В интернете можно найти вот такое понятное объяснение, в котором мультитенантность с арендой в офисном здании:
Многоквартирный дом - прекрасный образец мультитенантной архитектуры. В нем централизована система безопасности (на въезде), электричество, водоснабжение и другие удобства. Всем этим владеет арендодатель многоквартирного дома и предоставляет квартиросъемщикам (в случае приложения - тенантам).
В этой статье мы поговорим о мультитенантной архитектуре и рассмотрим одну из реализаций этого подхода в CUBA Platform.
Краткая история мультитенантности
Начнем мы совсем с другой темы - распространении и развертывании ПО. Изначально программы передавались на физических носителях - дискетах, дисках, флешках. И чаще всего ПО приходилось развертывать на сервере, установленном в локальной сети. Каналы связи в то время сильно были хуже, чем сейчас.
Но со временем пропускная способность интернета возросла, и теперь мы можем говорить о мегабитных и гигабитных скоростях в каналах передачи данных. Поэтому намного проще стало поставлять ПО через интернет - загрузка сотни мегабайтов занимает меньше времени, чем поход в магазин за DVD или Blu-ray-диском с ПО.
В то же время, развитие каналов связи и компьютерного оборудования вывело на рынок виртуализацию, а позже и IaaS (инфраструктура как услуга). Стало проще настроить сервер с уже установленным приложением и предоставить пользователям доступ к нему. В результате производители стали работать по модели SaaS - “ПО как услуга”. Клиенту достаточно просто приобрести доступ к уже развернутому приложению. Отсутствие необходимости поддерживать локальные сервера снижало затраты.
Установка приложения для каждого клиента все еще была непростой, поэтому разработчики начали использовать мультитенантную архитектуру - чтобы упростить развертывание и поддержку. Вы все еще можете встретить в интернете приложения, развернутые по модели SaaS. Например, Salesforce уже давно начали использовать мультитенантный подход и SaaS. Но реализация мультитенантных приложений - непростая задача. Нужно позаботиться о разделении пользовательских данных, совместном использовании ресурсов, совместном использовании данных и других вещах. Новейшее решение всех этих задач выразилось в модели PaaS (платформа как сервис).
Развитие контейнеризации и процессов DevOps - главные угрозы для мультитенантных приложений. Сейчас очень легко запустить новый сервер или целую инфраструктуру, включая базы данных, службы приложений, сервера кэширования и обратный прокси-сервер благодаря таким продуктам как Docker и Kubernetes. Для разработчика намного проще реализовать сингл-тенант приложение и развертывать его экземпляры для каждого нового клиента буквально за секунды, и зачастую полностью автоматически. Иногда это может привести к феномену под названием “cube sprawl” - “рой кубернетесов”, но “волшебной палочки” в мире пока не существует.
Тем не менее, мультитенантные приложения не теряют своей актуальности (взять, например, Salesforce, Work Day, Sumo Logic, и т.д.), и, опять же, на разработку таких продуктов есть спрос. Рассмотрим некоторые подходы к реализации мультитенантности и разберемся, что для этого предлагает CUBA Platform.
Реализации мультитенантности
Есть три основных модели, применяющихся в построении мультитенантного хранилища данных:

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


Плюсы и минусы мультитенантности
Мультитенантная архитектура обладает определенными плюсами и минусами:
Плюсы:
- Себестоимость. По сравнению с другими решениями затраты на нее меньше.
- Упрощенный режим поддержки - администраторам нужно следить, патчить, обновлять и настраивать только одну систему.
- Масштабируемость - добавление нового тенанта является стандартным, быстрым и простым процессом.
Минусы:
- Создать мультитенантное приложение сложнее, чем обычное.
- Еще один проблемный момент - адаптивность, особенно для приложений, пользователи которых находятся в разных странах. Могут быть разные требования к защите персональных данных, расчетам бизнес-логики и т.д..
- Проблема “любопытного соседа” - другая группа пользователей может получить доступ к вашим данным или использовать ваши ресурсы.
- Проблема с единой точкой отказа. Если есть ресурс, которым пользуются все клиенты (например, база данных), недоступность этого ресурса отразится на всех.
Мультитенантность в CUBA
Платформа CUBA позволяет на ходу менять запросы, генерируемые в БД, таким образом реализован доступ к данным на уровне строк. Было нетрудно расширить этот механизм для поддержки мультитенантных приложений и создать дополнительный модуль для фреймворка.
Если вы создаете мультитенантное приложение с помощью CUBA, нужно решить, какие сущности будут хранить данные тенантов. Возьмем, например, демо-приложение “PetClinic”. Модель данных приложения представлена на диаграмме ниже.
Можно разделить данные всех сущностей, но есть и другой вариант - сделать общие ссылочные сущности. Допустим, специальности ветеринаров и типы питомцев достаточно нейтральная информация, можено сделать ее общей для разных клиентов. То же самое можно сказать о других данных общего характера, например, список стран, города и т.д.. Это позволит нам сэкономить место и убедиться, что “стандартные” данные будут одинаковы для всех тенантов,так что на них можно полагаться при реализации бизнес логики.
Если вам нужно, чтобы сущность поддерживала мультитенантность в CUBA, необходимо реализовать специальный интерфейс - com.haulmont.cuba.core.entity.TenantEntity
. После этого аддон начнет учитывать специальную колонку для разделения групп пользователей. Например, вот фрагмент кода сущности Pet:
public class Pet extends NamedEntity implements TenantEntity {
@TenantId
@Column(name = "TENANT_ID")
protected String tenantId;
@NotNull
@Column(name = "IDENTIFICATION_NUMBER", nullable = false)
protected String identificationNumber;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TYPE_ID")
protected PetType petType;
Обратите внимание, что все “системные” сущности в CUBA по умолчанию поддерживают мультитенантность. Так что все пользователи, роли, сохраненные фильтры, роли безопасности и т.д. у вас будут отдельными для каждого клиента.
После создания сущностей CUBA добавит дополнительное условие “where tenant_id =
Например, если мы хотим выбрать питомцев и владельцев из базы данных ветклиники, итоговый запрос будет выглядеть так:
SELECT *
FROM PETCLINIC_PET t1
LEFT OUTER JOIN PETCLINIC_OWNER t0
ON ((t0.ID = t1.OWNER_ID)
AND (t0.TENANT_ID = t1.TENANT_ID))
LEFT OUTER JOIN PETCLINIC_PET_TYPE t2
ON (t2.ID = t1.TYPE_ID)
WHERE ((t1.TENANT_ID = 'clinic_one') AND (t1.DELETE_TS IS NULL))
Обратите внимание на условия запроса: AND (t0.TENANT_ID = t1.TENANT_ID))
и (t1.TENANT_ID = 'clinic_one')
. Эти условия неявно добавляются фреймворком CUBA.
Но есть ли способ реализовать мультитенантность на уровне не только приложения, но и БД, без изменения программного кода? Одним из решений является Citus.
Citus: мультитенантность для PostgreSQL
Как сказано в описании: “Citus - это беспроблемный Postgres, созданный для масштабирования. Это расширение для Postgres, которое поставляет данные и запросы в кластер устройств. Являясь расширением (а не форком), Citus поддерживает новые версии PostgreSQL, позволяя пользователям использовать новые возможности, и при этом совместим с существующими инструментами PostgreSQL.
Citus горизонтально масштабирует PostgreSQL на несколько серверов путем сегментирования и тиражирования. Его движок исполнения запросов распараллеливает входящие SQL-запросы по серверам, обеспечивая ответы больших массивов данных в режиме реального времени (менее секунды).”
Похоже, Citus решает множество проблем, связанных с созданием мультитенантных приложений. Он позволяет нам использовать модель с отдельными базами данных для групп пользователей, а выглядит это так, будто у приложения единая база данных. Все запросы преобразуются согласно правилам, заданным при создании кластера Citus.
Это значит, что вы можете использовать модель мультитенантности CUBA и получите физически разделенные базы данных без переписывания запросов. Рассмотрим подробнее процесс создания базы данных на Citus.
Прежде всего нужно создать кластер базы данных и добавить к нему ноды. Кластер состоит из ноды-координатора и рабочих нод. каждая рабочая надо хранит данные, специфичные для групп пользователей, а координатор перенаправляет запросы согласно колонке “tenant_id”. Приложение посылает запросы только координатору. Этот процесс представлен на картинке ниже (изображение взято из документации Citus DB):
Также для работы с Citus нужно определить типы таблиц во время создание БД. Есть три типа:
- Распределенный. Это таблицы, разделенные между рабочими узлами. Они содержат данные, специфичные для тенантов.
- Ссылочные таблицы - хранят информацию, которую могут использовать все тенанты. В случае PetClinic это будут сущности “Specialty” и “Pet Type”.
- Локальные таблицы. Сервисные таблицы, которые не задействованы в запросах на соединение с распределенными или ссылочными таблицами. Например, таблицы метаданных Citus, хранящиеся в узле-координаторе.
Корпоративная версия Citus позволяет разделять данные групп пользователей явно, назначая оператор:
SELECT isolate_tenant_to_new_shard('table_name', 'tenant_id');
Это заменит разделение на основе хэша, которое используется по умолчанию.
Использование Citus в CUBA
Для приложений на CUBA код переписывать не нужно, однако необходимо настроить процесс создания БД и таблиц для совместимости с Citus. CUBA всем нравится своей магией создания SQL, но, к сожалению, для поддержки Citus несколько скриптов придется написать вручную.
Процесс создания БД изменен. Нужно создать расширение для Citus, добавить ноды и репликацию:
CREATE EXTENSION citus;
SELECT * from master_add_node('localhost', 9701);
SELECT * from master_add_node('localhost', 9702);
SET citus.replication_model = 'streaming';
Одно из удобств состоит в том, что большую часть времени можно разрабатывать приложение на локальной “обычной” PostgreSQL, а скрипты создания таблиц Citus добавить уже непосредственно перед пробным развертыванием или продакшеном (если у вас хватит смелости пропустить пробный этап). Единственное, что изменится, это адрес базы данных - вместо локальной дев-БД нужно задать адрес БД кластера координатора Citus.
Что касается создания и обновления БД, необходимо определить распределяемые таблицы эксплицитно (имя таблицы и колонку разграничения):
SELECT create_distributed_table('petclinic_vet', 'tenant_id');
SELECT create_distributed_table('petclinic_owner', 'tenant_id');
SELECT create_distributed_table('petclinic_pet', 'tenant_id');
SELECT create_distributed_table('petclinic_visit', 'tenant_id');
И подобным же образом сделать это со ссылочными таблицами:
SELECT create_reference_table('petclinic_specialty');
SELECT create_reference_table('petclinic_vet_specialty_link');
SELECT create_reference_table('petclinic_pet_type');
Чтобы улучшить производительность, помимо разграничения можно добавить слияние таблиц. Но со стороны CUBA не нужно ничего менять, мультитенантность - более чем прозрачный процесс.
Вот и все. С минимальными усилиями мы добились того, что приложение PetClinic горизонтально масштабируется и использует мультитенантность, в том числе на уровне БД. Все сложности скрыты внутри фреймворка и модуля Citus.
Заключение
Создание мультитенантных приложений может оказаться весьма непростой работой. Нужно учесть все возможности, прежде чем начать реализовывать этот подход. Но технологии - только часть сложностей такой задачи: нужно также учитывать стоимость лицензий, тарифы облачных сервисов, удобство сопровождения, масштабируемость и т.д. Сегодня модель PaaS наравне с контейнеризацией и налаженными DevOps-процессами выглядит более привлекательно, чем “традиционная” мультитенантная архитектура, однако спрос на такие приложения все еще достаточно высок.
Если вы планируете в дальнейшим работать с мультитенантностью, убедитесь, что ваше приложение соответствует всем требованиям безопасности, таким как GDPR, HIPAA и т.д. Некоторые из них могу открыто запрещать хранить данные в одной и той же базе, так что вам нужно будет задействовать подход с отдельными базами данных для соответствия таким требованиям.
И помните - инструменты имеют значение. Соответствующие платформы для разработки и системы управления данными, которые имеют встроенную функциональность для поддержки мультитенантности, могут существенно упростить процесс разработки и предотвратить множество типичных ошибок, свойственных мультитенантной архитектуре.