Postgresql 关于我的数据库设计的反馈(多租户)

Postgresql 关于我的数据库设计的反馈(多租户),postgresql,database-design,rdbms,multi-tenant,saas,Postgresql,Database Design,Rdbms,Multi Tenant,Saas,SaaS工具的思想是拥有具有动态自定义字段和不同类型值的动态表,我们曾考虑使用“force.com/salesforce.com”示例,但它似乎过于复杂,无法继续前进,还需要创建一些具有巨大抽象级别的报告,所以我们提出了一个简单的想法,但我们必须确保这是一个不错的方法 这就是我们今天的架构(只需几个步骤) 每个租户在集群上都有自己的独立数据库(Postgres 12) TABLE TABLE,用于保留所有这些表作为引用,该实体与元表有多个关系,与数据表有多个关系 元表用于元数据配置,与字段有一对

SaaS工具的思想是拥有具有动态自定义字段和不同类型值的动态表,我们曾考虑使用“force.com/salesforce.com”示例,但它似乎过于复杂,无法继续前进,还需要创建一些具有巨大抽象级别的报告,所以我们提出了一个简单的想法,但我们必须确保这是一个不错的方法

这就是我们今天的架构(只需几个步骤)

  • 每个租户在集群上都有自己的独立数据库(Postgres 12)
  • TABLE TABLE,用于保留所有这些表作为引用,该实体与元表有多个关系,与数据表有多个关系
  • 元表用于元数据配置,与字段有一对一的关系(它有字段名称以及字段类型,例如
    TEXT/INTEGER/BOOLEAN/DATETIME
    等,属性值-作为字符串,仅作为参考)
  • 数据表与表和
    50个字符变化的
    列有多个关系,这些列的名称为:
    attribute1…50
    ,可以为空
  • 今天的流程示例:

  • 当用户想要打开一个表数据,例如“CARS”时,我们加载包含所有字段的元表(以获取此查询的字段)。用户指定要查询的:
    品牌、类别、年份、价格
  • 我们通过逻辑检查META>字段表中的品牌、类别、年份和价格参考,因此我们知道
    品牌=属性2,类别=属性5,年份=属性6和价格=属性7
  • 我们将他的请求解析为一个查询,例如:
    SELECT[attr…2,5,6,7]FROM DATA
    ,然后向用户显示结果,如果用户决定对其进行一些筛选,则基于此数据,例如
    Year>2017和Class='a'
    我们使用
    CAST()
    SQL的功能,例如
    选择CAST(attribute6 AS int)和attribute5来自数据,其中CAST(attribute6为int)>2017,attribute5='A',这样我们就可以真正支持SQL的大多数原则
  • 然而,在前进的道路上,我们有点害怕:

    • 为更多租户管理这样的环境,同时我们将拥有更多的表
      (例如,每个客户50个,每个表大约1-5 mil(
      5 mil是我们允许的最大值,对于更大的数据,我们有BigQuery
      ),这将在单个表数据中为我们提供50-250 mil行)
      ,这可能会影响查询的性能,特别是当我们提供了使用一些抽象语言管理简单WHERE语句(less、equal、null等)的可能性时,例如
      GET CARS[BRAND、CLASS、PRICE…]FILTER[EQ(CLASS、A)、MT(YEAR,2017)]
      开发为类似于JQL(Jira查询语言)
    • 事务锁定,因为我们允许将CSV批量上传到
      数据_X
      ,所以一旦他们想要加载例如1GB的数据,它就有点锁定表,以便其他系统访问数据表
    • 保留多个空列,这会稍微影响空间(因为现在我们不像创建表时那样害怕,客户可以决定他想要多少列,因此基于这一点,我们将此表分配给硬编码实体
      DATA_5、DATA_10、DATA_15、DATA_20、DATA_30、DATA_50
      ,其中数字对应于属性列的限制,并且这些实体不同,我们将lso支持迁移选项,如果他们决定从5个属性切换到10个属性等
    我们正处于超早期阶段,因此我们可以/应该在扩大规模之前制作这些产品,因为我们知道这很可能不是最好的方法,但我们将其用于为小客户运行该项目,目前效果良好

    我们也在考虑JSONB对象,但这不是我们的选择,因为我们想让获取数据变得简单

    您对该解决方案有何看法(fyi数据有两个表中的主键-(ID,TABLEID)和内置列CreatedAt(用于大多数查询),因此最多有3个索引)


    如果它看起来不好,那么根据我共享的详细信息(基本上是无模式的RDBMS),您会推荐什么作为此解决方案的替代方案呢?

    IMHO,当您想要连接表和使用cast等时,我预计会出现问题

    我们遵循了下面的方法,这将对您有所帮助

    我们有一个名为
    Cars
    的表,还有两个类似
    CarsMeta
    CarsExtension
    列的表。基础
    Cars
    表将包含所有租户的公共字段。此外,我们还将让
    CarsMeta
    表指出您可以为ex使用的列的类型处理
    Cars
    实体。在
    CarsExtension
    表中,您将有
    StringCol1…5、IntCol1…5、LongCol1…10等列

    通过这种方式,您可以轻松地筛选数据,如

    • 如果基础表上有一个筛选器,则执行搜索,如果找到结果,则将ID与
      CarsExtension
      表匹配,以获取此实体的已执行行列表
    • 如果过滤器位于扩展字段上,则在扩展表上进行搜索,并与基本实体ID的搜索相匹配
    • 因为我们将有如下组织的扩展表

      id-唯一id

      entityid-唯一ID(指向实体的主键)

      StringCol1-string

      IntCol1-int,

    在这种情况下,很容易对实体进行连接,然后获取数据和扩展字段

    如果要从单独的表中推断出表元数据和数据,那么在很长一段时间内以及大量数据中维护这一点将是一项困难的任务


    HTH

    感谢您的反馈,如果我理解正确,我们将有“Cars”表,其中包含一些常见数据,如Premium Ca