web-dev-qa-db-ja.com

マルチテナントアプリケーションで共通データを処理する方法

テナントごとに個別のDBインスタンスに同じデータベーススキーマを使用するマルチテナントアプリケーションを設計しています(つまり、新しい顧客を獲得するたびに、それらの顧客用に新しいデータベースを作成します)。このアプローチは、テナントごとにデータをフィルタリングする必要がないという点で、単純化のために望ましいです。たとえば、dbo.Users内のすべてのUserを選択するだけです。たとえば、CustomerIdでフィルタリングする必要はありません/忘れないでください。間違ったデータを間違った顧客に誤って公開します。

allデータベースに共通するデータをどのように処理するのかと思います。郵便番号(郵便番号)が良い例です。各データベースでこのデータを複製する必要があるようです(これは維持するのが悪夢であり、複製されたデータのloadsを格納していることを意味します)。 [〜#〜] or [〜#〜]いくつかの一般的なデータベースを使用する必要があるため、自然な方法でテーブルを結合したり、たとえばEntityFrameworkをそのまま使用できます。これらのどちらも、他のものよりも正しい/良い音はありません。

誰かがこれのための良い戦略を持っていますか?

3
thisextendsthat

いくつかの一般的なデータベースを使用する必要があるため、自然な方法でテーブルを結合したり、すぐに使えるEntityFramework。

これには EntityFramework.DynamicFilters を使用できます。 allクエリ(直接クエリと関連エンティティの読み込みの両方)に適用される動的フィルターをモデルに配置できます。

たとえば、動的削除されたアイテムをフィルター処理するためにDynamicFiltersを使用しています。 DeletedOn != nullは見えなくなります。

modelBuilder.Filter("IsDeleted", (BaseEntity d) => d.DeletedOn, null);

次のような類似のアプローチを使用できます。

modelBuilder.Filter("CustomerId", (User u) => u.CustomerId, GetCurrentCustomerId());

これにより、ほとんどの場合、純粋に顧客データを分離するためにマルチテナンシーを使用するというさらなる推論が無効になります。


ただし、マルチテナントプラットフォームを使用する必要が生じる可能性がある他の考慮事項があります。

テナントごとに個別のDBインスタンスに同じデータベーススキーマを使用するマルチテナントアプリケーションを設計しています(つまり、新しい顧客を獲得するたびに、それらの顧客用に新しいデータベースを作成します)。

  • 顧客がいて、データベーススキーマをアップグレードするとどうなりますか。全面的にアップグレードを実施しますか、それとも顧客がいつでも/したいときにアップグレードできるようにしますか?

  • ある顧客がデータの復元を要求するとどうなりますか?その顧客のデータのみを復元できるようにしますか? (私はそう思います-私はこれを指摘したかっただけです)。

どちらのケースもマルチテナンシーを使用する際の強みになる可能性があります。顧客は自分のペースでアップグレードでき、他の顧客に影響を与えることなくデータの復元を受信できます。

ただし、別の問題が発生します:異なるアプリケーションバージョン間の共通データ。アップグレード中に一般的なデータが変更されると、問題が発生します。

バージョン間で変更される傾向がないため、郵便番号の例はここではあまり当てはまりません。しかし、一般的なポイントはまだ残っています:いくつかの一般的なデータは実際にバージョン間で変更される可能性があります。

ここには2つの解決策があります。両方について簡単に説明します。


1。共通データのホスティング

共通データの集中データベースを維持できますが、これをサービスの背後に隠すことをお勧めします。これにより、versioned共通データを簡単に返すことができます。すべてのテナントが現在のバージョン(v1.3など)を通知し、サービスはバージョン1.3の共通データを返すことを保証します。

これにより、必要な分離が得られますが、ここではいくつかの問題があります。Webサービスを作成するにはオーバーヘッドがかかり、事実上、常に依存しなければならない外部依存関係です。

この方法は、バージョン固有ではなく「グローバルに正しい」と見なされる一般的なデータ(郵便番号のリストなど)に適しています。
しかし、集中化されたリポジトリに置くことを保証するために、妥当なデータサイズが必要です。 5つのフィールドの場合、サービスを作成するオーバーヘッドは、すべてのテナントでこれらの5行をコピーするデータフットプリントをはるかに上回ります。


2。テナントへの共通データの読み込み

データが大きすぎてすべてのテナントに個別に含めることが問題にならない限り、バージョン固有の一般的なデータにはこのアプローチをお勧めします。

つまり、(たとえば) EFのデータベースシード を使用してこれを実現できます。これにより、データベーススキーマを新しいバージョンにアップグレードすると同時に、共通データを更新できます。

これを達成するには多くの方法があります。データベースシーディングは、スキーマのアップグレードプロセスとうまく連携しているので、気に入っています。


データを一元化したい理由を理解しました。共有データですね。しかし、ここには合理性の線があり、every抽象化は必要ありません。

単純化しすぎた例として、すべてのエンティティに監査フィールド(CreatedOnModifiedOn、...)がある場合、これをIAuditableまたはAuditableEntity。それは良い習慣です
ただし、3つのエンティティ(PersonCountryおよびStuffedAnimal)があり、それらすべてにNameプロパティがある場合、つまり、これをINamedまたはNamedEntityに抽象化する必要があります。これはもはや合理的な議論ではありません。

ここでも同じことが起こっています。理論を手紙に適用する場合、共有データを一元化されたポイントに抽象化する必要があります。ただし、同じロジックによって、マルチテナンシーを使用するべきではありません。それらは共有データベーススキーマを持っているからですよね?

ここで文字に理論を適用するのではなく、代わりに実用的なアプリケーションを検討してください。テナントは、他のテナントから独立して実行するために特別に作成されます。そうすることにはいくつかの利点がありますが、「純粋な抽象化」はそれらの1つではありません。どちらかと言えば、マルチテナンシーはリソースを抽象化または共有することを拒否することであり、問​​題がすべてのyoru顧客のグローバルな問題になるのを防ぐことができます。

共通データの更新を常にすべてのテナントに伝達する必要がある場合は、オプション1の方が適しています。

テナントの共通データをバージョン管理できるようにしたい場合は、データをテナント自体の内部でローカルに保持することをお勧めします。

3
Flater

...新しい顧客を獲得するたびに、私は彼らのために新しいデータベースを作成します。このアプローチは、テナントごとにデータをフィルター処理する必要がないという点で単純であるため、望ましいものです。間違ったデータを誤って間違った顧客に公開することを忘れてしまう危険はありません。

本当に、私がお勧めするのは、このアプローチを再考することです。スキーマによってテナントを分離すると分離が提供され、ほとんどのRDBMSはそれを処理できますが、その過程で他の頭痛の種を生み出します。スキーマ全体で同じものが何度もラバースタンプされたときに消去される大規模なデータベースでは、スペースとパフォーマンスの点で多くの経済的メリットがあります。また、多数のデータベースのバックアップを処理する方法や、5,758番目が失敗したときに10,000のテナントデータベースへの変更をロールバックする方法など、ロジスティクス上の課題もあります。

行レベルのセキュリティ(RLS)を備えたデータベースを使用している場合、すべてのテナントと共通データを1つの屋根の下に維持しながら、マルチテナントの問題を非常に簡単に解決できます。 RLSでは、テーブルに影響を与えるクエリに行が含まれるかどうかを決定するテーブルごとのポリシーを設定できます。 SELECTUPDATE、およびDELETEクエリの場合、これはWHERE句に追加の制約を効果的に強制し、データベースがポリシーに適合しないかのようにデータベースを動作させます。存在しません。 INSERTでも同様の処理が行われ、通常、ポリシーに一致しない行を追加するクエリは拒否されます。

RLSのないデータベースでは、データを保持しているテーブルに対するテナントのアクセス許可を拒否し、ビューを介してアクセスするように強制できます。最新のRDBMSには更新可能なビューがあります。つまり、テーブルのように動作させることができます。

クライアントに ストアドプロシージャを介してデータベースにアクセスする を要求するなど、他の方法もあり、他のテナントのデータをフィルターするのを忘れる問題をスペースシフトするだけです。

これらの代替手段がどれも機能せず、引き続き別のスキーマを実行する場合は、データベースが外部データソースの概念をサポートしているかどうかを確認してください。これにより、外部テーブルをローカルであるかのように扱うことができ、共通データに対して検索しようとしているすべての参照整合性機能が有効になります。

2
Blrfl

郵便番号などの一般的なデータには共有データベースを使用します。 Entity Frameworkを使用してデータベース間でテーブルを結合することはできませんが、DbContextクラスが大きい場合は、(パフォーマンスの問題により)いずれにせよ、それは選択肢にならなかった可能性があります。 LINQを使用してオンザフライで参加できます。

私はおそらく共有データを何らかのSharedDataServiceでラップし、それを使用してそれを取得するでしょう。そのサービスは、低レイテンシで長寿命のキャッシュを使用して高速化できます。

共有データを1か所に保管できると、更新が必要になった場合でも、作業が楽になります。郵便番号はめったに変更されませんが、他のタイプはより頻繁に変更される可能性があります(言語、民族、SOC、NAICS、ICDなど)。

1
Dan Wilson

エンティティフレームワークを適切に使用できないことは、決定の要因にはなりません。エンティティフレームワークを使用していないいくつかのエンティティを持つリポジトリ(マルチテナントアプリケーションのような複雑なものではオプションではありません)でデータアクセスを適切に抽象化している場合は、目立ちません。また、dapper.netの使用を検討してください。このサイトで使用され、使いやすく、パフォーマンスが高く、どちらのアプローチでも問題は発生しません。

別のデータベースを選択するか、すべてのデータベースに共通データを配置するかを選択した場合は、共通データベース用に個別のデータベースを配置することになります。 幸いなことに、これらの2つのアプローチから選択する必要はありません。

別のオプションは、真実のソースとして機能する別のデータベースを用意し、そのデータを個々の顧客データベースにプッシュすることです。顧客データベースに保存されている共通のレコードは、永続的なキャッシュとして扱われます。 All共通データの書き込みは、最初に専用の共通データベースに送られ、次に顧客のDBにプッシュされます。私の考えでは、これが推奨されるアプローチです。

|C1| <-- |S| --> |C2|
C = Customer DB, S = Shared DB

RabbitmqやSQSなどのキューイングサービスを利用して、共通データが追加または編集されたときにプッシュをスケジュールします。

とにかく、顧客レコードなどには、とにかく別個の共通データベースが必要になるため、それを共通データに活用することもできます。また、明確に定義された真実の情報源があると非常に役立ちます。

同じデータの複数のコピーを持つことに問題はありません。実際の問題は、1つのデータに対して単一の定義された真実のソースがない場合に発生します。

0
TheCatWhisperer