Это второй раз за последние пару лет, когда мне выпала честь быть частью команды, создающей устойчивую многопользовательскую инфраструктуру SaaS. Определение мультиарендность - это архитектура, в которой один экземпляр программного обеспечения (который может состоять из нескольких сервисов / микросервисов) обслуживает несколько арендаторов / объектов, которые могут представлять либо потребителей, либо корпоративных пользователей сервиса.

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

Я воздержусь от пропаганды вариантов использования, в которых база данных SQL имеет преимущества по сравнению с другими типами баз данных, и предполагаю, что обсуждение того, какая база данных может вам понадобиться в вашем сервисе, должно иметь место в любом случае.

Требования к базе данных мультитенантного сервиса

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

Альтернатива A - База данных на арендатора

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

Одно из потенциальных преимуществ, которое можно получить, работая таким образом, - это возможность поддерживать разные версии схемы для разных клиентов, реализуя постепенное обновление.

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

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

Альтернатива B - схема PostgreSQL для каждого клиента

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

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

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

По-прежнему сохраняется обратная сторона сложности эксплуатации. Внесение обновлений в структуру базы данных потребует внесения изменений в таблицы во всех схемах.

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

Альтернатива C1 - Общие таблицы со столбцом для идентификации арендатора

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

Основным недостатком этого подхода является то, что он требует очень пристального внимания к мультиарендности при реализации сервисов / микросервисов на уровне доступа к данным. Неопытный разработчик может легко сделать ошибку, не добавив «WHERE tenant_id =‹… ›» в запрос, и повлиять на данные нескольких клиентов. То же самое (и даже более опасное) для рассмотрения того, что кто-то враждебно захватит ваши микросервисы и получит учетные данные для доступа к базе данных. Радиус взрыва в таком случае очень велик, и последствия такой проломы могут быть катастрофическими.

С другой стороны, такую ​​инфраструктуру базы данных очень легко создать и масштабировать до большого количества клиентов службы.

Хотя это популярный выбор, его недостатки (в основном - отсутствие принудительного разделения доступа на стороне сервера) заставили некоторых архитекторов программного обеспечения задуматься о чем-то более продвинутом.

Альтернатива C2 - Общие таблицы со столбцом для идентификации клиента и политик безопасности на уровне строк

Безопасность на уровне строк (RLS) в базах данных SQL - это механизм, который позволяет управлять доступом (с высокой степенью детализации) к строкам в таблицах, которые соответствуют определенному выражению.

Чтобы воспользоваться этой возможностью, для соответствующих таблиц должна быть включена защита на уровне строк («ALTER TABLE… ENABLE ROW LEVEL SECURITY»), а также должны быть определены объекты политики, определяющие соответствующие разрешения («CREATE POLICY ON»).

В частности, в случае, когда выделенное поле (например, tenant_id из приведенного выше примера) содержит критерии разделения, части политики using_expression и check_expression должны содержать логическое значение Выражение SQL.

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

Резюме

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

  • Какое количество арендаторов будет в вашей системе?
  • Как часто мы ожидаем обновлять схему базы данных?
  • Как часто мы ожидаем удалять арендаторов (и все их данные)?
  • Должен ли наш сервис соответствовать требованиям главы SOC 2 о конфиденциальности?
  • Должен ли наш сервис соответствовать стандарту ISO 27001?
  • Потребуется ли нам внедрять операции по обеспечению конфиденциальности (на основе GDPR / Закона Калифорнии о конфиденциальности)?

В следующих статьях серии мы предоставим более подробную техническую информацию о реализации.

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