可不可以在rails多个数据库连接池之间切换?
Would it be possible to have multiple database connection pools in rails to switch between?
一点背景知识
多年来,我一直将 Apartment gem 用于 运行 多租户应用程序。现在最近需要将数据库扩展到单独的主机中,数据库服务器根本无法跟上(读取和写入都变得太多) - 是的,我将硬件扩展到最大(专用硬件、64 核、RAID 10 中的 12 个 Nvm-e 驱动器、384Gb 内存等)。
我正在考虑对每个租户执行此操作(1 个租户 = 1 个数据库连接配置/池),因为这将是 "simple" 的一种有效方式,可以使容量增加 number-of-tenants
倍无需进行大量应用程序代码更改。
现在,我运行 rails 4.2 atm.,即将升级到 5.2。我可以看到 rails 6 添加了对每个模型连接定义的支持,但这并不是我真正需要的,因为我的 20 个租户中的每个租户都有一个完全镜像的数据库模式。通常我会根据请求(在中间件中)或每个后台作业(sidekiq 中间件)切换 "database",但是目前这是微不足道的,并且在 Apartment gem 中处理,因为它只是在 search_path
中设置Postgresql 并没有真正改变实际的连接。当切换到每租户托管策略时,我需要根据请求切换整个连接。
问题:
- 我知道我可以为每个请求/后台作业执行
ActiveRecord::Base.establish_connection(config)
- 但是,据我所知,这会触发一个全新的数据库连接握手并在 rails - 对吧?我想如果对我的应用程序的每个请求都产生这种开销,那将是一种性能自杀。
- 因此我想知道是否有人可以看到带有 rails 的选项,例如从一开始就预先建立多个(总共 20 个)数据库 connections/pools(例如在应用程序启动时),然后根据请求在这些池之间切换?这样他的数据库连接就已经建立并可以使用了。
- 这一切只是一个糟糕的想法吗,我应该寻找不同的方法吗?例如。 1 个应用程序实例 = 到一个特定租户的一个特定连接。或者是其他东西。
据我了解,(2) 应该可以在 Rails 中使用 manual connection switching 6.
据我了解,多租户应用程序有 4 种模式:
1.专用 model/Multiple 生产环境
每个实例或数据库实例完全托管不同的租户应用程序,租户之间不共享任何内容。
这是 1 个租户的 1 个实例应用程序和 1 个数据库。开发会很容易,就好像您只为 1 个租户服务一样。但是,如果你有 100 个租户,这对 devops 来说将是一场噩梦。
2。租户的物理隔离
所有租户的 1 个实例应用程序,但 1 个租户的 1 个数据库。这就是您正在寻找的。您可以使用 ActiveRecord::Base.establish_connection(config)
,或使用 gems,或按照其他建议更新到 Rails 6。请参阅下面 (2) 的答案。
3。隔离架构 model/Logical 隔离
在独立架构中,租户 table 或数据库组件在逻辑架构或名称-space 下分组并与其他租户架构分开,但是架构托管在同一个数据库实例。
所有租户的 1 个实例应用程序和 1 个数据库,就像您对公寓所做的那样 gem。
4。部分隔离组件
在此模型中,具有共同功能的组件在租户之间共享,而具有独特或不相关功能的组件被隔离。在数据层,识别租户的数据等公共数据被分组或保存在单个 table 中,而租户特定数据在 table 或实例层被隔离。
至于 (1),ActiveRecord::Base.establish_connection(config)
如果您正确使用它,则不会根据请求与 db 握手。您可以查看 here and read all the comment here.
至于(2),如果你不想用establish_connection
,你可以用gem multiverse(它适用于rails 4.2),或其他 gems。或者,按照其他人的建议,您可以更新到 Rails 6.
编辑:Multiverse gem 正在使用 establish_connection
。它将附加 database.yml
,并创建一个基础 class,以便每个子 class 共享相同的 connection/pool。基本上,它减少了我们使用 establish_connection
.
的努力
关于(3),答案:
如果你没有那么多租户,而且你的应用程序很复杂,我建议你使用专用模型模式。因此,您需要 1 个应用程序实例 = 与一个特定租户的一个特定连接。您不必通过添加多个数据库连接来使您的应用程序变得更复杂。
但是如果你有很多租户,我建议你使用租户物理隔离或部分隔离组件取决于你的业务流程。
无论哪种方式,您都必须 update/rewrite 您的应用程序符合新架构。
就在几天前,horizontal sharding 被添加到 Rails 上的 Ruby' GitHub 上的 master
分支。目前,此功能尚未正式发布,但根据您应用程序的 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
一点背景知识
多年来,我一直将 Apartment gem 用于 运行 多租户应用程序。现在最近需要将数据库扩展到单独的主机中,数据库服务器根本无法跟上(读取和写入都变得太多) - 是的,我将硬件扩展到最大(专用硬件、64 核、RAID 10 中的 12 个 Nvm-e 驱动器、384Gb 内存等)。
我正在考虑对每个租户执行此操作(1 个租户 = 1 个数据库连接配置/池),因为这将是 "simple" 的一种有效方式,可以使容量增加 number-of-tenants
倍无需进行大量应用程序代码更改。
现在,我运行 rails 4.2 atm.,即将升级到 5.2。我可以看到 rails 6 添加了对每个模型连接定义的支持,但这并不是我真正需要的,因为我的 20 个租户中的每个租户都有一个完全镜像的数据库模式。通常我会根据请求(在中间件中)或每个后台作业(sidekiq 中间件)切换 "database",但是目前这是微不足道的,并且在 Apartment gem 中处理,因为它只是在 search_path
中设置Postgresql 并没有真正改变实际的连接。当切换到每租户托管策略时,我需要根据请求切换整个连接。
问题:
- 我知道我可以为每个请求/后台作业执行
ActiveRecord::Base.establish_connection(config)
- 但是,据我所知,这会触发一个全新的数据库连接握手并在 rails - 对吧?我想如果对我的应用程序的每个请求都产生这种开销,那将是一种性能自杀。 - 因此我想知道是否有人可以看到带有 rails 的选项,例如从一开始就预先建立多个(总共 20 个)数据库 connections/pools(例如在应用程序启动时),然后根据请求在这些池之间切换?这样他的数据库连接就已经建立并可以使用了。
- 这一切只是一个糟糕的想法吗,我应该寻找不同的方法吗?例如。 1 个应用程序实例 = 到一个特定租户的一个特定连接。或者是其他东西。
据我了解,(2) 应该可以在 Rails 中使用 manual connection switching 6.
据我了解,多租户应用程序有 4 种模式:
1.专用 model/Multiple 生产环境
每个实例或数据库实例完全托管不同的租户应用程序,租户之间不共享任何内容。
这是 1 个租户的 1 个实例应用程序和 1 个数据库。开发会很容易,就好像您只为 1 个租户服务一样。但是,如果你有 100 个租户,这对 devops 来说将是一场噩梦。
2。租户的物理隔离
所有租户的 1 个实例应用程序,但 1 个租户的 1 个数据库。这就是您正在寻找的。您可以使用 ActiveRecord::Base.establish_connection(config)
,或使用 gems,或按照其他建议更新到 Rails 6。请参阅下面 (2) 的答案。
3。隔离架构 model/Logical 隔离
在独立架构中,租户 table 或数据库组件在逻辑架构或名称-space 下分组并与其他租户架构分开,但是架构托管在同一个数据库实例。
所有租户的 1 个实例应用程序和 1 个数据库,就像您对公寓所做的那样 gem。
4。部分隔离组件
在此模型中,具有共同功能的组件在租户之间共享,而具有独特或不相关功能的组件被隔离。在数据层,识别租户的数据等公共数据被分组或保存在单个 table 中,而租户特定数据在 table 或实例层被隔离。
至于 (1),ActiveRecord::Base.establish_connection(config)
如果您正确使用它,则不会根据请求与 db 握手。您可以查看 here and read all the comment here.
至于(2),如果你不想用establish_connection
,你可以用gem multiverse(它适用于rails 4.2),或其他 gems。或者,按照其他人的建议,您可以更新到 Rails 6.
编辑:Multiverse gem 正在使用 establish_connection
。它将附加 database.yml
,并创建一个基础 class,以便每个子 class 共享相同的 connection/pool。基本上,它减少了我们使用 establish_connection
.
关于(3),答案:
如果你没有那么多租户,而且你的应用程序很复杂,我建议你使用专用模型模式。因此,您需要 1 个应用程序实例 = 与一个特定租户的一个特定连接。您不必通过添加多个数据库连接来使您的应用程序变得更复杂。
但是如果你有很多租户,我建议你使用租户物理隔离或部分隔离组件取决于你的业务流程。
无论哪种方式,您都必须 update/rewrite 您的应用程序符合新架构。
就在几天前,horizontal sharding 被添加到 Rails 上的 Ruby' GitHub 上的 master
分支。目前,此功能尚未正式发布,但根据您应用程序的 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