Багатоклієнтність та MongoDB

В багатьох організаціях розробляють багатокористувацькі (multi-tenant) SaaS-застосунки для своїх клієнтів. Створення таких застосунків має свої унікальні виклики, включаючи:

  • Масштабування — як кількості клієнтів, так і кожного окремого клієнта.
  • Безпека даних.
  • Дотримання нормативних вимог та політик клієнтів.

При проектуванні багатокористувацького рішення існують три рекомендовані патерни для зберігання даних:

  • Спільне розміщення всіх даних клієнтів в однакових колекціях.
  • Окремі бази даних для кожного клієнта в одному розгортанні.
  • Окремі розгортання для кожного клієнта.

Ще одна можливість, яку не рекомендується використовувати, — це зберігання даних кожного клієнта в окремих колекціях однієї бази даних. Краще уникати цього патерну, оскільки він додає складності та обмежує майбутнє масштабування.

Ці патерни не є взаємовиключними. Фактори, такі як масштабування, безпека та дотримання нормативних вимог, визначатимуть найкращий підхід для вашої архітектури. Спочатку давайте детально розглянемо кожен з наведених патернів, а також переваги та недоліки кожного. Після цього ми глибше заглибимося в масштабування, дотримання вимог і те, що робити з "шумними сусідами".

Для нашого прикладу ми використаємо програмне забезпечення ресторанного сервісу (RaaS), яке включає меню, точку продажу та аналітику. Нехай це буде сервіс під назвою HalfBaked. HalfBaked має безліч клієнтів різного розміру. Їх найбільший клієнт, McDougals (tenantID mcd), є глобальною мережею швидкого харчування з тисячами локацій та користувачів. Їхні найменші клієнти — окремі ресторани з однією локацією, які зазвичай мають дуже мало користувачів. Один з таких прикладів — ресторан "Mom and Pop" з tenantID mompop.

Визначення пріоритетів

При проектуванні багатокористувацького застосунку вам потрібно визначити пріоритети. Ось кілька питань, на які слід відповісти:

  • Чи будуть усі клієнти мати однакові функціональні вимоги та структуру даних?
  • Наскільки буде варіюватися розмір клієнта?
  • Які вимоги до безпеки та відповідності?
  • Наскільки масштабованим має бути рішення для всіх клієнтів та всередині кожного клієнта?
  • Що відбудеться, якщо клієнт значно виросте і стане "шумним сусідом"?

Є компроміси у вашому підході — більше відміток означає кращий підхід для досягнення мети:

pic

Патерн: Спільне розміщення всіх даних клієнтів в однакових колекціях

pic

Це поширений підхід для багатокористувацьких систем. Кожен документ завжди буде мати tenantID, щоб зберегти окремість для кожного клієнта. Наприклад, кожен клієнт може мати кілька ресторанів, які використовують наш сервіс. Колекція location може виглядати так:

{  
 _id: ObjectId("507f1f77bcf86cd799439011"),  
 locationID: '1234',  
 tenantID: 'mcd',  
 name: 'Central Park West',  
 address: {},    
 phone: '(123) 555–3214',    
 features: ['Dine In', 'Takeout', 'Drive Thru']    
}  

У цьому випадку у кожної локації є tenantID в колекції. Усі локації клієнтів зберігаються в одній колекції. Переваги цього підходу:

  • Простота: немає необхідності створювати нові колекції/індекси тощо при додаванні нового клієнта.
  • Висока масштабованість: цю колекцію легко можна розділити по tenantID та locationID для горизонтального масштабування.

Є й кілька недоліків цього підходу:

  • Безпека: додатки та сервіси, що взаємодіють з даними, повинні забезпечувати, щоб tenantID був присутній у КОЖНОМУ запиті, щоб уникнути доступу одного клієнта до даних іншого.
  • Налаштовуваність: потреби великого клієнта, такого як McDougals, можуть значно відрізнятися від потреб "Mom and Pop".
  • Можливі колізії: цілком ймовірно, що кілька клієнтів використовують однаковий механізм locationID, наприклад, номер ресторану.
    У цьому випадку, нам потрібно додати locationID як окреме поле, а не використовувати "вільний" індекс на полі _id.

Вибір цього варіанту вимагатиме, щоб всі індекси починалися з поля tenantID для ефективного отримання даних. Наприклад, ми додамо наступний індекс, щоб забезпечити ефективне отримання конкретної локації:

