SlideShare ist ein Scribd-Unternehmen logo
1 von 36
Downloaden Sie, um offline zu lesen
© 2022 LayerX Inc.
1
マルチテナントのアプリケーション実装
〜実践編〜
SaaSにおけるマルチテナント設計の悩みと勘所
SaaS.tech #2
2022.04.20
© 2022 LayerX Inc.
2
自己紹介
株式会社LayerX
中川佳希
@yyoshiki41
バクラク請求書のテックリード
バックエンドからフロントエンドまで.
SaaS が扱う業務ドメインへの好奇心と
サービスの成長に日々ワクワクを感じています.
Gopher.
© 2022 LayerX Inc.
3
1. 導入
a. マルチテナントSaaS
2. データベース設計
a. 設計パターン
3. アプリケーション側での実装
a. テーブルスキーマ
b. 型(Type)の実装
c. コンテキスト(Request-scoped Data)
d. ORM
e. バリデーション
f. ロギング / モニタリング
g. テスト
章立て
© 2022 LayerX Inc.
4
マルチテナント SaaS
マルチテナント SaaS は・・・
同質のソフトウェアを全テナントのユーザーへ提供.
リソースの一部またはすべてをテナント間で共有.
SaaS 提供側のメリット(シングルテナントアプリケーションと比較)
● 機能提供アウトカムの最大化
● サービス運用の効率化
● インフラコストの削減
© 2022 LayerX Inc.
5
開発者を悩ますポイント
1. テナント毎に安全にデータを分離した状態で、アプリケーションを実装できるか?
2. テナント境界線をどのレイヤで実装するか?
3. テナント間のシステムリソース共有をどこまで行うか?
マルチテナント SaaS が絶対に防ぐべきこと
=> データが他のテナントにも共有されてしまうこと(漏洩)
マルチテナント SaaS
© 2022 LayerX Inc.
6
マルチテナント SaaS での安全なデータ分離の実装
安全にテナント毎のデータを分離するには?
● データベース
● ミドルウェア
○ アプリ => ミドルウェア => データベース のような Proxy を想定
● アプリケーション
● テスト
以降では低レイヤの部分から順にみていきます.
© 2022 LayerX Inc.
7
SaaS 開発における最初の分岐点
1. データベースをテナント毎に作成
=> データ所有者(テナント)を データベース名 で表現
2. データベースは共有、スキーマをテナント毎に作成
=> データ所有者(テナント)を スキーマ で表現
3. データベースは共有、テーブルをテナント毎に作成
=> データ所有者(テナント)を テーブル名 で表現
4. データベースは共有、テーブルも共有、
各テーブルがテナント識別カラムを持ち、レコード値で識別・分離
=> データ所有者(テナント)を テーブルのカラム値 で表現
データベース設計
Isolated
Shared
© 2022 LayerX Inc.
8
1. データベースをテナント毎に作成
メリット
1. データの持ち方としては、もっとも堅牢
デメリット
1. マイグレーション(テーブルスキーマ変更)コスト
2. アプリからのデータベース接続コスト
a. データベースユーザーが異なる場合, セッションも異なる
3. テナント作成時に、毎回データベースの初期化処理が必要
a. テーブル作成、データベースユーザーの認証設定
4. テナントとデータベースのリレーションが別途必要
Tenant B
Tenant A
© 2022 LayerX Inc.
9
スキーマとは...
PostgreSQL などで名前空間を作成できる仕組み.
データベースオブジェクト(テーブル、関数など)を
同じオブジェクト名でもスキーマが異なれば作成可能になる.
MySQL ではオブジェクトを論理的に分ける
(グルーピングする)仕組みに相当するものは見当たらず.
2. データベースは共有、スキーマをテナント毎に作成
Global
Tables
Tenant A
Tables
Tenant B
© 2022 LayerX Inc.
10
2. データベースは共有、スキーマをテナント毎に作成
Global
Tables
Tenant A
Tables
Tenant B
メリット
1. スキーマ単位で所有者を決めれる
デメリット
1. マイグレーション(テーブルスキーマ変更)コスト
2. アプリからのデータベース接続コスト
a. スキーマ所有者が異なる場合, セッションも異なる
3. テナント作成時に、毎回スキーマの初期化処理が必要
a. テーブル作成、スキーマ所有者の認証設定
4. テナントとスキーマのリレーションが別途必要
© 2022 LayerX Inc.
11
メリット
1. 1つのデータベース内でテナントのデータを保持できる
デメリット
1. マイグレーション(テーブルスキーマ変更)コスト
2. テナント作成時に、毎回テーブルの初期化処理が必要
a. テーブル作成
3. テナントとテーブル名のリレーションが別途必要
4. テナントのテーブル間で外部キーが煩雑になる
3. データベースは共有、テーブルをテナント毎に作成
Global
Tenant A
tables
Tenant B
tables
© 2022 LayerX Inc.
12
ID TenantID Name
1 A Foo
2 B Bar
メリット
1. マイグレーション(テーブルスキーマ変更)コストが低い
2. データベース側での設定コストが低い
デメリット
1. 他テナントのレコードへアクセスが容易
a. 同一テーブルの為, 最もカジュアルにアクセス可能
2. ロジックを実装する必要がある
a. アプリ側で制御する場合
i. WHERE 句
b. データベース側で制御する場合
i. Row-Level Security での制御
ii. ストアドプロシージャを実装しての制御
4. テーブルにテナント識別カラムを持ち, 行単位で制御
Global
tables
© 2022 LayerX Inc.
13
テーブルに識別カラムを持ち,
テーブルへのポリシー × データベースユーザーで制御するをデータベース側で実装す
る例
データベースユーザーのセッション管理などが一定ネックになる.
● PostgreSQL Row Level Security
○ PostgreSQL ネイティブの機能
● Implementing row level security in MySQL / SQL Maestro
○ Trigger や View テーブルを使って, MySQL で RLS を実現する例
4. テーブルにテナント識別カラムを持ち, 行単位で制御
© 2022 LayerX Inc.
14
アプリ => ミドルウェア => データベース のようなプロキシを想定.
● ProxySQL
○ mysql_query_rules: WHERE 句に TenantID がないクエリをはじく
● MariaDB MaxScale
○ Deny ルールを作る: WHERE 句に TenantID がないクエリをはじく
上記のような SQL を解釈できるプロキシミドルウェアで,
ポリシー(とデータベースユーザーの掛け合わせ)での制御.
ポリシーに沿わないレコードへのアクセスを拒否する.
実検証までは行っておらず 🙏
ミドルウェア(+α)
© 2022 LayerX Inc.
15
4. テーブルにテナント識別カラムを持ち, 行単位で制御
主な理由
● データベース、マイグレーション運用コストが最も低い
○ デプロイ(マイグレーション)難易度が高いサービスは致命的
○ テナント追加時, データベース側の初期化処理のサブシステム等が不要
● データベース側にロジックを寄せることでのロックインを避けたい
○ ストアドプロシージャなどの実装への依存も持ちたくない
● テナント数のスケールに最も適している
○ テナント毎のデータベースユーザーと管理, アプリからの接続セッションの管
理なども不要
● ロジックのテストの行いやすさ
バクラクでのデータベース設計
© 2022 LayerX Inc.
16
4. テーブルにテナント識別カラムを持ち, 行単位で制御
テナント毎にデータを(論理的に)識別・分離して扱うことは, 至上命題.
実際の取り組み(ここからが本題)
1. テーブルスキーマ
2. 型(Type)の実装
3. コンテキスト(Request-scoped Data)
4. ORM
5. バリデーション
6. ロギング / モニタリング
7. テスト
アプリケーション実装〜実践編〜
ID TenantID Name
1 A Foo
2 B Bar
© 2022 LayerX Inc.
17
全テーブルへ冗長に TenantID (テナント識別カラム)を付ける
(中間テーブル、子テーブルでも同様)
親テーブルにはマストで必要.
子テーブルでは従来, 親テーブルへの外部キー制約だけで充分.
冗長にカラムを付ける理由は,
1. ORM などで, WHERE句に TenantID を機械的に付けれる
a. カラム有無を考えなくて良い
2. JOIN 時にも TenantID を機械的に付けれる
3. 子テーブルから自テナントデータのみを取得可能
4. 親, 子テーブルで異なる TenantID のデータを排除
5. シャーディングが必要になった際, キーに使える
テーブルスキーマ
ID TenantID Name
1 A Foo
2 B Bar
parents
ID ParentID TenantID Name
11 1 A Foo
22 2 B Bar
children
© 2022 LayerX Inc.
18
JOIN 時にも TenantID を機械的に付けれる
通常, 起きてはいけない不整合データを取得時に排除可能.
※ INSERT時に検知すべきかつ, 従来のリレーショナルモデル
では考慮しなくてもよい問題ではある...
例.リレーションのあるテーブル間で異なる TenantID のレコード
テーブルスキーマ
ID TenantID Name
1 A Foo
2 B Bar
parents
ID ParentID TenantID Name
11 1 A Foo
22 2 XXX Bar
children
SELECT *
FROM parents
INNER JOIN children ON parents.ID = children.ParentID
AND parents.TenantID = children.TenantID
WHERE parents.TenantID = "B";
© 2022 LayerX Inc.
19
子テーブルから自テナントレコードのみを取得可能
アプリの処理単位もテナント単位なので,
利用しやすいデータモデル.
API 等から ID 指定でリソース取得するケースでも,
機械的に TenantID を付けてバリデーションできる.
テーブルスキーマ
ID TenantID Name
1 A Foo
2 B Bar
parents
ID ParentID TenantID Name
11 1 A Foo
22 2 B Bar
children
SELECT * FROM children
WHERE TenantID = "B";
SELECT * FROM children
WHERE TenantID = "B" AND ID IN ("ID1", "ID2", ...);
© 2022 LayerX Inc.
20
シャーディングが必要になった際には, キーとして使用できる.
Google’s F1 paper で, 分散データベースの階層型のデータモデル(The Cluster
Hierarchical Model)が触れられている.
リレーショナルモデルでは, 親テーブルへの外部キーを持つカラムだけでリレーションを表
現可能. しかし, 分散データベース環境化では従来のリレーショナルモデルの外部キーだ
けではトランザクション, ジョインなどのコストが高価になる.
先祖(親)の ID をプライマリキーに含めることで, 物理的にも同じマシンでの処理が行え
る.
● Google F1
● Designing your SaaS Database for Scale with Postgres / Citus Data
● Sharding a multi-tenant app with Postgres / Citus Data
テーブルスキーマ
© 2022 LayerX Inc.
21
従来のリレーショナルモデルとの比較
テーブルスキーマ
© 2022 LayerX Inc.
22
ユニークキーやインデックスも TenantID を軸に設計する
例えば name カラムにユニークキー制約をつけたい場合,
TenantID を先頭(プレフィックス)に付けた複合キー にする.
テーブルスキーマ
CREATE TABLE `table_a` (
`id` int(11) NOT NULL,
`tenant_id` varchar(36) NOT NULL,
`name` varchar(36) NOT NULL,
PRIMARY KEY `id`,
UNIQUE KEY (`tenant_id`,`name`),
CONSTRAINT `fk_tenant_id` FOREIGN KEY
(`tenant_id`) REFERENCES `tenants` (`id`)
) ENGINE=InnoDB;
ID TenantID Name
1 A Foo
2 B Bar
© 2022 LayerX Inc.
23
複合キーは列挙したカラム順に連結して, 内部値としてインデックスされる.
WHERE tenant_id = “B” (インデックスのプレフィックス) だけでも既知の範囲に絞るイ
ンデックスとして機能する.
アプリが使用するテナントのレコードへの効率的なクエリになる.
PrimaryKey を用いないクエリでは, 必須のインデックスになる.
※ PrimaryKey を用いる場合も, 後述する Context が持つ TenantID と一致している
か検証するため, WHERE 条件として使用している
tips:複合キーのカラムを ForeignKey として使用する際の話
MySQL 外部キー制約とインデックスに必要な知識 - LayerX エンジニアブログ
テーブルスキーマ
© 2022 LayerX Inc.
24
A defined type in Go
TenantID という型を新たに定義して使用
underlying type は, プリミティブな string 型
A defined type の主なメリット
● 関数引数, 返り値の間違いや代入ミスをコンパイル時にエラーにできる
● 独自メソッドを実装できる
型(Type)の実装
type TenantID string
© 2022 LayerX Inc.
25
Generate go model from a database table schema
テーブル定義から, Go の Struct 生成を行う.
xo というツールで自動生成.
TenantID カラム生成時には, 独自定義した型を使用している.
型(Type)の実装
type TableA struct {
ID string
TenantID TenantID
Name string
}
© 2022 LayerX Inc.
26
リクエストを受け取ったAPIサーバーは, コンテキストに TenantID を入れる
ユーザーの認証後に特定されたテナント情報をアプリのMiddleware層でセット.
以降は, この TenantID をもとにレスポンスまで処理を行っていく.
コンテキスト(Request-scoped Data)
func ContextWithTenantID(ctx context.Context, tenantID TenantID) context.Context {
return context.WithValue(ctx, ctxKeyTenantID, tenantID)
}
© 2022 LayerX Inc.
27
バッチ処理などの実装
テナント毎に処理を実行するように関数を実装する
※ テナントをまたいで処理を行う関数を極力実装しない
コンテキスト(Request-scoped Data)
func ProcessPerTenant(ctx context.Context, input string) context.Context {
// do stuff…
}
© 2022 LayerX Inc.
28
ORM
gorm の Callback plugin を実装.
WHERE 句に自動で TenantID 条件がつくようにしている.
func NewGlobalDB(conf *mysql.Config) {
…
// Register callback functions
db.Callback().Query().Before("gorm:query").Register("my_plugin:before_query", callbackTenantID)
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", callbackTenantID)
db.Callback().Delete().Before("gorm:delete").Register("my_plugin:before_delete", callbackTenantID)
db.Callback().RowQuery().Before("gorm:row_query").Register("my_plugin:before_row_query", callbackTenantID)
}
© 2022 LayerX Inc.
29
ORM
登録する Callback 関数の実装は,
Struct 内の TenantID フィールドを検出して, WHERE 句をセットする.
(SELECT / CREATE / UPDATE / DELETE)
アプリから呼び出す際には, TenantID を引数に渡してコールバック関数が登録された
DBインスタンスを仕様する.
err := app.NewDB(db, tenantID).First(&model).Error
© 2022 LayerX Inc.
30
一部のメソッド(Raw, Exec)では, callback 関数では対応出来ない..
※ Raw, Exec は, 記述したSQLをそのまま実行できる機能
● 基本的に使用させない.(CIでの検知)
● 管理画面など, 例外的には使用可能
ORM
var result Result
err := db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
© 2022 LayerX Inc.
31
callback 関数が発火せずに実行されたクエリに対しては,
ログを出力するようにもしている.
意図せずに, TenantID を指定しないクエリを発見出来るようにする.
ORM
© 2022 LayerX Inc.
32
バリデーション
APIサーバーの終端でレスポンスデータのバリデーションを実行
RDS含め Redis, ElasticSearch, DynamoDB などデータソース全体で,
他のテナントデータが含まれていないことを検証する最後の砦.
関数内で再帰的にレスポンスオブジェクトの TenantID フィールドが一致していることを
検証.
Go で書くには骨が折れる reflect での処理.
func ValidateTenantID(tenantID TenantID, obj interface{}) {
// do validation…
}
© 2022 LayerX Inc.
33
ログには常に TenantID を含めて出力
logger 側で Context 内の TenantID 自動で出力するように実装
アプリからの呼び出し:
モニタリングツール(Datadog)上でも,
Log Facets としてフィルタも可能.
トラブルシュート時にデータソース特定や
カスタマー連絡に役立ちます.
ロギング / モニタリング
app.LogError(ctx, err).Send()
© 2022 LayerX Inc.
34
TenantID がついていないSQLクエリログをモニタリングツールで監視
正規表現でのチェックが必要なため, まだまだ調整中
(理想はSQLパーサーでアラート条件を作れること)
ロギング / モニタリング
© 2022 LayerX Inc.
35
単体テスト毎にテナントを作成して, 並列に実行する
● 他テナントデータに影響を与える場合, テストで検知出来る
○ 他のテナントへ相互に影響を与える機能が存在しない
● パッケージ内の全テスト完了まで, テストデータをドロップ(削除)しない
● テスト/サブテスト並列度を上げる動機づけになる
● テナントセットアップは, ヘルパー関数を用意
テスト
func TestA(t *testing.T) {
helper.SetupTenant(t)
t.Run(“Case1”, func(t *testing.T) {
t.Parallel(t)
// run tests…
}
}
© 2022 LayerX Inc.
36
● アプリケーション実装でのテナントデータの論理的な分離を行う方針を取っていま
す.
○ データベースやミドルウェアでの制御でも, ポリシー × ユーザーでのロジック
実装部分は避けられないと思います.
● 開発/テスト/デプロイのコストの低さやロックインを避けれるなどメリットはあります.
○ その反面, アプリ側でのガードレール実装の重要度は高くなります.
● 安全かつ開発スピードも落ちない仕組みの改善は進めていきます.
まとめ

Weitere ähnliche Inhalte

Was ist angesagt?

Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)
Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)
Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)NTT DATA Technology & Innovation
 
Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Kohei Tokunaga
 
