SaaSを開発するとき、避けて通れない設計判断の一つが「複数の顧客のデータを、どう分離して管理するか」です。 この設計は「マルチテナントアーキテクチャ」と呼ばれ、選び方を誤ると、コストが膨らむだけでなく、 他社のデータが見えてしまうという重大な事故につながることもあります。
この権限設計には、大きく2つの層があります。一つは、他社(他のテナント)を締め出す「テナント間の境界線」。 もう一つは、同じテナントの中でも、社員・顧客ユーザー・取引先といった立場によって 見せる範囲を変える「テナント内の権限設計」です。本記事では、この2つの層を順番に整理します。
本記事は、 SaaS開発とは|進め方・費用・期間の基本 で扱った設計判断のうち、データベース設計をさらに深掘りしたものです。
「シングルテナント」は、顧客(テナント)ごとに専用のアプリケーションとインフラを用意する方式です。 顧客Aには顧客A専用のサーバーとデータベースを、顧客Bには顧客B専用のものを、それぞれ独立して構築します。
一方「マルチテナント」は、複数の顧客が同じアプリケーション・同じインフラを共有し、 データだけを顧客ごとに分離する方式です。多くのSaaSがマルチテナントを採用するのは、 インフラをまとめて運用できるためコストを抑えられ、機能追加やメンテナンスも 全顧客に一度に反映できるなど、運用効率とスケーラビリティの面で有利だからです。
マルチテナントを採用する場合も、顧客データをどのレベルで分離するかにはいくつかの選択肢があります。
| 方式 | 分離の単位 | メリット | デメリット |
|---|---|---|---|
| ①完全分離型 | データベースそのもの | 分離が最も強固で、他社データに物理的に到達できない。特定顧客だけ特別な構成にする対応もしやすい | 機能追加や仕様変更のたびに、テナントごとの環境へ個別に反映する必要があり、テナント数が増えるほど改修・保守の手間が積み上がる |
| ②スキーマ分離型 | スキーマ | データベースサーバーは共有しつつ、データは論理的に分離できる。完全分離ほどではないが分離は強い | スキーマの変更(マイグレーション)をテナントごとに適用する必要があり、テナント数が増えると運用が煩雑になる |
| ③共有型 | 行(レコード) | 改修やマイグレーションを1回行うだけで全テナントに一括反映できるため、運用・保守の手間が最も少ない。インフラコストも最も低い | 分離が最も弱く、tenant_id条件の書き忘れのような実装ミスが、そのまま情報漏洩事故に直結する |
つまり①完全分離型は、改修のたびにテナントごとに個別対応が必要で保守の手間が大きい一方、 ③共有型は1箇所を直せば全テナントに一括で適用できるため運用は楽ですが、 その分だけ、後述するように分離の強度という点では最も脆弱な方式でもあります。 データベースの種類やRDB/NoSQLの選び方の基本については データベースの種類とは?RDB・NoSQLの違いと選び方 でも整理しています。
3つの方式の違いが最もはっきり表れるのは、「実装時のミス」に対する耐性です。 以下のデモで、分離方式を切り替えながら「tenant_id条件を書き忘れる」チェックを入れ、 「請求データを取得する」を押してみてください。同じミスをしても、方式によって結果がまったく異なります。
実際の通信・データは発生しません。分離方式を切り替えて、「条件を書き忘れる」チェックを 入れた状態で「請求データを取得する」を押し、何が起きるかを確認してください。
| 請求先 | 請求書番号 | 金額 |
|---|
完全分離型・スキーマ分離型では、そもそも他のテナントのデータが物理的に別の場所にあるため、 条件を書き忘れても他社データには届きません。一方、共有型では全テナントのデータが同じテーブルに 同居しているため、tenant_id条件を書き忘れた1行のコードが、そのまま他社の顧客情報を 画面に表示してしまう事故に直結します。
共有型のアーキテクチャでは、ほぼすべてのクエリに「今ログインしているテナントのデータだけを対象にする」 という条件を、開発者が毎回正しく書き続ける必要があります。機能が増え、クエリの数が増えるほど、 どこか1箇所で書き忘れるリスクは積み上がっていきます。
これは、SQLインジェクション対策における「エスケープを1箇所忘れる」問題と構造がよく似ています。 一つひとつの対策は単純でも、開発者の注意力だけに依存していると、規模が大きくなるほど破綻しやすくなります。 SQLインジェクションの仕組みと対策については SQLインジェクションとは?仕組みと対策 で扱っています。
そのため、共有型を採用する場合は、tenant_id条件を人力で書き続けることに頼らず、 ORMのミドルウェア層で自動的にtenant_id条件を強制する、データベース自体の行レベルセキュリティ (Row Level Security)機能を使う、といった「書き忘れようがない」仕組みを用意しておくことが重要です。
「書き忘れようがない仕組み」の具体例が、デコレータです。権限のチェックを処理本体の中に書くのではなく、 処理の前に共通の確認を差し込む仕組みとして外側に切り出します。 「ログイン済みか」「権限があるか」は標準のデコレータで検討できますが、 「テナントをまたいでいないか」までは検討してくれないため、この部分は自作のデコレータで補います。
「対象データのテナントに本当に所属しているか」を確認するデコレータを共通の1ファイルにひとつだけ定義し、 テナントのデータを扱う処理それぞれの先頭に適用するだけです。チェックのロジックは1箇所にしかないため、 書き間違いや実装漏れが起きにくくなります。逆に言えば、このデコレータの適用自体を忘れることが tenant_id条件の書き忘れと同じ結果につながるため、レビューの段階で必須のデコレータが 揃っているかを確認する運用もあわせて必要です。
ここまでは、テナントという大きな境界線をどう守るかという話でした。 しかし実際の権限設計は、この境界線を引いただけでは終わりません。 同じテナントの中にも、立場の異なる複数の人が出入りするためです。
一つの顧客(テナント)の中にも、実際には複数の立場の人が関わります。 自社の社員、顧客企業に所属するユーザー、業務委託先や仕入先といった取引先など、 同じテナントのデータを扱うとはいえ、それぞれに見せてよい範囲は本来異なります。
権限設計で決めるべきことは、大きく3つの軸に分けられます。
この3つの軸を、社員・顧客ユーザー・取引先といった立場(ロール)ごとに組み合わせて設定し、 実際のアクセスをその設定どおりに制御する。ここまでを合わせて、はじめて権限設計が完成します。
テナント間の分離が「外部の第三者や、無関係の他社を締め出す」ための境界線だったのに対し、 テナント内の権限設計は、すでに正規にログインしている、同じテナントの関係者同士の間で、 見せてよい範囲・操作してよい範囲を線引きするものです。つまり、外部からの侵入を防ぐ対策ではなく、 内部の関係者同士のあいだで情報や操作範囲を適切に制限する、内部統制としてのセキュリティ対策にあたります。
実装の考え方自体は、テナント分離と同じです。「このユーザーは、このロールを持っているか」 「このロールは、この機能・このデータにアクセスしてよいか」という検討を、 先ほどのデコレータのように処理の外側に切り出し、機能を追加するたびに漏れなく適用できる形にしておきます。
テナント間の分離方式と、テナント内部の権限設計。この2つの層を踏まえたうえで、 最後に「どの分離方式を選ぶか」という判断に戻ります。正解が一つあるわけではなく、 顧客数、単価、業界特有のコンプライアンス要件によって判断が変わります。
また、最初から完璧な分離方式を選び切る必要はありません。事業の初期段階ではシンプルな共有型で始め、 顧客数や要件の変化に応じて、後から一部を分離型に移行する、という段階的な設計も可能です。 ただし、後から分離方式を変更する場合、データ移行の設計コストは小さくありません。 この点は DB設計を軽視すると開発コストは100倍に? で扱った「後から変更しにくい部分」の典型例でもあります。
テナント分離は、顧客データを預かるSaaSの信頼性そのものに関わる設計です。 Webアプリ全般のセキュリティ対策の全体像については Webアプリの脆弱性・セキュリティ対策まとめ でも整理しています。
マルチテナントアーキテクチャの権限設計は、2つの層に分けて考えると整理しやすくなります。 一つは、他社(他のテナント)を締め出す「テナント間の境界線」。完全分離型・スキーマ分離型・共有型には それぞれコストと分離の強度のトレードオフがあり、特に共有型を選ぶ場合は、 tenant_id条件の書き忘れが直接情報漏洩につながるリスクを、仕組みとして防ぐ設計が欠かせません。
もう一つは、同じテナントの中でも社員・顧客ユーザー・取引先といった立場によって、 アクセスできるデータ・できる操作・使える機能を線引きする「テナント内の権限設計」です。 これは外部からの侵入を防ぐ対策ではなく、すでに正規にログインしている関係者同士のあいだで 情報や操作範囲を適切に制限する、内部統制としてのセキュリティにあたります。
どちらの層も、デコレータのように検討・強制のロジックを処理の外側へ切り出し、 機能を追加するたびに漏れなく適用できる仕組みにしておくことが共通の鍵になります。 そのうえで、顧客数・単価・コンプライアンス要件に応じてどの分離方式を選ぶか、 あるいはハイブリッドで進めるかを、事業の初期段階から意識しておくことが重要です。