是否可以在rails中有多个数据库连接池来进行切换?


12

一点背景

多年来,我一直在使用Apartment gem来运行多租户应用程序。现在最近有需要将数据库扩展到单独的主机,数据库服务器根本无法跟上(读和写都变得太多了)-是的,我将硬件扩展到最大(专用)硬件,64核,raid 10中的12 Nvm-e驱动器,384Gb ram等)。

我正在考虑按每个租户执行此操作(1个租户= 1个数据库连接配置/池),因为这是一种“简单”且有效的方式,可以在number-of-tenants不进行应用程序代码更改的情况下获得多达两倍的容量。

现在,我正在Rails 4.2 atm上运行,不久将升级到5.2。我可以看到Rails 6增加了对每个模型的连接定义的支持,但这并不是我真正需要的,因为我为20个租户中的每一个都有一个完全镜像的数据库架构。通常,我会针对每个请求(在中间件中)或每个后台作业(sidekiq中间件)切换“数据库”,但是目前这对于公寓的gem来说是微不足道的,因为它只是search_path在Postgresql中进行设置,而实际上并没有改变实际的连接。当切换到每租户托管策略时,我将需要根据请求切换整个连接。

问题:

  1. 我知道我可以完成ActiveRecord::Base.establish_connection(config)每个请求/后台工作-但是,正如我也了解的那样,这将触发进行全新的数据库连接握手,并在Rails中产生一个新的数据库池-对吗?我猜想对我的应用程序的每个请求都会产生这种开销,这会降低性能。
  2. 因此,我想知道是否有人可以从一开始就预建立多个(总共20个)数据库连接/池(例如,在应用程序启动时),然后根据每个请求在这些池之间进行切换?这样他的数据库连接已经建立并准备使用。
  3. 所有这些仅仅是一个糟糕的主意,我应该寻找一种不同的方法吗?例如1个应用实例=与一个特定租户的特定连接。或者是其他东西。


1
您可能对Rails的GitHub存储库中的此PR感兴趣,该存储库最近向当前的Rails master分支中完全添加了所需的功能。是将Rails Egde作为您当前的Rails版本的选项还是将其反向试用?
spickermann

@spickermann ActiveRecord::Base.connected_to(shard: :shard_one) do ... end意味着将(重新)使用该池,而不是每次都创建一个全新的连接?

Answers:


4

据我了解,多租户应用程序有4种模式:

1.专用模型/多种生产环境

每个实例或数据库实例完全托管不同的租户应用程序,并且租户之间不共享任何内容。

这是1个实例应用程序和1个租户的1个数据库。就像您仅服务一名租户一样,开发将很容易。但是如果您有100个租户,这将是开发人员的噩梦。

2.房客的物理隔离

所有租户都有1个实例应用程序,但1个租户有1个数据库。这就是您要搜索的。您可以使用ActiveRecord::Base.establish_connection(config),或使用gems,或按照其他建议更新到Rails 6。请参阅下面的(2)答案。

3.隔离的模式模型/逻辑隔离

在隔离模式中,租户表或数据库组件在逻辑模式或名称空间下分组,并与其他租户模式分开,但是该模式托管在同一数据库实例中。

1个实例应用程序和1个数据库用于所有租户,就像您使用公寓gem一样。

4.部分隔离的组件

在此模型中,具有共同功能的组件在租户之间共享,而具有唯一或不相关功能的组件则被隔离。在数据层,公用数据(例如标识租户的数据)被分组或保存在单个表中,而租户特定的数据在表或实例层被隔离。


至于(1),ActiveRecord::Base.establish_connection(config)如果正确使用它,则不要与每个请求握手到db。您可以在此处查看在此处阅读所有评论

至于(2),如果不想使用establish_connection,可以使用gem multiverse(适用于rails 4.2)或其他gem。或者,如其他建议,您可以更新到Rails 6。

编辑:Multiverse gem正在使用establish_connection。它将附加database.yml,并创建一个基类,以便每个子类共享相同的连接/池。基本上,它减少了我们的使用工作量establish_connection

