【问题标题】:Multi-tenant Rails app with Postgresql and Unicorn带有 Postgresql 和 Unicorn 的多租户 Rails 应用程序
【发布时间】:2014-05-18 21:27:14
【问题描述】:

我正在构建一个应用程序,其中每个公司都有自己的私有模式 (Postgresql)。

对于每个申请,我在 before_action 中设置 Postgres 搜索路径,如下所示:

ActiveRecord::Base.connection.schema_search_path = 'company_id, public'

我的疑问是,如果我有多个独角兽工人,并且一个工人“A”设置了路径,而另一个工作“B”在工人 A 完成之前设置了路径,我认为这会产生一些冲突和“A”工人可能会意外地从错误的架构中保存/读取模型,对吧?

还有其他解决方案可以更好地配合 Unicorn 设计吗?

编辑,架构详情:

每家公司都有很多用户。用户和公司表都存在于公共模式中,其余的(产品、客户...)存在于私有模式中

编辑,更多研究:

经过一番研究,我发现每个数据库客户端连接都有自己的搜索路径。因此,如果我使用一个连接更改搜索路径,其他连接不会受到影响,因此这可以与 Unicorn 一起使用,因为每个请求都有自己的连接,但它不适用于 Puma 等多线程服务器。

但是,答案中仍然存在一些问题,例如 ActiveRecord 为每个请求重新加载架构。我想听听有人在生产中使用这种方法的经验。

【问题讨论】:

  • 每个唯一的公司都有自己的登录名,还是所有公司共享相同的登录名?另外,这些公司是如何访问数据库的?
  • 每个公司都有很多用户。用户和公司表都存在于公共模式中,其余的则存在于私有模式中。
  • 嗯,我对 Rails 的了解还不够,我确实发现这只会加强你的怀疑:groups.google.com/forum/#!topic/rails-oceania/tvzC85huXEA
  • @Jirico,我已经在生产环境中解决了这个问题,并且我提供了一种替代方法(您的问题提出了这个问题),同时深入了解了为什么在没有 AR 解析模式的情况下动态更改模式路径会造成类型处理的风险。仅仅因为您想要/首选的方法存在实际问题/风险和问题,并不会使答案或建议无效。我已邀请您自己检查适配器代码并解释了您所面临问题的详细信息。
  • 我同意,到目前为止,您的回答是最完整的。我将在生产中尝试我的方法,我将继续更新这个线程关于我的结果。感谢您的帮助。干杯

标签: ruby-on-rails postgresql activerecord rails-activerecord unicorn


【解决方案1】:

我不认为多个模式是一个好主意,因为您的 ORM 将需要在每个请求上重新加载其模式,除非您计划为每个租户运行一个服务器实例...

从我所读的内容来看,多个模式在 postres 中是不可扩展的。如果您有数以万计的租户,您将开始遇到性能问题。

我使用的方法是在每个表中都有一个tenant_id,并且只在模型上使用一个范围并进行一些验证检查,以确保相关模型在所需的租户或用户范围内。它真的很简单,效果很好。

我使用 request_store 来设置 User.currentTenant.current 来自基本控制器,以便我在模型中拥有所需的上下文,以在需要时限制和强制执行租户或用户范围。我在另一个堆栈溢出问题here 中发布了一个示例。

我发现在我的多租户应用程序中,并非所有内容都与租户隔离,我需要共享一些表,因此我很快就放弃了多模式解决方案,再加上每个请求的模式重新加载问题,并且能够轻松创建新租户,因为普通模型保存使多个模式无法启动。

假设您设法避免 AR 中的架构不一致问题,您还需要考虑实时流式传输、SSE 或 websocket 被排除在外,或者使用您的方法变得异常困难,因为您不能让线程在不同的租户中运行,而 Unicorn 也没有支持长时间运行的请求。

