Отзыв о дизайне моей базы данных (мультитенантность) - PullRequest
0 голосов
/ 29 апреля 2020

Идея инструмента SaaS состоит в том, чтобы иметь динамические c таблицы с динамическими c настраиваемыми полями и значениями различных типов, мы думали использовать пример «force.com/salesforce.com», но, похоже, слишком сложный, чтобы продолжать двигаться вперед, а также создавать некоторые отчеты с огромным уровнем абстракции, поэтому мы пришли к простой идее, но должны быть уверены, что это хороший подход.

Это архитектура, которую мы иметь сегодня (за несколько шагов).

  1. У каждого арендатора есть своя отдельная база данных в кластере (Postgres 12).
  2. Таблица TABLE, используемая для хранения всех этих таблиц в качестве ссылки, этот объект имеет отношение ManyToOne к таблице META и отношение OneToMany с таблицей DATA.
  3. Таблица META используется для настройки метаданных, имеет отношение OneToMany с FIELDS (в котором есть имя полей, а также тип поле, например TEXT/INTEGER/BOOLEAN/DATETIME et c. и значение атрибута - в виде строки, только в качестве ссылки).
  4. Таблица DATA имеет отношение ManyToOne к TABLES и 50 character varying столбцы с именами, такими как: attribute1...50, которые могут иметь значение NULL.

Пример потока сегодня:

  1. Когда пользователь хочет открыть ТАБЛИЦУ ДАННЫХ, например " АВТОМОБИЛИ ", мы загружаем таблицу META со всеми ПОЛЯМИ (чтобы получить поля для этого запроса). Пользователь указал, что он хочет выполнить запрос к: Brand, Class, Year, Price столбцам.
  2. Мы проверяем по логи c, ссылку на бренд, класс, год и цену в таблице META> FIELDS, поэтому мы знаем, что Brand = attribute2, Class = attribute 5, Year = attribute6 and Price = attribute7.
  3. Мы анализируем его запрос в запросе, например: SELECT [attr...2,5,6,7] FROM DATA, и затем показываем результаты пользователю, если пользователь решает применить к нему некоторые фильтры, основываясь на этих данных, например, Year > 2017 AND Class = 'A', которые мы используем CAST() функциональность SQL, например SELECT CAST(attribute6 AS int) AND attribute5 FROM DATA WHERE CAST(attribute6 AS int) > 2017 AND attribute5 = 'A';, поэтому мы можем фактически поддерживать большинство принципов SQL.

Однако, продвигаясь вперед, мы немного напуганы:

  • Управляйте такой средой для большего количества арендаторов, в то время как у нас будет больше таблиц (e.g. 50 per customer, with roughly 1-5 mil per TABLE ( 5mil - это максимум, который мы допускаем, для больших данных у нас есть BigQuery ) which is giving us 50-250 mil rows in single table DATA_X), который может повлиять на производительность запросов, особенно когда мы дали возможности управления простыми операторами WHERE (меньше, равно, ноль и т. д. c.) с использованием некоторого языка абстракции, например, GET CARS [BRAND,CLASS,PRICE...] FILTER [EQ(CLASS,A),MT(YEAR,2017)], разработанного, чтобы быть похожим на JQL (язык запросов Jira).
  • Блокировка транзакций, как w мы разрешаем пакетную загрузку CSV в DATA_X, поэтому, как только они захотят загрузить, например, 1 ГБ данных, он как бы блокирует таблицу для доступа других систем к таблице DATA.
  • Сохранение нескольких столбцов NULL, которые могут повлиять немного (пока мы не так напуганы, как при создании TABLE, клиент может решить, сколько столбцов он хочет, поэтому на основании этого мы присваиваем этот TABLE одной из жестко закодированных сущностей DATA_5, DATA_10, DATA_15, DATA_20, DATA_30, DATA_50, где числа соответствуют ограничениям столбцы атрибутов, и эти объекты различны, мы также поддерживаем опцию миграции, если они решают переключиться с 5 на 10 атрибутов и т. д. c.

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

Мы также думали об объектах JSONB, но это не вариант, так как мы хотим упростить получение данных.

Что вы думаете об этом решении (к вашему сведению, DATA имеет первичный ключ из 2 таблиц - (ID, TABLEID) и встроенный столбец CreatedAt, который используется для большинства запросов, поэтому максимум будет 3 индекса)?

Если это покажется плохим, что бы вы порекомендовали в качестве альтернативы этому решению, основываясь на подробностях, которыми я поделился (в основном СУБД без схемы)?

1 Ответ

1 голос
/ 04 мая 2020

ИМХО, я ожидаю проблем, когда вы захотите присоединиться к столам, а также использовать cast et c.

Мы следовали приведенному ниже подходу, который поможет вам

У нас есть таблица называется Cars, а также имеет несколько таблиц, таких как CarsMeta, CarsExtension столбцы. Базовая таблица Cars будет иметь все общие поля для всех арендаторов. Кроме того, у нас будет таблица CarsMeta, указывающая, какие типы столбцов можно использовать для расширения сущности Cars. В таблице CarsExtension у вас будут такие столбцы, как StringCol1...5, IntCol1....5, LongCol1...10

Таким образом, вы можете легко фильтровать данные, например,

  • Если у вас есть фильтр на В базовой таблице выполните поиск, если результаты найдены, сопоставьте идентификаторы с таблицей CarsExtension, чтобы получить список расширенных строк для этой сущности
  • В случае, если фильтр находится в расширенных полях, выполните поиск в таблице расширений и совпадают с идентификаторами базовых идентификаторов сущностей.
  • Поскольку у нас будет таблица расширений, организованная следующим образом:

    id - UniqueId

    entityid - uniqueid (указывает на первичный ключ сущности)

    StringCol1 - строка,

    ...

    IntCol1 - int, ...

В этом случае будет легко выполнить объединение для сущности, а затем получить данные вместе с полями расширения.

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

HTH

...