マルチテナントアーキテクチャについて
SaaSシステムを開発しているみなさま、お元気でしょうか。
SaaSシステムというといわゆるWebサービスよりももう少しBtoBの雰囲気が漂ってまいります。
SaaSシステムでは契約者(ここではテナントと呼ぶ)が複数いて、テナント毎に複数のログインユーザーやロールが存在するのが一般的です。そして当然ながらテナント毎のデータは漏洩・混濁が許されない高いセキュリティが求められます。
SaaSシステムの構築はスケーラビリティにおいても100テナント程度から始まりゆくゆくは数千、数万テナントまで少なくとも線形にスケールするアーキテクチャを開発当初から求められ、さらに突発的な大規模テナントも問題なく吸収したいという要求があります。
その要求を満たす設計・開発・保守・運用をやっていくのは当然ながら簡単ではありません。
というわけで今日はマルチテナントアーキテクチャのお話です。
世に出る情報がとっても少ない
マルチテナントアーキテクチャはBtoB領域で必要とされることが多い構成のため、設計に関する概要からその詳細に至るまで各社あまり表に出したりしません。 情報を積極的に出している企業は海外ではSalesForce、国内ではサイボウズといったところでしょうか。
さくっと検索すると以下のような情報がてにはいります。
SalesForceのマルチテナントアーキテクチャ。老舗の実績あるアーキテクチャ。
サイボウズのクラウド基盤の解説。サービスセットと呼ばれる概念を導入している。
手前味噌ですが私の過去記事でも、とあるマルチテナントサービスにおけるアーキテクチャの一例をうかがい知ることができるでしょう。
日本語で読めるマルチテナントアーキテクチャの分類やその中で考慮すべき項目を整理した記事として、IBM developerworks のこの記事は外せないでしょう。
Web アプリケーションをマルチテナント型 SaaS ソリューションに変換する
特にマルチテナントアーキテクチャの分離レベルとして以下の3つに分類できるというのは覚えておくとよいです。(以下引用)
- クラウド内での単純な仮想化により、ハードウェアのみを共有
- 1つのアプリケーションで、テナントごとに異なるデータベースを使用
- 1つのアプリケーションでデータベースを共有 (最も効率的な真のマルチテナンシー)
Salesforceは3のタイプを採用しています。
私の経験したマルチテナントアーキテクチャ
私自身も今まで数度のマルチテナントアーキテクチャによるシステムの構築や他企業のアーキテクチャの実装の調査をしてきましたが、追求していくとなかなかおもしろいものです。
私が経験したマルチテナントアーキテクチャはオーソドックスな2です。
顧客データを全て一つのDBに混ぜてしまうのはデータ混濁のリスクが怖いのでDBの機能・ユーザーでデータを分離してしまうパターンです。
また、世のOSSのWebフレームワーク、DBフレームワーク(含むORマッパ)はたいていシングルテナント前提に作られていますから、OSSの恩恵を受けつつマルチテナントを実現しようとするとだいたいこの選択になるかと思います。
Salesforceのマルチテナントアーキテクチャ
Salesforceが複数テナントを1DBにストアするアーキテクチャを選択したというのは、アプリケーションレイヤでのテナントのデータ分離について非常に強力なフレームワークを自社で構築しているからこそ選択できたのではないかなぁと思います。 また、開発時期的にOracleで大量のスキーマを作るのは現実的ではなかったという時代背景があるのかも知れません。
アプリケーションレイヤでテナントのデータを分離すると、多数のテナントに少ないリソースでサービス提供できるとよく言われますね。
IBMの記事でも「最も効率的な真のマルチテナンシー」などと言われちゃってます。
開発時の考慮すべき点
さて、私がマルチテナントシステムの構築にあたって、こうすべきだなと思っていることについてつらつらと書いていきます。 ベースにしているマルチテナントアーキテクチャの実現手法は2(テナント毎にデータベースを分離)です。
テナント毎にドメインを分け、ログイン画面も分けよう
URLのドメインはテナント毎にできる限り分けるようにしましょう。
同一ドメインだとCookieに保存するセッションIDのキー名が同じだと他のテナントと混ざってはよくありません。 お客様は複数契約してくださって、テナントAとテナントBに同時にログインしたいと思うかもしれません。
ワイルドカード証明書で *.example.com を取っておいて tennant1.example.com, tennant2.example.com などとするのがよいでしょう。
特定テナントだけ全く別のドメインにしやすいとか、JavaScriptのセキュリティだとか、特定クライアントだけ別基盤に振るとか、IPアドレス制限とかそういうのもやりやすくなります。 一番メリットとして大きいのはDBを引かずともどのテナントからのアクセスか区別できるので障害調査やアプリレイヤのFWが作りやすくなる事でしょう。
歴史のあるSaaSシステムだと時期的にそういうことを考慮して構築できなかった場合もありますが、今から構築するなら分けたほうが苦労が少ないでしょう。
DBの接続先の切り替えはユーザーのリクエストを処理する最初の段階でフレームワークレベルで行う
タイトルが全てですね。
アプリケーションレイヤで開発するときに、自分がどのテナントのデータを処理しているか、どのDBに接続しているかを意識しないといけないとなると開発者のうっかりミスでデータの混濁が発生する可能性がとっても高くなります。
マルチテナントアーキテクチャの方式設計をする際は、慎重なアーキテクトで居るよう心がけましょう。開発者が意識しなくてもデータが混濁しないフレームワークを作りあげることがとても大切です。
また、フレームワーク設計時にはリクエスト完了時にはそのコネクションを忘れず close しておくのが安心です。 なぜかコネクション切り替えがうまく行かず、前回のリクエスト時に利用したコネクションを使いまわしたりすると困ったことになります。
コネクション確立コストの高いRDBMSではコネクションプールを用意しよう
PostgreSQLのことです。MySQLではそういうことを考えずとも十分コネクション確立コストが低いですが、PostgreSQLはコネクション確立コストが高いためコネクションプールを導入しないとアクセス数の多いサービスでは耐えられません。
しかしマルチテナント対応したコネクションプールというのはさほど多くありません。マルチテナント対応したコネクションプールとは、1テナント(スキーマ)あたり最大30コネクション、コネクションプール全体で500コネクションといった制限のかけられるコネクションプールの事です。
Javaではこのような要件に対してマッチするコネクションプール製品は残念ながらありません。 Javaのコネクションプーリング製品とは CommonsDBCP, HikariCP, TomcatCP, C3CP 等の事です。 私の調査では外部コネクションプール製品の pgPool2 などはこの要件を満たせず、最終的に pgbouncer がいいだろうという結論になりました。
複雑なクエリを発行する BtoB システムを作るなら PostgreSQL を使いたいところですが、コネクションプール周りは悩ましくなる事が増えるのでよく考えましょう。
テナントのデータを処理しているログにはテナントを特定できるキーが出るようにしよう
これは、まともに運用しようという意志があれば当然思いつくことですね。
テナント毎のコネクションを確立するのと同じタイミングでリクエストスコープの変数に入れてログ出力時に必ず出力されるようにしましょう。 JavaのLoggerであればMDCなどを用いて実現するのがよいでしょう。
ログインについてはSSOの仕組みをなるべく備えておこう
自社サービスとしてマルチテナントサービスを開発する際、余力があるのであればSSOの仕組みは最初から考慮して構築することにしましょう。
特にあなたの会社がマルチテナントサービスを複数提供する予定があるのであればこれはやっておいたほうが良いことです。
ある会社があなたのサービスAとサービスBを契約したいと考えた時、それぞれのログインID、パスワードを発行し、利用ユーザー情報を登録する手間を考えてみましょう。ゲンナリですね。
世にある様々な ActiveDirectory, LDAP, OpenID Connect, SAML などなどへの対応はサービスが成功してからでも構わないと思いますが、とにかくアカウント情報は一箇所にまとめるのがおすすめです。
このあたり、ヌーラボさんがサービスが複数の製品群を普及後にSSOのローンチに成功させましたが、良き知見・見解などがありそうで、一度そのあたりのお話を詳しくお聞きしてみたいものです。
クォータ・スロットリング
さて、マルチテナントの運用で起きる辛いことは「ある特定のテナントが多くのリソースを利用するために、他のテナントが割を食う」ということです。
で、公平性をなるべく高めようとするとクォータ・スロットリングなどの仕組みをマルチテナントシステムに組み込みたいという解決策に惹かれることになります。
しかし、実際のところ IaaS などのような計算資源を直接提供するならいざ知らず、SaaSでそのような制限を加えるのは実際ツラミが多い&知見少ないので、テナント毎のリソース使用を計測だけできるようにして最初は運用サポートでもいいんじゃないかと思っています。
SalesForce はそこのところはちゃんとガバナ制限という形で用意されており、さすがは老舗だなぁという感はありますね。
疲れてきたので今日はここまで
この記事は本来GWに描き上げる予定だったのですが、書き終わらなかったので放置されていたものです。
先日の勉強会で「マルチテナントアーキテクチャ、最高に面白いのでみんなと話したい」と言ったのものの、面白さが伝わってないとつまらないので取り急ぎでも公開してしまおうということで公開することにしました。
他にも以下のような事を紹介したいと思っていましたが、これは今後「マルチテナントアーキテクチャ大好き」な仲間ができてからということにしようと思います。
- 過負荷時の時に特定ドメインのリクエストだけ別サーバーにふれるようにしておこう
- DBのマイグレーションはテナント数だけかかるので作業時間の見積もり困難。無停止でリリースするならなるべく今あるテーブルに影響を及ぼさないよう別テーブルにしよう。
- テナントのデータはDBインスタンスへの依存を減らし、特定テナントだけ別DBインスタンスに移動することも可能にしておこう(大規模顧客対応)
- 課金対象項目の計測の仕組みを考えておく
- 休止、解約、メンテナンス、縮退など正常稼働時以外の状態を設計する
みんなで最高のマルチテナントアーキテクチャの知見を共有し、障害のないSaaS型業務システムが世を席巻する世界を目指そう!