3分でわかるAzureでのService Principal
3分でわかるAzureでのService Principal3分でわかるAzureでのService Principal
3分でわかるAzureでのService PrincipalToru Makabe
 
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところY Watanabe
 
Spanner移行について本気出して考えてみた
Spanner移行について本気出して考えてみたSpanner移行について本気出して考えてみた
Spanner移行について本気出して考えてみたtechgamecollege
 
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)NTT DATA Technology & Innovation
 
超実践 Cloud Spanner 設計講座
超実践 Cloud Spanner 設計講座超実践 Cloud Spanner 設計講座
超実践 Cloud Spanner 設計講座Samir Hammoudi
 
そんなトランザクションマネージャで大丈夫か?
そんなトランザクションマネージャで大丈夫か?そんなトランザクションマネージャで大丈夫か?
そんなトランザクションマネージャで大丈夫か?takezoe
 
AWS Black Belt Online Seminar 2018 Amazon DynamoDB Advanced Design Pattern
AWS Black Belt Online Seminar 2018 Amazon DynamoDB Advanced Design PatternAWS Black Belt Online Seminar 2018 Amazon DynamoDB Advanced Design Pattern
AWS Black Belt Online Seminar 2018 Amazon DynamoDB Advanced Design PatternAmazon Web Services Japan
 
