SaaSサービスをRailsで開発するにあたり、マルチテナントに関する情報収集をしたため本ページにまとめとして記録いたします。
DBのマルチテナント
DBのマルチテナントにあたっては、セキュリティーの確保と保守性が方式の選定ポイントとなります。
ただし、SaaSサービスとして成功するほど保守のコストが増大するためプール型に移行していくようです。
ブリッジ型でマルチテナントを実現可能なGem「apartment」
データーベースのインスタンスは全テナントで共有するものの、テナントごとにスキーマ(テーブル、インデックス、ビュー、ストアドプロシージャ)を分ける方式です。
この実装にはGem「apartment」の使用が有名です。
SmartHR社も創業当初はセキュリティーを高めるためにapartmentを利用していたようです。
ただし、後述するように、サービスの特性上カラム数が多く契約社数の伸びにマイグレーションの作業が追いつかなくなったようです。
Sansan社もBill Oneにおいてこの方式を採用していたようですが、マイグレーションにかかる時間が長い課題にぶつかっています。
結局両社とも、後述するプール型と呼ばれる分離モデルに切り替え、デメリットとなるセキュリティーに関してはRow Level Securityを採用してセキュリティーを確保しています。
プール型でマルチテナントを実現可能なGem「activerecord-multi-tenant」
全てのテナントで同一のインスタンス、同一のスキーマを使用るするのがプール型です。
各テーブルにはテナントを識別するtenant_idのようなカラムを追加し、ソフトウェアレベルでアクセスするレコードを絞り込みます。
管理するテーブルが減るため保守がしやすくなる一方で、情報漏洩がデメリットとなります。
これに関しては、Row Level Security(RLSと略されます)の採用が一般的なようです。
RLSは、データベース層での分離が可能となるため、万が一ソフトウェアレベルでtenant_idの絞り込みミスがあったとしてもデータベース層で防ぐことが可能となります。
(ただしコネクションを取得する際にテナントの指定をミスすればRLSにおいても他テナントの情報が漏洩する可能性があります)
実装にあたっては、RailsでPostgreSQLのRow Level Security Policyを使ったマルチテナントの記事が参考になります。
Gem「activerecord-tenant-level-security」がありますが、実績や更新頻度などチェックしてから導入を検討すると良いでしょう。(筆者は確かRidgepoleと相性が悪く利用を断念しました。)
参考:Active RecordでRow Level Securityを使って安全にテナント間のデータを分離する
ログイン(devise)のマルチテナント
プール型のマルチテナントを進めるにあたり課題となるのがログイン処理です。
Railsのログイン処理にdevise(またはdevise_auth_token)を使っているプロジェクトが多いと思いますが、emailでユニークとなる仕様となっています。
マルチテナント対応するにあたり、同じユーザー(emailアドレス)であってもテナントごとにユーザーを作りたい場合は、この仕様に苦労することを事前に理解しておきましょう。
カスタマイズはそれほど複雑ではなさそうですが、deviseのバージョンアップに対して慎重になる必要がありそうです。
このあたりはNext.js + NextAuth.js + Rails APIを用いたマルチテナント開発メモのdevise_auth_tokenについてにて触れています。
s3のマルチテナント
バケットごとに分ける方式
分かりやすいが、バケットの初期上限数が100、AWS上の最大上限数が1,000となっている点に注意。
参考:バケットの制約と制限
フォルダーで分ける
s3にはデータの総量とオブジェクト数に制限がないため、フォルダーごとに格納することでバケット方式のデメリットを避けられます。
参考:https://aws.amazon.com/jp/s3/faqs/
RailsのActiveStorageの期限付きURL生成を利用する
期限付きURLを発行することにより、ログインユーザー単位でセキュリティーが確保しやすくなります。
またActiveStorageにアクセスするレコードそのものがDBでテナント管理されているため多重に管理されることになります。