您可能希望改为考虑使用基于 EventMachine 的瘦服务器和可能的机架光纤池,这样您就可以支持实时流媒体或 SSE 的货币、慢速客户端和长时间运行的请求,并且没有与线程相关的问题,但具有出色的可扩展性.使用光纤方法,您需要弄清楚如何在光纤恢复时切换/恢复模式上下文,但原则上它是可行的。

【讨论】:

  • 您能否更好地解释一下为什么更改 postgresql 搜索路径会导致我的 ORM 每次请求都重新加载其架构?
  • orm 需要询问数据库模式,以便发现模型的表和列。每个请求都需要切换架构,并且您将获得活动记录以解析每个请求的所有架构元数据。我什至不知道如何在没有一些默认架构和用户表的情况下对用户进行身份验证,甚至不知道您应该使用您的方法切换到哪个租户特定架构。总而言之,将涉及大量架构切换开销
  • 要明确 AR 适配器代码在打开与数据库的连接时加载类型和列定义。但是模式是表、视图、函数、类型和约束的容器,并且可能对相同的类型名称使用不同的 oid,因此您正在玩一个非常危险的游戏,并且应该在更改模式路径时关闭和重新打开连接。您的迁移策略是什么?您必须独立迁移每个租户架构,我认为 Rails 迁移不会为您工作。
  • 我建议查看 AR postgres 适配器代码(我做过)。 AR 缓存表/列信息和类型信息。每个模式中具有相同名称的类型的 oid 可以不同,因为每个模式都是独立的。保证正确行为的唯一方法是在租户/架构更改时关闭并重新打开,以便 AR 正确理解您为该租户使用的架构。与我的租户相比,您的迁移方法听起来非常低效
  • 除了 AR 和 Rails,你真的应该阅读this Microsoft article,它讨论了多租户架构的优缺点。基本上,从长远来看,共享设计成本最低,但前期开发成本更高。
【解决方案2】:

我认为这不是一个可行的解决方案。这就是文档所说的:

schema_search_path=(schema_csv) public 将模式搜索路径设置为 一串以逗号分隔的模式名称。以 $ 开头的名字有 被引用(例如 $user => ‘$user’)。看: http://www.postgresql.org/docs/current/static/ddl-schemas.html

这不应该手动调用,而是在database.yml中设置。

这就是实现

    def schema_search_path=(schema_csv)
      if schema_csv
        execute("SET search_path TO #{schema_csv}", 'SCHEMA')
        @schema_search_path = schema_csv
      end
    end

对于您的用例来说,它看起来太全球化了。

【讨论】:

  • 很好的信息。现在我怀疑 postgres 搜索路径是否有每个数据库连接的实例。如果有,我认为它可能会起作用,因为如果我没记错的话,每个独角兽工人都有自己的一组连接。如果我说的是对的,每个工人都可以拥有自己的 postgres 搜索路径实例。
【解决方案3】:

我不能直接回答你的问题,但你可以看看acts_as_tenant gem。

这是一个使用示例: https://github.com/Bahanix/RubyBB/blob/master/app/controllers/application_controller.rb#L15-L25

【讨论】:

  • 这与@Andrew Hacking 建议的方法相同,使用租户列并将所有内容放在同一个表中。
  • 我愤怒地使用了 ActsAsTenant gem,但是当您共享数据或其他关于应该可见的访问规则时,它无法正常工作。即“共享/公共”或“仅限租户”或租户可以是可选的。
【解决方案4】:

我制作了一个名为 acts_as_restricted_subdomain 的 gem,它实现了上述单一模式策略。我们已经在 Unicorn 和 Resque 的生产中成功使用它大约 4 年了,以将我们所有客户的数据彼此分离,而不会溢出。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-16
    • 1970-01-01
    • 2012-06-22
    • 2018-08-29
    • 2020-12-03
    • 1970-01-01
    • 2012-12-19
    • 2017-11-15
    相关资源
    最近更新 更多