Ingress on Azure Kubernetes Service
Ingress on Azure Kubernetes ServiceIngress on Azure Kubernetes Service
Ingress on Azure Kubernetes ServiceToru Makabe
 
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)A AOKI
 
GKE に飛んでくるトラフィックを 自由自在に操る力 | 第 10 回 Google Cloud INSIDE Games & Apps Online
GKE に飛んでくるトラフィックを 自由自在に操る力 | 第 10 回 Google Cloud INSIDE Games & Apps OnlineGKE に飛んでくるトラフィックを 自由自在に操る力 | 第 10 回 Google Cloud INSIDE Games & Apps Online
GKE に飛んでくるトラフィックを 自由自在に操る力 | 第 10 回 Google Cloud INSIDE Games & Apps OnlineGoogle Cloud Platform - Japan
 
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3 データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3 Hiroshi Ito
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」Takuto Wada
 
Knative Eventing 入門(Kubernetes Novice Tokyo #11 発表資料)
Knative Eventing 入門(Kubernetes Novice Tokyo #11 発表資料)Knative Eventing 入門(Kubernetes Novice Tokyo #11 発表資料)
Knative Eventing 入門(Kubernetes Novice Tokyo #11 発表資料)NTT DATA Technology & Innovation
 
エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織Takafumi ONAKA
 
kubernetes初心者がKnative Lambda Runtime触ってみた(Kubernetes Novice Tokyo #13 発表資料)
kubernetes初心者がKnative Lambda Runtime触ってみた(Kubernetes Novice Tokyo #13 発表資料)kubernetes初心者がKnative Lambda Runtime触ってみた(Kubernetes Novice Tokyo #13 発表資料)
kubernetes初心者がKnative Lambda Runtime触ってみた(Kubernetes Novice Tokyo #13 発表資料)NTT DATA Technology & Innovation
 
Azure API Management 俺的マニュアル
Azure API Management 俺的マニュアルAzure API Management 俺的マニュアル
Azure API Management 俺的マニュアル貴志 上坂
 
webエンジニアのためのはじめてのredis
webエンジニアのためのはじめてのrediswebエンジニアのためのはじめてのredis
webエンジニアのためのはじめてのredisnasa9084
 
MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法Tetsutaro Watanabe
 

Was ist angesagt? (20)

Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)
Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)
Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)
 
Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行
 
3分でわかるAzureでのService Principal
3分でわかるAzureでのService Principal3分でわかるAzureでのService Principal
3分でわかるAzureでのService Principal
 
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
 
Spanner移行について本気出して考えてみた
Spanner移行について本気出して考えてみたSpanner移行について本気出して考えてみた
Spanner移行について本気出して考えてみた
 
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
 
超実践 Cloud Spanner 設計講座
超実践 Cloud Spanner 設計講座超実践 Cloud Spanner 設計講座
超実践 Cloud Spanner 設計講座
 
そんなトランザクションマネージャで大丈夫か?
そんなトランザクションマネージャで大丈夫か?そんなトランザクションマネージャで大丈夫か?
そんなトランザクションマネージャで大丈夫か?
 
AWS Black Belt Online Seminar 2018 Amazon DynamoDB Advanced Design Pattern
AWS Black Belt Online Seminar 2018 Amazon DynamoDB Advanced Design PatternAWS Black Belt Online Seminar 2018 Amazon DynamoDB Advanced Design Pattern
AWS Black Belt Online Seminar 2018 Amazon DynamoDB Advanced Design Pattern
 
Ingress on Azure Kubernetes Service
Ingress on Azure Kubernetes ServiceIngress on Azure Kubernetes Service
Ingress on Azure Kubernetes Service
 
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
 
GKE に飛んでくるトラフィックを 自由自在に操る力 | 第 10 回 Google Cloud INSIDE Games & Apps Online
GKE に飛んでくるトラフィックを 自由自在に操る力 | 第 10 回 Google Cloud INSIDE Games & Apps OnlineGKE に飛んでくるトラフィックを 自由自在に操る力 | 第 10 回 Google Cloud INSIDE Games & Apps Online
GKE に飛んでくるトラフィックを 自由自在に操る力 | 第 10 回 Google Cloud INSIDE Games & Apps Online
 
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3 データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介 #jjug_ccc #ccc_g3
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
 
Knative Eventing 入門(Kubernetes Novice Tokyo #11 発表資料)
Knative Eventing 入門(Kubernetes Novice Tokyo #11 発表資料)Knative Eventing 入門(Kubernetes Novice Tokyo #11 発表資料)
Knative Eventing 入門(Kubernetes Novice Tokyo #11 発表資料)
 
エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織
 
kubernetes初心者がKnative Lambda Runtime触ってみた(Kubernetes Novice Tokyo #13 発表資料)
kubernetes初心者がKnative Lambda Runtime触ってみた(Kubernetes Novice Tokyo #13 発表資料)kubernetes初心者がKnative Lambda Runtime触ってみた(Kubernetes Novice Tokyo #13 発表資料)
kubernetes初心者がKnative Lambda Runtime触ってみた(Kubernetes Novice Tokyo #13 発表資料)
 
Azure API Management 俺的マニュアル
Azure API Management 俺的マニュアルAzure API Management 俺的マニュアル
Azure API Management 俺的マニュアル
 
webエンジニアのためのはじめてのredis
webエンジニアのためのはじめてのrediswebエンジニアのためのはじめてのredis
webエンジニアのためのはじめてのredis
 
MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法
 

Ähnlich wie マルチテナントのアプリケーション実装〜実践編〜

Dalvikバイトコードリファレンスの読み方 改訂版
Dalvikバイトコードリファレンスの読み方 改訂版Dalvikバイトコードリファレンスの読み方 改訂版
Dalvikバイトコードリファレンスの読み方 改訂版Takuya Matsunaga
 
[db tech showcase Tokyo 2015] A26:内部犯行による漏えいを防ぐPostgreSQLの透過的暗号化機能に関する実装と利用方法...
[db tech showcase Tokyo 2015] A26:内部犯行による漏えいを防ぐPostgreSQLの透過的暗号化機能に関する実装と利用方法...[db tech showcase Tokyo 2015] A26:内部犯行による漏えいを防ぐPostgreSQLの透過的暗号化機能に関する実装と利用方法...
[db tech showcase Tokyo 2015] A26:内部犯行による漏えいを防ぐPostgreSQLの透過的暗号化機能に関する実装と利用方法...Insight Technology, Inc.
 
Tech Dojo 02/09 IBM Japan CSM
Tech Dojo 02/09 IBM Japan CSMTech Dojo 02/09 IBM Japan CSM
Tech Dojo 02/09 IBM Japan CSM勇 黒沢
 
[db tech showcase Tokyo 2015] D35:高トランザクションを実現するスケーラブルRDBMS技術 by 日本電気株式会社 並木悠太
[db tech showcase Tokyo 2015] D35:高トランザクションを実現するスケーラブルRDBMS技術 by 日本電気株式会社 並木悠太[db tech showcase Tokyo 2015] D35:高トランザクションを実現するスケーラブルRDBMS技術 by 日本電気株式会社 並木悠太
[db tech showcase Tokyo 2015] D35:高トランザクションを実現するスケーラブルRDBMS技術 by 日本電気株式会社 並木悠太Insight Technology, Inc.
 
株式会社waja 安藤様 登壇資料
株式会社waja 安藤様 登壇資料株式会社waja 安藤様 登壇資料
株式会社waja 安藤様 登壇資料leverages_event
 
20151029 ヒカラボ講演資料
20151029 ヒカラボ講演資料20151029 ヒカラボ講演資料
20151029 ヒカラボ講演資料Daisuke Ando
 
復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0Yuta Matsumura
 
Node.jsアプリの開発をモダン化するために取り組んできたこと
Node.jsアプリの開発をモダン化するために取り組んできたことNode.jsアプリの開発をモダン化するために取り組んできたこと
Node.jsアプリの開発をモダン化するために取り組んできたことbitbank, Inc. Tokyo, Japan
 
The Twelve-Factor Appで考えるAWSのサービス開発
The Twelve-Factor Appで考えるAWSのサービス開発The Twelve-Factor Appで考えるAWSのサービス開発
The Twelve-Factor Appで考えるAWSのサービス開発Amazon Web Services Japan
 
技術選択とアーキテクトの役割
技術選択とアーキテクトの役割技術選択とアーキテクトの役割
技術選択とアーキテクトの役割Toru Yamaguchi
 
Cosmos DB 入門 multi model multi API編
Cosmos DB 入門 multi model multi API編Cosmos DB 入門 multi model multi API編
Cosmos DB 入門 multi model multi API編Takekazu Omi
 
CSI Driverを開発し自社プライベートクラウドにより適した安全なKubernetes Secrets管理を実現した話
CSI Driverを開発し自社プライベートクラウドにより適した安全なKubernetes Secrets管理を実現した話CSI Driverを開発し自社プライベートクラウドにより適した安全なKubernetes Secrets管理を実現した話
CSI Driverを開発し自社プライベートクラウドにより適した安全なKubernetes Secrets管理を実現した話Katsuya Yamaguchi
 
『これからの.NETアプリケーション開発』セミナー .NET用アプリケーション フレームワーク Open 棟梁 概説
『これからの.NETアプリケーション開発』セミナー .NET用アプリケーション フレームワーク Open 棟梁 概説『これからの.NETアプリケーション開発』セミナー .NET用アプリケーション フレームワーク Open 棟梁 概説
『これからの.NETアプリケーション開発』セミナー .NET用アプリケーション フレームワーク Open 棟梁 概説Daisuke Nishino
 
Mvc conf session_2_shibamura
Mvc conf session_2_shibamuraMvc conf session_2_shibamura
Mvc conf session_2_shibamuraHiroshi Okunushi
 
Prometheus超基礎公開用.pdf
Prometheus超基礎公開用.pdfPrometheus超基礎公開用.pdf
Prometheus超基礎公開用.pdf勇 黒沢
 
Azure でサーバーレス、 Infrastructure as Code どうしてますか?
Azure でサーバーレス、 Infrastructure as Code どうしてますか?Azure でサーバーレス、 Infrastructure as Code どうしてますか?
Azure でサーバーレス、 Infrastructure as Code どうしてますか?Kazumi IWANAGA
 
AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed な テレコムコアシステムを構築・運用してる話
AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed なテレコムコアシステムを構築・運用してる話AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed なテレコムコアシステムを構築・運用してる話
AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed な テレコムコアシステムを構築・運用してる話SORACOM,INC
 
QuickDemo HashiCorp Terraform with Microsoft Azure and VMware vSphere
QuickDemo HashiCorp Terraform with Microsoft Azure and VMware vSphereQuickDemo HashiCorp Terraform with Microsoft Azure and VMware vSphere
QuickDemo HashiCorp Terraform with Microsoft Azure and VMware vSphereWataru Unno
 
Software Development with Symfony
Software Development with SymfonySoftware Development with Symfony
Software Development with SymfonyAtsuhiro Kubo
 
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第5回 ‟配列と構造体„
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第5回 ‟配列と構造体„【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第5回 ‟配列と構造体„
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第5回 ‟配列と構造体„和弘 井之上
 

Ähnlich wie マルチテナントのアプリケーション実装〜実践編〜 (20)

Dalvikバイトコードリファレンスの読み方 改訂版
Dalvikバイトコードリファレンスの読み方 改訂版Dalvikバイトコードリファレンスの読み方 改訂版
Dalvikバイトコードリファレンスの読み方 改訂版
 
[db tech showcase Tokyo 2015] A26:内部犯行による漏えいを防ぐPostgreSQLの透過的暗号化機能に関する実装と利用方法...
[db tech showcase Tokyo 2015] A26:内部犯行による漏えいを防ぐPostgreSQLの透過的暗号化機能に関する実装と利用方法...[db tech showcase Tokyo 2015] A26:内部犯行による漏えいを防ぐPostgreSQLの透過的暗号化機能に関する実装と利用方法...
[db tech showcase Tokyo 2015] A26:内部犯行による漏えいを防ぐPostgreSQLの透過的暗号化機能に関する実装と利用方法...
 
Tech Dojo 02/09 IBM Japan CSM
Tech Dojo 02/09 IBM Japan CSMTech Dojo 02/09 IBM Japan CSM
Tech Dojo 02/09 IBM Japan CSM
 
[db tech showcase Tokyo 2015] D35:高トランザクションを実現するスケーラブルRDBMS技術 by 日本電気株式会社 並木悠太
[db tech showcase Tokyo 2015] D35:高トランザクションを実現するスケーラブルRDBMS技術 by 日本電気株式会社 並木悠太[db tech showcase Tokyo 2015] D35:高トランザクションを実現するスケーラブルRDBMS技術 by 日本電気株式会社 並木悠太
[db tech showcase Tokyo 2015] D35:高トランザクションを実現するスケーラブルRDBMS技術 by 日本電気株式会社 並木悠太
 
株式会社waja 安藤様 登壇資料
株式会社waja 安藤様 登壇資料株式会社waja 安藤様 登壇資料
株式会社waja 安藤様 登壇資料
 
20151029 ヒカラボ講演資料
20151029 ヒカラボ講演資料20151029 ヒカラボ講演資料
20151029 ヒカラボ講演資料
 
復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0復習も兼ねて!C#6.0-7.0
復習も兼ねて!C#6.0-7.0
 
Node.jsアプリの開発をモダン化するために取り組んできたこと
Node.jsアプリの開発をモダン化するために取り組んできたことNode.jsアプリの開発をモダン化するために取り組んできたこと
Node.jsアプリの開発をモダン化するために取り組んできたこと
 
The Twelve-Factor Appで考えるAWSのサービス開発
The Twelve-Factor Appで考えるAWSのサービス開発The Twelve-Factor Appで考えるAWSのサービス開発
The Twelve-Factor Appで考えるAWSのサービス開発
 
技術選択とアーキテクトの役割
技術選択とアーキテクトの役割技術選択とアーキテクトの役割
技術選択とアーキテクトの役割
 
Cosmos DB 入門 multi model multi API編
Cosmos DB 入門 multi model multi API編Cosmos DB 入門 multi model multi API編
Cosmos DB 入門 multi model multi API編
 
CSI Driverを開発し自社プライベートクラウドにより適した安全なKubernetes Secrets管理を実現した話
CSI Driverを開発し自社プライベートクラウドにより適した安全なKubernetes Secrets管理を実現した話CSI Driverを開発し自社プライベートクラウドにより適した安全なKubernetes Secrets管理を実現した話
CSI Driverを開発し自社プライベートクラウドにより適した安全なKubernetes Secrets管理を実現した話
 
『これからの.NETアプリケーション開発』セミナー .NET用アプリケーション フレームワーク Open 棟梁 概説
『これからの.NETアプリケーション開発』セミナー .NET用アプリケーション フレームワーク Open 棟梁 概説『これからの.NETアプリケーション開発』セミナー .NET用アプリケーション フレームワーク Open 棟梁 概説
『これからの.NETアプリケーション開発』セミナー .NET用アプリケーション フレームワーク Open 棟梁 概説
 
Mvc conf session_2_shibamura
Mvc conf session_2_shibamuraMvc conf session_2_shibamura
Mvc conf session_2_shibamura
 
Prometheus超基礎公開用.pdf
Prometheus超基礎公開用.pdfPrometheus超基礎公開用.pdf
Prometheus超基礎公開用.pdf
 
Azure でサーバーレス、 Infrastructure as Code どうしてますか?
Azure でサーバーレス、 Infrastructure as Code どうしてますか?Azure でサーバーレス、 Infrastructure as Code どうしてますか?
Azure でサーバーレス、 Infrastructure as Code どうしてますか?
 
AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed な テレコムコアシステムを構築・運用してる話
AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed なテレコムコアシステムを構築・運用してる話AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed なテレコムコアシステムを構築・運用してる話
AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed な テレコムコアシステムを構築・運用してる話
 
QuickDemo HashiCorp Terraform with Microsoft Azure and VMware vSphere
QuickDemo HashiCorp Terraform with Microsoft Azure and VMware vSphereQuickDemo HashiCorp Terraform with Microsoft Azure and VMware vSphere
QuickDemo HashiCorp Terraform with Microsoft Azure and VMware vSphere
 
Software Development with Symfony
Software Development with SymfonySoftware Development with Symfony
Software Development with Symfony
 
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第5回 ‟配列と構造体„
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第5回 ‟配列と構造体„【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第5回 ‟配列と構造体„
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第5回 ‟配列と構造体„
 

マルチテナントのアプリケーション実装〜実践編〜

  • 1. © 2022 LayerX Inc. 1 マルチテナントのアプリケーション実装 〜実践編〜 SaaSにおけるマルチテナント設計の悩みと勘所 SaaS.tech #2 2022.04.20
  • 2. © 2022 LayerX Inc. 2 自己紹介 株式会社LayerX 中川佳希 @yyoshiki41 バクラク請求書のテックリード バックエンドからフロントエンドまで. SaaS が扱う業務ドメインへの好奇心と サービスの成長に日々ワクワクを感じています. Gopher.
  • 3. © 2022 LayerX Inc. 3 1. 導入 a. マルチテナントSaaS 2. データベース設計 a. 設計パターン 3. アプリケーション側での実装 a. テーブルスキーマ b. 型(Type)の実装 c. コンテキスト(Request-scoped Data) d. ORM e. バリデーション f. ロギング / モニタリング g. テスト 章立て
  • 4. © 2022 LayerX Inc. 4 マルチテナント SaaS マルチテナント SaaS は・・・ 同質のソフトウェアを全テナントのユーザーへ提供. リソースの一部またはすべてをテナント間で共有. SaaS 提供側のメリット(シングルテナントアプリケーションと比較) ● 機能提供アウトカムの最大化 ● サービス運用の効率化 ● インフラコストの削減
  • 5. © 2022 LayerX Inc. 5 開発者を悩ますポイント 1. テナント毎に安全にデータを分離した状態で、アプリケーションを実装できるか? 2. テナント境界線をどのレイヤで実装するか? 3. テナント間のシステムリソース共有をどこまで行うか? マルチテナント SaaS が絶対に防ぐべきこと => データが他のテナントにも共有されてしまうこと(漏洩) マルチテナント SaaS
  • 6. © 2022 LayerX Inc. 6 マルチテナント SaaS での安全なデータ分離の実装 安全にテナント毎のデータを分離するには? ● データベース ● ミドルウェア ○ アプリ => ミドルウェア => データベース のような Proxy を想定 ● アプリケーション ● テスト 以降では低レイヤの部分から順にみていきます.
  • 7. © 2022 LayerX Inc. 7 SaaS 開発における最初の分岐点 1. データベースをテナント毎に作成 => データ所有者(テナント)を データベース名 で表現 2. データベースは共有、スキーマをテナント毎に作成 => データ所有者(テナント)を スキーマ で表現 3. データベースは共有、テーブルをテナント毎に作成 => データ所有者(テナント)を テーブル名 で表現 4. データベースは共有、テーブルも共有、 各テーブルがテナント識別カラムを持ち、レコード値で識別・分離 => データ所有者(テナント)を テーブルのカラム値 で表現 データベース設計 Isolated Shared
  • 8. © 2022 LayerX Inc. 8 1. データベースをテナント毎に作成 メリット 1. データの持ち方としては、もっとも堅牢 デメリット 1. マイグレーション(テーブルスキーマ変更)コスト 2. アプリからのデータベース接続コスト a. データベースユーザーが異なる場合, セッションも異なる 3. テナント作成時に、毎回データベースの初期化処理が必要 a. テーブル作成、データベースユーザーの認証設定 4. テナントとデータベースのリレーションが別途必要 Tenant B Tenant A
  • 9. © 2022 LayerX Inc. 9 スキーマとは... PostgreSQL などで名前空間を作成できる仕組み. データベースオブジェクト(テーブル、関数など)を 同じオブジェクト名でもスキーマが異なれば作成可能になる. MySQL ではオブジェクトを論理的に分ける (グルーピングする)仕組みに相当するものは見当たらず. 2. データベースは共有、スキーマをテナント毎に作成 Global Tables Tenant A Tables Tenant B
  • 10. © 2022 LayerX Inc. 10 2. データベースは共有、スキーマをテナント毎に作成 Global Tables Tenant A Tables Tenant B メリット 1. スキーマ単位で所有者を決めれる デメリット 1. マイグレーション(テーブルスキーマ変更)コスト 2. アプリからのデータベース接続コスト a. スキーマ所有者が異なる場合, セッションも異なる 3. テナント作成時に、毎回スキーマの初期化処理が必要 a. テーブル作成、スキーマ所有者の認証設定 4. テナントとスキーマのリレーションが別途必要
  • 11. © 2022 LayerX Inc. 11 メリット 1. 1つのデータベース内でテナントのデータを保持できる デメリット 1. マイグレーション(テーブルスキーマ変更)コスト 2. テナント作成時に、毎回テーブルの初期化処理が必要 a. テーブル作成 3. テナントとテーブル名のリレーションが別途必要 4. テナントのテーブル間で外部キーが煩雑になる 3. データベースは共有、テーブルをテナント毎に作成 Global Tenant A tables Tenant B tables
  • 12. © 2022 LayerX Inc. 12 ID TenantID Name 1 A Foo 2 B Bar メリット 1. マイグレーション(テーブルスキーマ変更)コストが低い 2. データベース側での設定コストが低い デメリット 1. 他テナントのレコードへアクセスが容易 a. 同一テーブルの為, 最もカジュアルにアクセス可能 2. ロジックを実装する必要がある a. アプリ側で制御する場合 i. WHERE 句 b. データベース側で制御する場合 i. Row-Level Security での制御 ii. ストアドプロシージャを実装しての制御 4. テーブルにテナント識別カラムを持ち, 行単位で制御 Global tables
  • 13. © 2022 LayerX Inc. 13 テーブルに識別カラムを持ち, テーブルへのポリシー × データベースユーザーで制御するをデータベース側で実装す る例 データベースユーザーのセッション管理などが一定ネックになる. ● PostgreSQL Row Level Security ○ PostgreSQL ネイティブの機能 ● Implementing row level security in MySQL / SQL Maestro ○ Trigger や View テーブルを使って, MySQL で RLS を実現する例 4. テーブルにテナント識別カラムを持ち, 行単位で制御
  • 14. © 2022 LayerX Inc. 14 アプリ => ミドルウェア => データベース のようなプロキシを想定. ● ProxySQL ○ mysql_query_rules: WHERE 句に TenantID がないクエリをはじく ● MariaDB MaxScale ○ Deny ルールを作る: WHERE 句に TenantID がないクエリをはじく 上記のような SQL を解釈できるプロキシミドルウェアで, ポリシー(とデータベースユーザーの掛け合わせ)での制御. ポリシーに沿わないレコードへのアクセスを拒否する. 実検証までは行っておらず 🙏 ミドルウェア(+α)
  • 15. © 2022 LayerX Inc. 15 4. テーブルにテナント識別カラムを持ち, 行単位で制御 主な理由 ● データベース、マイグレーション運用コストが最も低い ○ デプロイ(マイグレーション)難易度が高いサービスは致命的 ○ テナント追加時, データベース側の初期化処理のサブシステム等が不要 ● データベース側にロジックを寄せることでのロックインを避けたい ○ ストアドプロシージャなどの実装への依存も持ちたくない ● テナント数のスケールに最も適している ○ テナント毎のデータベースユーザーと管理, アプリからの接続セッションの管 理なども不要 ● ロジックのテストの行いやすさ バクラクでのデータベース設計
  • 16. © 2022 LayerX Inc. 16 4. テーブルにテナント識別カラムを持ち, 行単位で制御 テナント毎にデータを(論理的に)識別・分離して扱うことは, 至上命題. 実際の取り組み(ここからが本題) 1. テーブルスキーマ 2. 型(Type)の実装 3. コンテキスト(Request-scoped Data) 4. ORM 5. バリデーション 6. ロギング / モニタリング 7. テスト アプリケーション実装〜実践編〜 ID TenantID Name 1 A Foo 2 B Bar
  • 17. © 2022 LayerX Inc. 17 全テーブルへ冗長に TenantID (テナント識別カラム)を付ける (中間テーブル、子テーブルでも同様) 親テーブルにはマストで必要. 子テーブルでは従来, 親テーブルへの外部キー制約だけで充分. 冗長にカラムを付ける理由は, 1. ORM などで, WHERE句に TenantID を機械的に付けれる a. カラム有無を考えなくて良い 2. JOIN 時にも TenantID を機械的に付けれる 3. 子テーブルから自テナントデータのみを取得可能 4. 親, 子テーブルで異なる TenantID のデータを排除 5. シャーディングが必要になった際, キーに使える テーブルスキーマ ID TenantID Name 1 A Foo 2 B Bar parents ID ParentID TenantID Name 11 1 A Foo 22 2 B Bar children
  • 18. © 2022 LayerX Inc. 18 JOIN 時にも TenantID を機械的に付けれる 通常, 起きてはいけない不整合データを取得時に排除可能. ※ INSERT時に検知すべきかつ, 従来のリレーショナルモデル では考慮しなくてもよい問題ではある... 例.リレーションのあるテーブル間で異なる TenantID のレコード テーブルスキーマ ID TenantID Name 1 A Foo 2 B Bar parents ID ParentID TenantID Name 11 1 A Foo 22 2 XXX Bar children SELECT * FROM parents INNER JOIN children ON parents.ID = children.ParentID AND parents.TenantID = children.TenantID WHERE parents.TenantID = "B";
  • 19. © 2022 LayerX Inc. 19 子テーブルから自テナントレコードのみを取得可能 アプリの処理単位もテナント単位なので, 利用しやすいデータモデル. API 等から ID 指定でリソース取得するケースでも, 機械的に TenantID を付けてバリデーションできる. テーブルスキーマ ID TenantID Name 1 A Foo 2 B Bar parents ID ParentID TenantID Name 11 1 A Foo 22 2 B Bar children SELECT * FROM children WHERE TenantID = "B"; SELECT * FROM children WHERE TenantID = "B" AND ID IN ("ID1", "ID2", ...);
  • 20. © 2022 LayerX Inc. 20 シャーディングが必要になった際には, キーとして使用できる. Google’s F1 paper で, 分散データベースの階層型のデータモデル(The Cluster Hierarchical Model)が触れられている. リレーショナルモデルでは, 親テーブルへの外部キーを持つカラムだけでリレーションを表 現可能. しかし, 分散データベース環境化では従来のリレーショナルモデルの外部キーだ けではトランザクション, ジョインなどのコストが高価になる. 先祖(親)の ID をプライマリキーに含めることで, 物理的にも同じマシンでの処理が行え る. ● Google F1 ● Designing your SaaS Database for Scale with Postgres / Citus Data ● Sharding a multi-tenant app with Postgres / Citus Data テーブルスキーマ
  • 21. © 2022 LayerX Inc. 21 従来のリレーショナルモデルとの比較 テーブルスキーマ
  • 22. © 2022 LayerX Inc. 22 ユニークキーやインデックスも TenantID を軸に設計する 例えば name カラムにユニークキー制約をつけたい場合, TenantID を先頭(プレフィックス)に付けた複合キー にする. テーブルスキーマ CREATE TABLE `table_a` ( `id` int(11) NOT NULL, `tenant_id` varchar(36) NOT NULL, `name` varchar(36) NOT NULL, PRIMARY KEY `id`, UNIQUE KEY (`tenant_id`,`name`), CONSTRAINT `fk_tenant_id` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ) ENGINE=InnoDB; ID TenantID Name 1 A Foo 2 B Bar
  • 23. © 2022 LayerX Inc. 23 複合キーは列挙したカラム順に連結して, 内部値としてインデックスされる. WHERE tenant_id = “B” (インデックスのプレフィックス) だけでも既知の範囲に絞るイ ンデックスとして機能する. アプリが使用するテナントのレコードへの効率的なクエリになる. PrimaryKey を用いないクエリでは, 必須のインデックスになる. ※ PrimaryKey を用いる場合も, 後述する Context が持つ TenantID と一致している か検証するため, WHERE 条件として使用している tips:複合キーのカラムを ForeignKey として使用する際の話 MySQL 外部キー制約とインデックスに必要な知識 - LayerX エンジニアブログ テーブルスキーマ
  • 24. © 2022 LayerX Inc. 24 A defined type in Go TenantID という型を新たに定義して使用 underlying type は, プリミティブな string 型 A defined type の主なメリット ● 関数引数, 返り値の間違いや代入ミスをコンパイル時にエラーにできる ● 独自メソッドを実装できる 型(Type)の実装 type TenantID string
  • 25. © 2022 LayerX Inc. 25 Generate go model from a database table schema テーブル定義から, Go の Struct 生成を行う. xo というツールで自動生成. TenantID カラム生成時には, 独自定義した型を使用している. 型(Type)の実装 type TableA struct { ID string TenantID TenantID Name string }
  • 26. © 2022 LayerX Inc. 26 リクエストを受け取ったAPIサーバーは, コンテキストに TenantID を入れる ユーザーの認証後に特定されたテナント情報をアプリのMiddleware層でセット. 以降は, この TenantID をもとにレスポンスまで処理を行っていく. コンテキスト(Request-scoped Data) func ContextWithTenantID(ctx context.Context, tenantID TenantID) context.Context { return context.WithValue(ctx, ctxKeyTenantID, tenantID) }
  • 27. © 2022 LayerX Inc. 27 バッチ処理などの実装 テナント毎に処理を実行するように関数を実装する ※ テナントをまたいで処理を行う関数を極力実装しない コンテキスト(Request-scoped Data) func ProcessPerTenant(ctx context.Context, input string) context.Context { // do stuff… }
  • 28. © 2022 LayerX Inc. 28 ORM gorm の Callback plugin を実装. WHERE 句に自動で TenantID 条件がつくようにしている. func NewGlobalDB(conf *mysql.Config) { … // Register callback functions db.Callback().Query().Before("gorm:query").Register("my_plugin:before_query", callbackTenantID) db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", callbackTenantID) db.Callback().Delete().Before("gorm:delete").Register("my_plugin:before_delete", callbackTenantID) db.Callback().RowQuery().Before("gorm:row_query").Register("my_plugin:before_row_query", callbackTenantID) }
  • 29. © 2022 LayerX Inc. 29 ORM 登録する Callback 関数の実装は, Struct 内の TenantID フィールドを検出して, WHERE 句をセットする. (SELECT / CREATE / UPDATE / DELETE) アプリから呼び出す際には, TenantID を引数に渡してコールバック関数が登録された DBインスタンスを仕様する. err := app.NewDB(db, tenantID).First(&model).Error
  • 30. © 2022 LayerX Inc. 30 一部のメソッド(Raw, Exec)では, callback 関数では対応出来ない.. ※ Raw, Exec は, 記述したSQLをそのまま実行できる機能 ● 基本的に使用させない.(CIでの検知) ● 管理画面など, 例外的には使用可能 ORM var result Result err := db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
  • 31. © 2022 LayerX Inc. 31 callback 関数が発火せずに実行されたクエリに対しては, ログを出力するようにもしている. 意図せずに, TenantID を指定しないクエリを発見出来るようにする. ORM
  • 32. © 2022 LayerX Inc. 32 バリデーション APIサーバーの終端でレスポンスデータのバリデーションを実行 RDS含め Redis, ElasticSearch, DynamoDB などデータソース全体で, 他のテナントデータが含まれていないことを検証する最後の砦. 関数内で再帰的にレスポンスオブジェクトの TenantID フィールドが一致していることを 検証. Go で書くには骨が折れる reflect での処理. func ValidateTenantID(tenantID TenantID, obj interface{}) { // do validation… }
  • 33. © 2022 LayerX Inc. 33 ログには常に TenantID を含めて出力 logger 側で Context 内の TenantID 自動で出力するように実装 アプリからの呼び出し: モニタリングツール(Datadog)上でも, Log Facets としてフィルタも可能. トラブルシュート時にデータソース特定や カスタマー連絡に役立ちます. ロギング / モニタリング app.LogError(ctx, err).Send()
  • 34. © 2022 LayerX Inc. 34 TenantID がついていないSQLクエリログをモニタリングツールで監視 正規表現でのチェックが必要なため, まだまだ調整中 (理想はSQLパーサーでアラート条件を作れること) ロギング / モニタリング
  • 35. © 2022 LayerX Inc. 35 単体テスト毎にテナントを作成して, 並列に実行する ● 他テナントデータに影響を与える場合, テストで検知出来る ○ 他のテナントへ相互に影響を与える機能が存在しない ● パッケージ内の全テスト完了まで, テストデータをドロップ(削除)しない ● テスト/サブテスト並列度を上げる動機づけになる ● テナントセットアップは, ヘルパー関数を用意 テスト func TestA(t *testing.T) { helper.SetupTenant(t) t.Run(“Case1”, func(t *testing.T) { t.Parallel(t) // run tests… } }
  • 36. © 2022 LayerX Inc. 36 ● アプリケーション実装でのテナントデータの論理的な分離を行う方針を取っていま す. ○ データベースやミドルウェアでの制御でも, ポリシー × ユーザーでのロジック 実装部分は避けられないと思います. ● 開発/テスト/デプロイのコストの低さやロックインを避けれるなどメリットはあります. ○ その反面, アプリ側でのガードレール実装の重要度は高くなります. ● 安全かつ開発スピードも落ちない仕組みの改善は進めていきます. まとめ