Как стать автором
Обновить

Комментарии 39

Александр, спасибо за статью. Хотелось бы кое-что прояснить для себя:
1. Не беспокоитесь ли вы, что с ростом количества клиентов у вас будут проблемы с администрированием N баз данных?
2. Как вы шерите общие справочники, актуальные одновременно для всех тенантов?
3. Что такое «монолитные сервисы» и чем они отличаются от микросервисов в вашей интерпретации?
4. Вам не мешает жесткая привязка тенанта к потоку при выполнении асинхронного метода контроллера для http-запроса? Ведь при выполнении асинхронной операции неизвестно в каком потоке будет выполнен continuation после await.
1. На один облачный стенд у нас приходится около 80 тенантов (80 БД). Таких стендов несколько. Есть еще выделенные стенды для крупных компании, где только 1 БД. Администрировать все БД руками не реально. Поэтому все основные задачи автоматизируются скриптами и джобами (выполняются массово для всех БД) — бэкапы, обновления структуры БД. И админ не один — несколько.

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

Минусы варианта, когда добавляем колонку «идентификатор тенанта» в каждую таблицу:
— в каждый запрос к БД нужно добавлять фильтр по тенанту
— в каждый индекс в БД нужно добавлять тенант в ключ
— есть таблицы, которые быстро растут в организациях (документы, задачи, права), если сейчас они как бы секционированы (у каждой организации своя БД с таблицами), то если данные организаций объединить запросы станут хуже по скорости (я про поисковые запросы по произвольным свойствам с учетом прав)

А еще мы действительно используем возможность перераспределения тенантов по стендам, т.е. когда данные одного тенанта переносятся на другой стенд.
2. Общие справочники мы дублируем. У каждого тенанта своя копия записей. Но таких справочников не много, место занимают мало. Это города, страны, регионы, системные роли и пользователи, журналы регистрации…
3. Монолитные (в контексте нашей системы) — это сервисы, в которых большой объем кода, наверное, близко к миллиону строк. Их писали годами, у них много ответственностей (функционала). Один человек не может держать в голове детали по всему функционалу.

А микросервисы (в контексте нашей системы) — это сервисы, у которых явно выделены одна-две ответственности. Их в принципе можно переписать за неделю.

Я говорил у нас 5 монолитных сервисов, но точнее будет так: у нас один монолит (платформа) и на ее базе 5 сервисов с разным АПИ.
4. Да, с такой проблемой мы сталкивались. Тут все зависит от того как реализовать привязку. Вначале мы делали привязку так и в какой-то момент получили проблемы:
private static readonly ThreadLocal<string> tenantId = new ThreadLocal<string>();

Потом сменили на:

private static readonly AsyncLocal<string> tenantId = new AsyncLocal<string>();
Да, я как раз про это.
Видимо сказывается многозначность термина «Поток» в русском языке) Он может означать и thread и asynchronous flow.
Куча БД — не масштабируемое решение. Сейчас 80, что уже много, а представьте что будет с 1000 или 10000 клиентов.
Представьте как это дело обновлять. Добавили колонку, а скрипт который обновляет схемы БД падает на каком-то тенанте.
Всем этим зоопарком придется как-то управлять, и почти наверняка чем-то своим самописным.

А еще билинг, еще отчеты. Как сделать выборку всех заказов всех клиентов для аналитики? Все это очень усложняет управление.
Тенанты мы объединяем в группы: 80 тенантов на стенд, размер одной БД ~5 Гб, суммарный объем баз ~400 Гб. Это значит 10000 тенантов будут разбиты на ~100 стендов.

Обновлять стенды можно последовательно. Поэтому проблему, что обновление падает на конкретном тенанте (а проблема кстати существует), будет только для конкретного стенда.

Но то что придется рулить зоопарком в 100 стендов — это конечно напряг, нужны админы.

Отчеты производительности и ошибок — это уже автоматизировано. А отчетов по данным из БД организаций мы не собираем (я не слышал чтобы собирали), это же ECM-система.
рулить зоопарком в 100 стендов — это конечно напряг, нужны админы

Если сделать в одной большой БД — то их столько не надо. Значит если бы был владельцем, получил бы больше денег ;)

это же ECM-система

Надо же вам иметь статистику по кол-ву юзеров/документов во всех БД. Если бы я был вашим топ-менеджером, то спросил бы, на сколько мы выросли за год.

Что касается статистики по количество документов-задач. Да, такая есть
Куча БД — не масштабируемое решение.
Если сделать в одной большой БД — то их столько не надо.

Окей. А как вы будите масштабировать одну большую БД?

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

Если и этого будет мало, то значит мы уже не маленькая компания на 30 человек, а значит сможем позволить себе специалистов которые подскажут-расскажут-сделают.

