Salesforceでカスタムオブジェクトを作っても、Orgごとに専用の物理テーブルが増えるわけではない。オブジェクトや項目の定義をメタデータとして持ち、共有のデータ構造をOrgごとに異なるスキーマとして見せる。そのデータを扱う現在のトランザクション基盤が、クラウド向けに設計されたSalesforceDBだ。
ここで話が混ざりやすい。Orgは顧客の論理的な区画。複数のOrgを収容するサービスの単位がHyperforce Cellで、Cell内のトランザクションDBがSalesforceDBだ。
メタデータによる「何の項目があるか」という説明と、ログやデータファイルによる「どう保存するか」という説明も、扱う観点が違う。
この記事では、Salesforceが公開しているマルチテナントの技術資料と現在のプラットフォーム構成をつなげて整理する。
カスタムオブジェクトは、メタデータから組み立てられる
たとえば、あるOrgで Customer__c というカスタムオブジェクトを作り、Age__c という数値項目を追加する。通常のRDBを思い浮かべると、CREATE TABLE Customer__c (...) のような処理を想像するかもしれない。Salesforceの説明では、新しいオブジェクトに対応する物理テーブルをその都度作るのではなく、定義をメタデータとして保存し、実行時にアプリケーションの構成要素を組み立てる。
その仕組みを説明する公開モデルが、Universal Data Dictionary(UDD)だ。Salesforceの資料には、オブジェクト定義を持つ MT_Objects、項目定義を持つ MT_Fields、レコードを保持する MT_Data が登場する。MT_Data の Value0 から ValueN は、項目の値を格納する flex column(slot)として説明されている。同じslotでも、オブジェクトが違えば別の項目に対応できる。
Org A: Customer__c.Age__c → Value1
Org B: Contract__c.Term__c → Value1
↑ ↑
異なる論理項目 共有構造上の同じslot
この図は概念図だ。公開資料からは、MT_* が現在のSalesforceDB内に同じ物理テーブルとして存在するか確認できない。UDDの資料が説明するのは、Orgごとに異なる論理スキーマを共有基盤でどう表すか、という部分である。
共有するからこそ、テナントの識別が欠かせない。マルチテナントの資料では、Org固有のレコードに OrgID が付き、プラットフォームがDBにアクセスする際にそれを使うと説明されている。現在のSalesforceDBの資料でも、マルチテナント表の主キーにテナントIDを含め、LSM内でテナントごとのデータを近くに配置する構成が示されている。どちらも、共有データにテナント識別子を付ける考え方を示す。ただし、両資料の OrgID とテナントIDが同一カラムを指すとは公開情報だけでは言えない。
Orgを収容する単位がHyperforce Cell
Salesforce全体に一つの巨大なDBがある、と考えると構成を見誤る。先ほどのマルチテナント資料でいう「単一の共有DB」は、一つのSalesforce Platform instance の説明だ。
現在のHyperforceでは、従来のSalesforce instanceに相当する単位を Cell と呼ぶ。Salesforceのアーキテクチャ資料によると、Cellには一つ以上のOrgが入り、スケールの単位であると同時に、障害の影響範囲を区切る境界にもなる。各Orgが属するCellにはSalesforceDBサービスが含まれる。
Hyperforce
├─ Cell A
│ ├─ Org A / Org B / ...
│ ├─ Platform Runtime
│ └─ SalesforceDB
└─ Cell B
├─ Org C / Org D / ...
├─ Platform Runtime
└─ SalesforceDB
サービス間の関係を示す概念図で、Hyperforce InstanceやFunctional Domainなどの階層は省略している。
CellはDBの別名ではない。Orgのメタデータを解釈してアプリケーションを動かすランタイムや、DBを含むサービス群の単位だ。現在のプラットフォームには、メタデータで定義されたオブジェクトをDB上のデータと対応づけるオブジェクト・リレーショナル・マッパー(ORM)もある。
SalesforceDBはトランザクション処理を担う
Salesforceの説明では、SalesforceDBはPostgreSQLを拡張したクラウドネイティブなリレーショナルDBで、CRMのトランザクションデータを扱う。SQLを処理する計算資源(Compute)と、データを保持するストレージ(Storage)を分けている。
SQL Compute ── Storage Cache ── Cloud Storage
Cloud Storageを最終的なデータの保管先とし、遅延を抑えるためにStorage Cacheを置く。キャッシュはトランザクションログ用とデータファイル用に分かれる。公開資料では、SQL Computeは3つのAvailability Zoneにまたがる。変更を扱うPrimary Clusterが一つ、問い合わせを扱うStandby Clusterが二つある。
Computeを増やす際は、共有された不変のストレージから読めるノードを追加できる。これは「ノードを足すたびに、そのノード専用のデータ一式を移す」というモデルとは異なる。ただし、どの要求をどのノードへ振り分けるかという内部のルーティング規則までは、公開資料からは分からない。
SalesforceDBは記録系の処理を担う。分析やAI向けの大量データ処理は、Salesforceが別に説明しているData 360の領域だ。両者を同じ「Salesforceのデータ基盤」として一括りにすると、トランザクションDBの設計意図が見えにくくなる。
LSMは、確定した変更を不変のファイルへ整理する
Computeが共有ストレージを読みやすくするうえで、不変のデータファイルが効いている。SalesforceDBは変更をログに記録してメモリに蓄え、確定した変更をキー順のデータファイルへまとめる。このデータ構造が Log-Structured Merge Tree(LSM) だ。公開資料に沿った流れは次のとおり。
変更 → トランザクションログ + メモリ
→ コミット
→ 確定済みの変更をキー順のデータファイルへ書き出す
→ ファイルを後からマージ・コンパクションする
一度公開されたデータファイルは、その場で書き換えない。複数のCompute Nodeが同じファイルを読むとき、読み取り中にファイルの中身が変わらない性質は都合がよい。Salesforceも、不変ストレージがノード間の調整なしの読み取りとスケールに役立つと説明している。
ここでコミットとコンパクションは別の処理だ。コミットはトランザクションを確定する。コンパクションは、後から増えたファイルを整理してストレージ効率などを改善する。コンパクションを待って初めて保存が成功するわけではない。Salesforceの資料によれば、コミットは複数のAvailability Zoneにまたがり、障害時には進行中のトランザクションを中止し、コミット済みのものを復旧する。
LSMだけでComputeとStorageの分離が成立するわけでもない。SalesforceDBは、両者の分離と不変ストレージ、LSMを組み合わせている。
論理スキーマと保存方式をつなげて読む
Customer__c に項目を追加するときは、UDDなどのメタデータによる論理スキーマを見る。レコードを保存するときは、プラットフォームがOrgのメタデータを使って項目や処理を解釈し、SalesforceDBでトランザクションを確定する。DB側では変更をログとメモリに記録し、コミット済みの変更を後から不変のデータファイルへまとめる。
ここまでが公開資料から組み立てた概念的な流れであり、画面に成功が表示される時点と内部の各処理の厳密な順序を示すものではない。
OrgとCellは、顧客とサービスの収容範囲を示す。UDDはOrg固有の論理スキーマを、SalesforceDBのCompute・Storage Cache・LSMは保存とスケールの仕組みを説明する。それぞれの役割を分けて見ると、Salesforceのマルチテナント設計が読み解きやすくなる。
参照した一次資料
- Platform Multitenant Architecture — UDD、共有データ構造、OrgID
- The Salesforce Platform - Transformed for Tomorrow — Hyperforce Cell、SalesforceDB、ComputeとStorage、LSM