「Spannerはスケールする。だから選んだ」――もし君がこの理由だけでCloud Spannerをアーキテクチャのコアに据えているなら、それはこの偉大な分散データベースの価値の半分、それも極めて表面的な部分しか見ていない。
真に驚異的なのは、グローバル規模で強整合性(External Consistency)を担保しながら、ミリ秒未満のレイテンシで「ゼロトラスト」なアクセス制御をクエリ実行エンジンの最深部で強制(Enforce)する、そのエレガントな内部アーキテクチャだ。
多くのエンジニアは「IAMで認証しているから安全だ」と漠然と考え、その裏で何が起きているかを知らない。しかし、ミッションクリティカルなシステムを設計・運用するテクニカルリードであるならば、クエリが分散ノードを駆け巡る中で、どのように認証・認可のコンテキストが伝播し、各ストレージレイヤで検証されるのかを完全に把握しておく必要がある。
今回は、SpannerのAPIフロントエンドから、最下層の分散ストレージノード(Spanserver)に至るまでの「アクセス制御の強制メカニズム」を、その内部フローとともに解剖する。
—
1. 認証・認可の分散ライフサイクル:クエリが流れる「3つのゲート」
クライアントが発行した1行のSQL、あるいはAPIリクエスト。これがデータにアクセスするまでに、Spannerは単一のモノリシックな壁ではなく、「多層防御(Defense in Depth)」の分散アーキテクチャを展開する。
全体像は以下の3つのフェーズに大別される。
[Client]
|
| (1) gRPC / OAuth2 Token
v
[Spanner API Frontend (FE)] <---> [Google IAM Policy Cache]
| 認証・論理認可の確定
| クエリのパース、AST生成
| データベースロールの適用(FGAC)
| 署名付き「セキュリティコンテキスト」の生成
|
| (2) Distributed Query Plan + Security Context (gRPC RPC)
v
[Root / Intermediate Node]
| 実行プランの分散分割(Split単位)
|
| (3) Subquery Execution + Security Context
v
[Leaf Node (Spanserver)]
| ローカルでの署名検証(暗号論的検証)
| 物理データ(Colossus/SSTable)へのアクセス強制
v
[Storage Layer]
フェーズ1:API Frontend (FE) における「ゲートキーパー」と論理認可
リクエストはまず、Spannerのゲートウェイである API Frontend (FE) に到達する。ここはgRPCの終端であり、Google Cloudの認証基盤(IAM)と最も密接に連携する場所だ。
1. アイデンティティの確定(Authentication):
クライアントが提示したOAuth2アクセストークン(またはOIDCトークン)は、FEで検証される。この際、外部のIAMサービスに毎回同期問い合わせを行うと致命的なボトルネックになる。そのため、FEは高度に最適化されたIAMポリシーのローカルキャッシュ(Policy Cache)を保持しており、ミリ秒未満で呼び出し元のアイデンティティ(サービスアカウント、ユーザー)を確定させる。
2. データベースロール(Database Role)の解決:
Spannerは、より細粒度なアクセス制御(FGAC: Fine-Grained Access Control)をサポートしている。FEは、セッション確立時に指定されたデータベースロール(例:`Reader`, `DataAnonymizer`)が、そのアイデンティティに付与されているかを検証する。
フェーズ2:クエリコンパイラによる「Query Rewrite(クエリ書き換え)」
認可が通ると、FE内のクエリコンパイラがSQLをパースし、抽象構文木(AST)を生成する。ここでFGAC(行・列レベルのセキュリティ)の強制が静的に行われる。
例えば、あるユーザーが「メールアドレス(`email`)」列へのアクセス権を持たないロールでアクセスしてきた場合、エンジンはクエリを内部的に書き換える(Query Rewrite)。
— 元のクエリ
SELECT user_id, email, signup_date FROM Users;
— FGAC適用後の内部書き換え(一例)
SELECT user_id, CAST(NULL AS STRING) AS email, signup_date FROM Users;
この書き換えは、実行プラン(Query Execution Plan)が生成される前に行われる。つまり、無許可のデータは、この段階で実行パイプラインから物理的に排除されるのだ。
フェーズ3:分散ノード(Spanserver)への「セキュアなコンテキスト伝播」
ここからがSpannerの真骨頂だ。Spannerはデータを「Split(スプリット)」と呼ばれる単位に分割し、複数の物理ノード(Spanserver)に分散配置している。クエリは複数のサブクエリに分解され、それぞれのデータを保持するLeafノードへ分散RPCとして送信される。
ここで疑問が生じる。「Leafノードは、受け取ったリクエストが本当に認可されたものか、どうやって判断するのか?」
まさか、末端のLeafノードが毎回IAMサービスやFEに「このクエリ、通していい?」と問い合わせるわけにはいかない。そんなことをすれば、分散データベースのスケールアウト性能は完全に崩壊する。
これを解決するのが、「暗号署名付きセキュリティコンテキスト(Capability Token)」の伝播である。
1. FEは、認可されたセッション情報、適用されたデータベースロール、アクセス可能なSplitの範囲などをカプセル化したセキュリティトークンを生成する。
2. このトークンは、Spannerの内部認証局(CA)の秘密鍵で暗号署名される。
3. 分散クエリプランのRPC(Remote Procedure Call)ヘッダーに、このトークンが埋め込まれる。
4. Leafノード(Spanserver)は、パブリックキー(公開鍵)を用いてローカルでこの署名を検証する。署名が正当であり、要求されたデータ(Split)がトークンの許可範囲内であれば、ストレージエンジン(Colossus/SSTable)への物理アクセスを実行する。
この仕組みにより、Spannerは「中央集権的な認可の厳格さ」と「完全分散型アーキテクチャの超高速性」を極限の次元で両立させている。
—
2. Fine-Grained Access Control (FGAC) の具現化とDDL設計
実務において、この強力なアクセス制御をどのように設計に落とし込むべきか。
SpannerのFGACを使いこなすための、具体的かつ堅牢なDDLおよびアクセス制御のパターンを示す。
今回は、マルチテナント型SaaSにおいて、「一般スタッフ(`staff`)」と「監査役(`auditor`)」でアクセスできるデータ(顧客の個人情報、決済情報)を厳密に制御するユースケースを考える。
DDLによるロールと行・列レベルセキュリティの定義
— 1. データベースロールの作成
CREATE ROLE staff;
CREATE ROLE auditor;
— 2. テーブル定義(通常のデータモデル)
CREATE TABLE Customers (
CustomerID STRING(36) NOT NULL,
Name STRING(100) NOT NULL,
Email STRING(255) NOT NULL,
CreditCardNum STRING(19) NOT NULL,
Region STRING(10) NOT NULL,
) PRIMARY KEY (CustomerID);
— 3. 特権ロール(auditor)には全テーブル・全カラムの参照権限を付与
GRANT SELECT ON TABLE Customers TO ROLE auditor;
— 4. 一般ロール(staff)には、特定の非機密カラムのみ参照を許可(列レベルセキュリティ)
GRANT SELECT(CustomerID, Name, Region) ON TABLE Customers TO ROLE staff;
— 5. 行レベルセキュリティ(Row-Level Security: RLS)の定義
— staffロールは、自分の所属するRegion(ここでは ‘APAC’ とする)のデータしか見られないように制限する
CREATE POLICY apac_staff_policy ON Customers
FOR SELECT
TO ROLE staff
USING (Region = ‘APAC’);
クエリ実行プランにみる「Query Rewrite」の実態
`staff` ロールで接続したクライアントが、以下の単純なクエリを実行したとする。
— staff ロールとして実行
SELECT FROM Customers;
このとき、Spannerのクエリプランナーは、内部で以下のような実行プランを組み立てる。
1. アクセス不可能な列の排除: `SELECT ` は、認可されていない `Email` および `CreditCardNum` を除外した `SELECT CustomerID, Name, Region` に動的に書き換えられる。
2. ポリシー(Filter)のインジェクション: `WHERE Region = ‘APAC’` が自動的に挿入される。
実行プラン(EXPLAIN)のイメージ:
Distributed Cross Apply
+- Distributed Union (Split Range Scan)
+- Filter (Condition: Region = ‘APAC’) <-- RLSがクエリプランの最深部に注入されている
+- Table Scan (Table: Customers)
ここが設計の急所だ。
RLS(行レベルセキュリティ)によって `Region = ‘APAC’` というフィルタが自動挿入されるということは、`Region` カラムに適切なインデックス(Secondary Index)が存在しない場合、Spannerはフルスキャン(Full Table Scan)を実行せざるを得なくなる。
セキュリティを強化した結果、システム全体のパフォーマンスが死滅する――これは、クエリエンジンの内部挙動を理解していないエンジニアが必ず踏む地雷である。
—
3. 実務で勝つための「極限の最適化」とアンチパターン
Spannerのアクセス制御の強制メカニズムを理解した上で、実稼働環境で高スループットを維持するためのテクニカルリードとしてのプラクティスを授けよう。
アンチパターン1:Session Churning(セッションの頻繁な再作成)
SpannerのSDK(Java, Go, Node.jsなど)は、バックグラウンドで「セッションプール」を管理している。Spannerにおける「セッション」とは、単なるTCPコネクションではなく、「認証・認可コンテキスト(データベースロールなど)を紐付けた論理的な状態」である。
もし君のコードが、リクエストごとにセッションを明示的に作成(Create)して破棄(Delete)しているなら、即刻それを止めさせろ。
// 悪しき実装例:リクエストごとにセッション(クライアント)を再作成している
func HandleRequest(ctx context.Context) {
// 毎回IAMの認証・認可プロセスが走り、API FEのキャッシュをバイパスしてオーバーヘッドが発生する
client, _ := spanner.NewClientWithConfig(ctx, db, spanner.ClientConfig{
DatabaseRole: “staff”,
})
defer client.Close()
// クエリ実行…
}
対策:
データベースロールごとに、長期生存する(Long-lived)クライアントインスタンスをプール化し、再利用せよ。Spanner SDKは、プール内のセッションを再利用する際、すでに認証・認可が完了しているセッションコンテキストを再利用するため、IAM検証のオーバーヘッドは事実上ゼロになる。
アンチパターン2:RLS(行レベルセキュリティ)を考慮しないインデックス設計
先ほど触れた通り、RLSはクエリプランを書き換える。
例えば、マルチテナントで `TenantID` に基づくRLSを導入する場合、すべての参照クエリに `WHERE TenantID = @tenant_id` が暗黙的に追加される。
対策:
RLSのポリシーで使用するカラム(例:`TenantID`, `Region`)は、必ずセカンダリインデックスの先頭キー(Leading Column)にするか、インターリーブ(Table Interleaving)の親キーとして設計せよ。
— 悪い例:RLSで Region を使うのに、インデックスが Name にしか効いていない
CREATE INDEX idx_customers_name ON Customers(Name);
— 良い例:RLSのフィルタがインデックスにヒットするように複合インデックスを設計する
CREATE INDEX idx_customers_region_name ON Customers(Region, Name);
—
4. テクニカルリードとしての決断
「セキュリティとパフォーマンスはトレードオフである」
凡庸なエンジニアはこの言葉を言い訳に、セキュリティを甘くするか、あるいはパフォーマンスの低下を許容する。しかし、Cloud Spannerのアーキテクチャを脳内にマッピングしている我々は、そのどちらも妥協しない。
Spannerのアクセス制御は、境界防御(Perimeter Security)のように「外壁を高くする」思想ではない。「データそのものが、分散ノードの末端に至るまで、自律的に自身のアクセス権を検証し、執行する」という、極めてモダンなゼロトラスト思想に基づいている。
- API Frontendでの論理的なQuery Rewrite
- 暗号署名付きトークンによるノード間でのセキュアなコンテキスト伝播
- RLSを考慮したインデックス設計による、ミリ秒未満のクエリ実行の維持
これらを完全にコントロール下におくことで、君の設計するシステムは、いかなるスケールにおいても、堅牢でありながら俊敏であり続けることができる。コードレビューや設計審議の場では、ぜひこの「クエリ実行エンジン内部の動き」を引き合いに出し、チームを正しい設計へと導いてほしい。
コメント