{tenantID : 1, locationID : 1}

Загалом, цей патерн найкраще підходить для випадків, коли всі клієнти подібні за розміром і функціональними вимогами, а додатки, що працюють із клієнтами, можуть легко забезпечити наявність tenantID в кожному запиті. Варіанти використання Інтернету речей (IoT) зазвичай добре працюють з цією архітектурою, оскільки масштабованість тут висока.

Патерн: Окремі бази даних для кожного клієнта

pic

Це ще один поширений підхід — кожен клієнт має набір колекцій у своїй власній базі даних. Основні переваги цього підходу:

  • Краща безпека, оскільки користувач клієнта має доступ тільки до однієї бази даних за допомогою вбудованого управління доступом на основі ролей.
  • Легша кастомізація для конкретного клієнта.
  • Більше варіантів для масштабування з часом.

Однак є один суттєвий недолік цього підходу: масштабування має свої обмеження. MongoDB рекомендує мати не більше 10 000 колекцій в одному кластері. Хоча 10 000 не є жорстким лімітом, проблеми з продуктивністю можуть виникнути, якщо кількість колекцій наближається до цього значення. Цей патерн може призвести до необхідності розгортання окремих кластерів, щоб підтримувати кількість колекцій та індексів на розумному рівні.

Патерн: Окремі розгортання для кожного клієнта

pic

Цей варіант надає найбільшу гнучкість і безпеку. Якщо ви використовуєте Atlas, ви можете скористатися API Atlas для автоматичного розгортання кластера і потім надати клієнту доступ до нього. Інші переваги цього варіанту:

  • Найвищий рівень безпеки.
  • Ізоляція мережі через віртуальну приватну мережу (VPC).
  • Клієнт може вибрати, у якому хмарному постачальнику та регіонах здійснити розгортання.
  • Повна кастомізація для кожного клієнта.
  • Можливість автоматизації розгортання для задоволення потреб кластера.

Основні недоліки цього підходу:

  • Витрати — окремий кластер для кожного клієнта буде коштувати дорожче. Це потрібно враховувати в ціні на ваші послуги.
  • Аналітика між клієнтами — запуск аналітики між клієнтами може бути складним.

Гібридний підхід

Опції окремої бази даних та розгортання тісно пов’язані, оскільки з точки зору застосунку різниця між ними мінімальна. Цей підхід дає вам найкраще з обох світів: менші клієнти, такі як Mom and Pop’s, можуть бути розміщені на одному розгортанні, а великі клієнти, такі як McDougals, можуть мати свій окремий кластер.

Найпростіший спосіб реалізувати це — мати мета-сховище в одній базі даних, яке зберігає інформацію про з'єднання для конкретного клієнта. Потім додаток може перевірити це і зрозуміти, до якої бази даних потрібно підключитися для конкретного tenantID. Архітектурно гібридний підхід виглядав би так:

pic

Масштабування нашого RaaS рішення та шумні сусіди

Потрібність масштабування вашого продукту — це найкраща проблема, яку можна мати. Якщо рішення спроектоване правильно, масштабування буде легким, і ви не станете жертвою свого власного успіху. Масштабування можна розглядати двояко: масштабування самого рішення або масштабування окремого клієнта. Безумовно, кількість транзакцій у точках продажу McDougals набагато більша, ніж у Mom and Pop’s.

Масштабування самого рішення

Підхід, який ви оберете, визначатиме, як потрібно здійснювати масштабування:

Спільні колекції для всіх даних: Розподіліть великі колекції за потребою для додавання горизонтального масштабу. Додаткові шардінги можна додавати з часом, і MongoDB подбає про балансування даних серед шардів для вас.

Окремі бази даних: Розподіліть великі колекції за потребою.
Як тільки шардінг буде додано, не поділені колекції для баз даних клієнтів можна перемістити на нові шарди за допомогою команд movePrimary або moveCollection command (новий в 8.0) для балансування не поділених колекцій між кількома шардми.

Окремі розгортання/гібрид: Якщо у вас є окремі розгортання, масштабування всього рішення стає трохи легшим. Масштабуйте вертикально розгортання, які потребують більше потужності, або шардируйте великі колекції в розгортанні для обробки навантаження.

Масштабування окремого клієнта

Якщо всі клієнти знаходяться на спільних колекціях, то єдиний вибір — це масштабувати розгортання або створити нове розгортання та перемістити клієнта до нього.