至于(3),答案是:

如果您没有那么多租户,并且您的应用程序很复杂,建议您使用专用模型模式。因此,您需要一个应用程序实例=一个与特定租户的特定连接。您不必通过添加多个数据库连接来使您的应用程序更复杂。

但是,如果您有很多租户,建议您根据业务流程使用租户的物理隔离或部分隔离的组件。

无论哪种方式,您都必须更新/重写您的应用程序以符合新的体系结构。


嗨,谢谢你的回答。如果他们是好的解决方案,我将需要一些时间来实际测试该建议,然后才能奖励其中一个赏金。
Niels Kristian

关于1和2,我有两个问题。1:我不确定我是否理解您的参考。您是在说什么,我可以在不进行数据库握手/重新创建数据库轮询的情况下调用。Establishment_connection(config)吗?在这种情况下,我不确定这两个链接是如何解释的?2:对于multiverse,不是整个应用程序都按模型切换数据库,而不是整个数据库切换吗?我觉得他们的文件含糊不清
Niels Kristian

我想我有误会。您介意详细说明这些句子吗?我明白,我可以每次请求/后台作业做一个ActiveRecord :: Base.establish_connection(配置) -不过,我也明白,触发器进行一个全新的数据库连接握手和一个新的数据库池产卵在轨它建议一个请求创建一个数据库池?
KSD Putra

我的意思是:(1)我不得不为每次请求都调用ActiveRecord :: Base。Establishment_connection(config)而在不同数据库/国家之间切换时担心性能/网络开销
Niels Kristian

您不必担心开销。现在,如果使用单个数据库,则只有一个连接池(您可以在上面的(1)的答案中检查有关连接的链接)。如果establish_connection在这样的模型中使用:class SecondTenantUser < ActiveRecord::Base; establish_connection(DB_SECOND_TENANT); end,并且说您有5个模型,则将创建5个到DB_SECOND_TENANT的连接池。每个池都受到同等对待。因此,您不会为每个请求创建一个池,而是为每个创建一个池establish_connection
KSD Putra


3

就在几天前,水平分片已添加到masterGitHub上的Ruby on Rails的分支中。当前,此功能尚未正式发布,但是根据您应用程序的Rails版本,您可能需要考虑将Rails master添加到您的应用程序中Gemfile

gem "rails", github: "rails/rails", branch: "master"

使用此新功能,您可以利用Rails的数据库连接池并根据条件切换数据库。

我没有使用这个新功能,但是看起来很简单:

# in your config/database.yml
production:
  primary:
    database: my_database
    # other config: user, password, etc
  primary_tenant_1:
    database: tenant_1_database
    # other config: user, password, etc

# in your controller for example when updating a tenant
ActiveRecord::Base.connected_to(shard: "primary_tenant_#{tenant.database_shard_number}") do
  tenant.save
end

您没有添加太多有关如何确定租户编号或如何在应用程序中进行授权的详细信息。但我会尝试尽快确定租户号码中application_controller的一个around_action。这样的事情可能是一个起点:

around_filter :determine_database_connection

private

def determine_database_connection
  # assuming you have a method to determine the current_tenant and that tenant
  # has a method that returns the number of the shard to use or even the 
  # full shard identifier
  shard = current_tenant.database_shard # returns for example `:primary_tenant_1` 

  ActiveRecord::Base.connected_to(shard: shard) do
    yield
  end
end

在这种情况下,切换回默认连接也有同样的意义吗?github.com/influitive/apartment#middleware-considerations
Ben

1
一旦离开该ActiveRecord::Base.connected_to ... do块,它将再次使用默认连接。
spickermann

@spickermann我正在读这本绝品,不仅是rails6吗?
7urkm3n

@ 7urkm3n它包含在当前的Rails master分支中。
spickermann

嗨,谢谢你的回答。如果他们是好的解决方案,我将需要一些时间来实际测试该建议,然后才能奖励其中一个赏金。
Niels Kristian
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.