背景
DELISH KITCHEN 開発部で小売向き合いの開発に携わっている大村 (@kosukeohmura) といいます。
エブリーでは リテールメディアの構築・提供を通して小売様を支援していく 構想を掲げており、retail HUB という枠組みでいくつかの SaaS プロダクトを開発・提供しています。開発のフェーズとしてはまだまだ未成熟な部分も多いのですが、今回はその中でもマルチテナントな SaaS に想定される要求に着目して、それを満たすために考えてきたことをお話しします。
マルチテナントなシステムとは
エブリーでは各小売(主にスーパーマーケット)様がそれぞれテナントとなりシステムを共用する、いわゆるマルチテナントなプロダクトを作っています。マルチテナントなシステムは、テナントという概念自体が存在しないシステムや、テナントごとに独自のシステムが存在する場合に比べてどのような時に向いているものなのでしょうか。それぞれの場合について、私なりの理解を書いてみます。
テナントという概念が無いシステム
ここで挙げるなかで最もシンプルなケースとして、テナントという概念がなく、各使用者が独立しているシステムがあります。弊社が運営するレシピ動画メディア DELISH KITCHEN もこのモデルです。 (当たり前な話ですが)組織で使用されないようなシステムでは、テナントを使ってリソースを束ねたい動機も生まれません。
下記のような要求が生まれうるシステムでは、テナントという単位で各種リソースを束ねることを検討する必要が出てきます。
- 自組織のメンバーを確認したい
- 自組織に属するリソースの変更を行いたい
- 特定の組織に属するユーザー群の、プロダクトの使用状況を確認したい
特定のテナント向けのシステム
次にやや極端な例として、テナントそれぞれに要求を細かく聴取し、その都合にあわせてそれぞれ制作・構築・提供されるシステムを考えてみます。コストを考えない場合には最もテナントの要求を満たしうる手法であり、テナントごとにシステムに対する要求が異なる場合、最も柔軟に対応できる手法です。
一方で、同じような課題を多数のテナントが抱える中でこの手法を取った場合には、システムごとに作り替えやリリース作業、仕様策定などのコストがかかり、そのコストは必要以上に多くなります。複数のシステムに同じ変更を適用し続けることも、テナントが増えるに連れて難しくなります。
マルチテナントなシステム
マルチテナントなシステムでは、ソースコードやその他サーバー等のリソースをテナント間で共用します。テナントの増加に対して開発や運用に掛かるコストが(直接は)比例しないので、スケールしやすいシステムとなります。何らかの組織を表すようなデータ構造が必要で、同じような課題を持ったテナントが多く存在する場合には有力な選択肢となります。私達のチームでも、多くの小売様の抱える課題を共通のプロダクトで解決していきたいと考え、この手法をとっています。
留意しておくべきこととして、テナントごとの課題が大きく異なる場合や、各テナントからの要求を安直に受け取り機能拡張を続けたような場合には、特定のテナント向けの特別な実装が乱立したり、システムの必要以上な多機能化が起こり得ます。そうなるとプロダクトは複雑性を増していき、開発速度の低下やバグの頻出を招くでしょう。
テナントごとの要求は多少なりとも異なるものであり、それらの要求を時には切り捨てつつ、どうテナント共通の課題を解決できるプロダクトに落とし込んでいくか、という難しさに向き合うことが必要となります。マルチテナントなシステムは多くの場合 to B であり、お客様との関係が to C と比べて近く、システム使用者の生の声が間近で聞けることもあるかと思います。その中で、未来を見据えてプロダクトをどういうものにしていくかを考え、意思決定を行い続けることが大切だと思います。
マルチテナントなプロダクトにおける要求
複数のテナントがリソースを共用する都合上、テナントごとのデータの分離によるプライバシーの確保が要求の筆頭として挙げられます。異なるテナントのデータがプロダクト上で見えてしまうと、プロダクトの信頼は失われ事業自体の存続が危うくなってしまいます。
他方で、プロダクトを運営・改善していく上ではテナントを横断したデータを見て分析を行い・次の施策へ繋げていきたいというニーズもあり、また運営側のメンバーが各テナントの代わりにプロダクトのデータを閲覧・操作するケースも考えられます。
マルチテナントなプロダクト開発で考えること
データの分離
RDB
プロダクトやその管理画面に表示されるほとんどのデータが入っているデータベースです。金銭的なコストと DB への負荷とを考え、データベースのインスタンスは共通とし、テナントごとに論理データベースを分離する形にしています。加えて、テナント共通のレコードについては共用のスキーマを作成し、そこに保存しています。RDB ユーザーをテナントごとに作成し、それぞれ対応するテナントのデータ以外にアクセスできないようにしています。
画像などのファイル
各種ファイルを保存するストレージサービス (Amazon S3 を使用しています) についても、RDB と同様にテナントごとのデータの分離が必要であり、テナントごとにバケットを分けてファイルを保管しています。こちらもテナント共通のファイルについては共用のバケットを使用しています。
各種イベントログ、アクセスログ
各種分析に使うクライアントアプリ上でのイベントログやサーバー・ロードバランサへのアクセスログについては、センシティブな情報は入らないこととテナント横断で統計的な見たいケースが多いと考え、テナントごとに分けずに保存しています。テナントの識別子をログに含めることで、テナントごとのログを抽出することができます。
API サーバー・ロードバランサー
それぞれテナント共通のものを使用していますが、API リクエストの内容からテナントを識別し、他テナント用の RDB やストレージへの接続が起き得ないようにしています。
開発用のテナント
細かい話ですが、開発時や社内でのデモに使うテナントどうするの?という話があります。弊社ではローカル環境や開発用の環境には社内共通の架空のテナントの環境を用意し、開発や社内用デモアプリでの動作確認にそれを使用しています。
運営メンバーのユースケースの考慮
運営側のメンバーが各テナントの代わりにプロダクトのデータを閲覧・操作しようとした場合に、各テナントごとのユーザーアカウントをいくつも持つ形だと、ログイン・ログアウトを繰り返したり、複数のサイトを渡り歩くなど面倒なことが想定できます。このことを防ぐため、アカウントに複数のテナントが紐づくような形とし、閲覧・操作対象のテナントを選択できるような形としています。
サービスやサーバーの Endpoint の URL をテナントごとに分けるか
世の中の SaaS プロダクトを使っているとテナントごとにサブドメインを付与して {tenant}.example.com
のような形とするケースがあります。基本的にはテナントごとに付与したほうがリソースの競合の可能性がなくなると考えてそうしていますが、テナントを切り替えるようなユースケースがある場合にはサブドメインを変えにくいです。
おわりに
マルチテナントなシステムの開発を始めている中で考えてきたことを挙げてみました(現在はこうしている、というもので、今後変えるところも出てくると思います)。マルチテナントではないシステムに比べて、基本的なところでも想像以上に考えることが多く、またこの辺の設計がセキュリティや使い勝手の向上に大きく効いてくると感じています。開発が進んできたら、実践的な話にも踏み込んだ話をしたいです。