Вспомните что было с ОК.ру и как он тормозил. Они выросли, переписали все внутри, перенесли данные и сейчас всё у них хорошо. Мне кажется это правильным подходом.

Реплики на чтение, не решают проблему шумных соседей. Её можно решить только выносом этих клиентов на отдельные инстансы.


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

запас прочности в виде выноса на отельные инстансы

Дело в том что подход с колонками tenantid — можно расширить на отдельные инстансы, путем вывода «жирных» клиентов по другим серверам.
А вот подход multyinstance множество мелких клиентов уже никак не объединить в один сервер.
Понимаете, я могу объяснить бизнесу, что клиент приносящий несколько тысяч долларов в месяц требует дополнительный сервер, но почему на каждого мелкого клиента с совокупным доходом несколько десятков долларов, мы должны поддерживать для него базу/инстанс — с трудом.
А вот подход multyinstance множество мелких клиентов уже никак не объединить в один сервер.

А что этому мешает?

А что этому мешает?

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

Закладывание этой прочности в архитектуру сейчас, когда там и так справляется одна машина, на мой взгляд лишнее.
Эта архитектура (СЕЙЧАС) лишь дополнительная уйма работы для DBA и DevOps. На ровном месте создали постоянные издержки для компании только из-за предположения что когда тенанты начнут сильно расти, мы легко с этим справимся.

Когда все тенанты в одной БД, то не сильно сложно склонировать схему, забить её только данными нагруженного тенанта и отправить эту отдельную БД на свою отдельную железку.

Вопрос лишь в деньгах.

Не всегда архитектура предполагает возможность использовать реплики на чтение.
Шардинг как раз таки решает проблему когда один тенант больше остальных грузит инстанс БД

А теперь представь тысячи клиентов на одном бд сервере)

У нас ~3000 тенантов и суммарно ~1млн юзеров. Все в одной БД.
Одна активная СУБД + два активных сервера приложений в пике держат до 400 запросов в секунду (в среднем 70-100).

Если бы у нас была мульти БД архитектура — я бы повешался.
Когда все тенанты хранятся в одной БД возникает другая крайность — все клиенты оказывают нагрузку на одну и ту же БД.

Справедливо ли это? Когда у вас для каждого клиента своя БД, то вы можете этим управлять и выносить наиболее нагруженных клиентов на отдельные сервера.
все клиенты оказывают нагрузку на одну и ту же БД

Это сильно зависит от самого приложения и бизнес процессов. Если есть очень много мелких клиентов и несколько крупных, то проще — мелочь сложить в одну базу, а крупным выделить свой instance бд/сервисов/сайтов и т.д.
Реплики на чтения решат 90% нашей загрузки к БД.
Надеюсь когда нибудь буду решать эту проблему :) Чем больше нагрузки — тем мы больше заработаем.

Если прямо завтра надо решать эту задачу, я сделаю бэкап базы, подниму новый сервер с клоном (со всем тенантами) и перенесу нагрузку на новую СУБД. Да, будет проблема назад всё это слить, но ведь нам надо бизнес делать, а не придумывать идеальные схемы :)
Такую нагрузку ~1млн юзеров не любое приложение потянет. Данные могут в терабайтах измеряться. А что у вас за приложение?
Интернет-магазин. Да и 1млн он же мертывый лежит. Активных то куда как меньше.
А для чего деление на тенанты в интернет-магазине?
Не точно выразился. У нас SaaS-платформа интернет-магазинов (.net + sql server + redis + elk).

Почему в качестве конфигурации тенантов файл, а не отдельная общая бд + кеш? Это было бы проще чем распростронять файл по куче серверов

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

Но с файлами было проще (так исторически сложилось)
Гипотетически ведь можно не имя бд указывать, а весь connectionstring целиком? чтобы например еще и по разным SQL-серверам раскидывать, так?
Гипотетически можно. Ограничений нет на работу с несколькими СУБД. NHibernate «съест» без проблем.

100 таблиц? Насколько я помню Директум, почти всё в одной гигантской таблице лежит, с миллионом колонок.

В Directum одна гигантская, а тут DirectumRX — следующее поколение платформы, которую с нуля начали писать (уже давно как). В ней нет такой проблемы.

Чем не понравился подход "schema per tenant"?

С подходом «БД на тенант» получалось проще администрирование (проще чем с «schema per tenant»). Админу проще перенести БД на другой облачный стенд (т.е. перераспределить тенанты по стендам), проще забэкапить и восстановить бэкап конкретной организации. Проще узнать объем памяти занимаемой БД. Т.е. такой единицей как БД манипулировать удобнее.

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

Правда таблицы у нас общие. В каждой таблице есть первичный ключ (tenant_id, id) и в случае необходимости можно вынести отдельного клиента в отдельную бд.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий