切り替えるために複数のデータベース接続プールをレールに置くことは可能でしょうか?


12

少し背景

私は何年もマルチテナントアプリを実行するためにApartment gemを使用しています。最近、データベースを別のホストにスケールアウトする必要が出てきました。dbサーバーはこれ以上追いつくことができません(読み取りと書き込みの両方が多すぎる)-そしてはい、ハードウェアを最大にスケールしました(専用)ハードウェア、64コア、RAID 10の12個のNvm-eドライブ、384Gb RAMなど)。

これはnumber-of-tenants、アプリケーションコードを変更することなく、最大で倍の容量を実現するための「シンプル」で効率的な方法であるため、このテナントごと(1テナント= 1データベース接続構成/プール)を使用することを検討していました。

現在、私はレール4.2気圧を実行していますが、すぐに5.2にアップグレードします。rails 6はモデルごとの接続定義のサポートを追加していることがわかりますが、20のテナントごとに完全にミラーリングされたデータベーススキーマがあるため、実際には必要ありません。通常、リクエストごと(ミドルウェア内)またはバックグラウンドジョブごと(sidekiqミドルウェア)に「データベース」を切り替えますがsearch_path、Postgresqlでを設定するだけで実際の接続は実際には変更しないので、これは現在のところ簡単で、Apartment gemで処理されます。テナントごとのホスティング戦略に切り替える場合、リクエストごとに接続全体を切り替える必要があります。

質問:

  1. ActiveRecord::Base.establish_connection(config)はリクエストごと/バックグラウンドジョブを実行できることを理解しています-しかし、私も理解しているように、まったく新しいデータベース接続のハンドシェイクが作成され、新しいDBプールがRailsで生成されます-正しいですか?これは、アプリケーションへのすべてのリクエストでこの種のオーバーヘッドを発生させるパフォーマンス自殺になると思います。
  2. したがって、たとえば複数(合計20)のデータベース接続/プールを最初から(たとえば、アプリケーションの起動時に)事前に確立するレールのオプションが表示され、リクエストごとにそれらのプールを切り替えることができるかどうか疑問に思いますか?そのため、彼はdb接続がすでに作成され、使用できるようになっています。
  3. これらはすべて単に貧しい貧しいアイデアですか?代わりに別のアプローチを探す必要がありますか?たとえば、1つのアプリインスタンス= 1つの特定のテナントへの1つの特定の接続。または、他の何か。


1
現在のRails ブランチに必要な機能を最近追加したRailsのGitHubリポジトリのこのPRに興味があるかもしれませんmaster。Rails Egdeを実行することはオプションですか、それとも現在のRailsバージョンにその機能をバックプリントしますか?
スピッカーマン

@spickermann ActiveRecord::Base.connected_to(shard: :shard_one) do ... endは、毎回まったく新しい接続を作成する代わりに、プールが(再)使用されることを意味しますか?
ベン

回答:


4

私が理解しているように、マルチテナンシーアプリには4つのパターンがあります。

1.専用モデル/複数の生産環境

各インスタンスまたはデータベースインスタンスは、異なるテナントアプリケーションを完全にホストし、テナント間で共有されるものはありません。

これは、1つのテナントに対して1つのインスタンスアプリと1つのデータベースです。1つのテナントのみを提供するかのように、開発は簡単です。しかし、たとえば100人のテナントがある場合、devopsにとって悪夢になります。

2.テナントの物理的な分離

すべてのテナントに1つのインスタンスアプリ、1つのテナントに1つのデータベース。これがあなたが探しているものです。ActiveRecord::Base.establish_connection(config)他の提案どおりに、またはgemsを使用するか、Rails 6に更新できます。以下の(2)の回答を参照してください。

3.分離スキーマモデル/論理分離

分離スキーマでは、テナントテーブルまたはデータベースコンポーネントは論理スキーマまたは名前空間の下にグループ化され、他のテナントスキーマから分離されますが、スキーマは同じデータベースインスタンスでホストされます。

アパートの宝石と同じように、すべてのテナントに対して1つのインスタンスアプリと1つのデータベース。

4.部分的に分離されたコンポーネント

このモデルでは、共通の機能を持つコンポーネントはテナント間で共有され、固有の機能または関連しない機能を持つコンポーネントは分離されます。データレイヤーでは、テナントを識別するデータなどの一般的なデータが1つのテーブルにグループ化または保持され、テナント固有のデータはテーブルまたはインスタンスレイヤーで分離されます。


(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)に関しては、答え:

テナントがあまりなく、アプリケーションがかなり複雑な場合は、専用モデルパターンを使用することをお勧めします。したがって、1つのアプリインスタンス= 1つの特定のテナントへの1つの特定の接続を使用します。複数のデータベース接続を追加して、アプリをより複雑にする必要はありません。

ただし、テナントが多数ある場合は、ビジネスプロセスに応じて、テナントの物理的な分離または部分的に分離されたコンポーネントを使用することをお勧めします。

どちらの方法でも、新しいアーキテクチャに準拠するようにアプリケーションを更新/書き換えする必要があります。


回答ありがとうございます。賞金が良い解決策である場合、賞金の答えの1つに報酬を与える前に、提案を実際にテストするために少し時間が必要です。
Niels Kristian

1と2に関していくつか質問があります。1:あなたの参考文献を理解できているかどうかわかりません。あなたが言っていることは、私が.establish_connection(config)を呼び出すことができるのは、dbハンドシェイク/ dbポーリングを再作成することなく?その場合、2つのリンクがそれをどのように説明するのかわかりませんか?2:マルチバースについては、アプリ全体のdbスイッチ全体ではなく、モデルごとのデータベースの切り替えではありませんか?彼らのドキュメントはかなり曖昧だと思います
Niels Kristian

誤解していると思います。これらの文章を詳しく説明してもよろしいですか?リクエスト/バックグラウンドジョブごとにActiveRecord :: Base.establish_connection(config)を実行できることを理解しています。ただし、これも理解しているように、まったく新しいデータベース接続ハンドシェイクが作成され、新しいDBプールがRails生成されます。 1つのリクエストで1つのdbプールを作成することを提案しますか?
KSDプトラ

つまり、(1)リクエストごとにActiveRecord :: Base.establish_connection(config)を呼び出す必要があるときに、パフォーマンス/ネットワークのオーバーヘッドが心配です。異なるデータベース/国を切り替えるだけです
Niels Kristian

オーバーヘッドを心配する必要はありません。これで、単一のDBを使用する場合、接続プールは1つになります(上記の(1)の回答で接続に関するリンクを確認できます)。次のestablish_connectionようなモデルで使用する場合class SecondTenantUser < ActiveRecord::Base; establish_connection(DB_SECOND_TENANT); end、5つのモデルがあるとすると、DB_SECOND_TENANTへの5つの接続プールが作成されます。そして、各プールは平等に扱われます。したがって、リクエストごとではなく、ごとにプールを作成しますestablish_connection
KSDプトラ

3

私が理解していることから、(2)Rails 6の手動接続切り替えで可能になるはずです。


おかげで、これは私のユースケースからかなり遠いようです。これは、この手順をどこでも使用できるようにアプリ全体を書き換えることを意味します。
Niels Kristian

3

ほんの数日前、GitHubのRuby on Railsのブランチに水平分割が追加されましたmaster。現在、この機能は正式にはリリースされていませんが、アプリケーションのRailsバージョンmasterによっては、これをに追加してRailsの使用を検討したい場合があります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私はこの宝石を読んでいました、rails6だけではありませんか?
7urkm3n

@ 7urkm3n現在のRails masterブランチに含まれています。
スピッカーマン

回答ありがとうございます。賞金が良い解決策である場合、賞金の答えの1つに報酬を与える前に、提案を実際にテストするために少し時間が必要です。
Niels Kristian
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.