Якщо кожен клієнт має свою базу даних, найпростіший спосіб масштабувати великого клієнта — це перемістити його до власного кластера. Це можна навіть вважати послугою "білої рукавички" і брати за неї додаткову плату. Давайте розглянемо це більш детально в наступному розділі.

Шумні сусіди

Ніхто не любить шумних сусідів. На щастя, у світі баз даних зміна сусідів набагато простіша, ніж у реальному житті. Візьмемо, наприклад, McDougals. Коли їх вперше підключали, вони були в десятеро меншими за розміром, ніж зараз. Сьогодні їх обсяг викликає проблеми для інших клієнтів, і їм потрібно переміститися в своє розгортання, щоб ізолювати свою роботу від тих, хто працює з меншою інтенсивністю. На щастя, ми можемо зробити це майже без простою. Є два варіанти міграції. Кожен з них передбачає створення нового розгортання і потім його заповнення даними:

Варіант 1: Використання CDC для переміщення тільки їхніх даних

CDC коннектори, такі як Debezium, дозволяють фільтрувати дані перед публікацією у вашу чергу pub/sub. Загальний процес буде виглядати так:

pic

  • Цей крок потребує невеликого періоду простою — зазвичай кілька хвилин.

Зверніть увагу, що як тільки McDougals запуститься в новому місці, їхні дані повинні бути видалені з оригінального розгортання. Якщо всі дані знаходяться в спільних колекціях, вам потрібно буде написати код, щоб видалити всі їхні дані з оригінального розгортання, враховуючи потужність ресурсів на їхньому старому місці. Якщо кожен клієнт має свою базу даних, то можна виконати команду dropDatabase на їхньому оригінальному місці для видалення всіх їхніх даних.

Варіант 2: Використання синхронізації між кластерами для копіювання всіх даних

Синхронізація між кластерами дозволяє вибрати переміщення шумного сусіда або всіх тих, хто працює тихо. Якщо McDougals не терплять жодних простоїв, може бути простіше перемістити менших клієнтів і залишити McDougals в оригінальному розгортанні. У будь-якому випадку, цільовий кластер отримає всі ті ж дані, що й вихідний. Дані для клієнтів, які більше не перебувають на вихідному або цільовому кластері, повинні бути видалені. Це легко зробити, якщо кожен клієнт знаходиться в своїй базі даних.

Антипатерни

Є кілька пасток, яких слід уникати при проєктуванні архітектури для багатьох клієнтів:

Видалення tenantID: Якщо кожен клієнт має свою базу даних, може бути спокуса видалити поле tenantID з кожної колекції. Однак, найкраща практика — включати це поле, оскільки воно буде необхідне для запуску аналітики серед клієнтів. Це можна зробити за допомогою Atlas Data Federation.
Ймовірно, вам знадобиться поле tenantID для створення змістовної аналітики серед клієнтів.

Шардінг маленьких колекцій: Шардінг додає деякі витрати до операцій зчитування та запису, оскільки кожна операція має бути маршрутизована. Крім того, метадані шардінгу повинні зберігатися для кожної колекції. Для великих колекцій ці витрати виправдовуються за рахунок масштабованості. Однак надмірна кількість метаданих шардінгу може спричинити проблеми з продуктивністю на метасховищі шардінгу. Якщо ви використовуєте шаблон "база даних на кожного клієнта", ви можете залишити маленьких клієнтів повністю без шардінгу. Кожна база даних має основний шард, на якому зберігаються всі не шардовані колекції, і цей шард призначається за принципом кругового чергування при кожному створенні нової бази даних. Використовуйте команду movePrimary, щоб переконатися, що не шардовані дані клієнта збалансовані серед усіх ваших шард.

Висновок

При проєктуванні багатоклієнтського SaaS важливо розуміти, як клієнти можуть виглядати та розвиватися в майбутньому. Вибір правильного дизайну дозволить досягти максимальних результатів у масштабованості, безпеці та продуктивності.

Відвідайте наступні посилання для подальшого навчання:

MongoDB багатоклієнтська архітектура

Atlas Data Federation

Команда movePrimary

Команда moveCollection (нова в 8.0)

Шардінг в MongoDB

Синхронізація між кластерами

Перекладено з: Multi-tenancy and MongoDB

Leave a Reply

Your email address will not be published. Required fields are marked *