【テクニカル・上級編】 カラムとデータ型 – Cloud Spanner

Cloud Spannerの型システム:分散トランザクションを支える「メモリの正体」

Cloud Spannerは、単なるマネージドなRDBではない。これは、TrueTimeによって物理時間を論理的な一貫性と整合させた、分散コンピューティングの金字塔だ。

多くのエンジニアはSpannerのデータ型を「SQL準拠のインターフェース」として消費するが、真のアーキテクトであれば、そのデータ型がColossus上のSSTableにどう物理配置され、どれだけのメモリフットプリントを消費し、分散クエリエンジンがどう解釈するかを理解しておく必要がある。

今日は、Spannerのデータ型を「物理層」から解剖する。

—

1. 固定長か可変長か:メモリ最適化の極意

Spannerの内部ストレージは、Googleの伝統的なエンジンと同様、効率的な圧縮アルゴリズム(SnappyやZlib等の派生)と、列指向に近いレイアウトの恩恵を受けている。

  • INT64 / FLOAT64 / BOOL: これらはハードウェアネイティブに近い。特にINT64は、固定長として扱われるため、ソートや比較演算においてCPUサイクルを最小限に抑えることができる。
  • STRING / BYTES: これらは可変長だ。注意すべきは「最大長」の設定である。`STRING(MAX)`は便利だが、インデックスのオーバーヘッドを考慮せよ。Spannerのインデックスは物理的に別のテーブルとして保持される。`STRING(MAX)`をインデックスのキーに含めると、インデックスサイズが爆発し、キャッシュヒット率が急落する。

アーキテクトの戒律:
「キー設計においては、可能な限り物理サイズを小さくせよ。インデックスのリーフノードがRAM(Spannerのデータキャッシュ)に乗り切るか否かが、レイテンシを1桁変える。」

2. TIMESTAMPとTrueTimeの共犯関係

`TIMESTAMP`型は、Spannerの心臓部だ。これは単なる時刻データではない。Spannerのマルチバージョン同時実行制御(MVCC)において、`TIMESTAMP`はデータの「有効期限」や「トランザクションの順序」を定義するタグとして機能する。

— トランザクション内での特定タイムスタンプ指定
— 読み取り専用トランザクションでこれを使うと、
— 指定時刻のデータスナップショットに静止する。
SELECT FROM Orders@{FORCE_PROTECT_SNAPSHOT=true}
WHERE CommitTimestamp < '2023-10-27T10:00:00Z'; このクエリが実行されるとき、Spannerは物理的に最新のSSTableだけでなく、不要領域回収(Garbage Collection)が行われる前の古いバージョンを、`TIMESTAMP`をキーとして引き抜く。この「時間旅行」のコストは、`TIMESTAMP`がインデックスの先頭にあるかどうかで劇的に変わる。

3. JSON型:柔軟性とパフォーマンスのトレードオフ

Spannerの`JSON`型は、単なる文字列ではない。内部的には解析済み(Parsed)のバイナリ形式で保持される。これにより、JSON内の特定のパスに対するクエリを高速化できる。

しかし、注意が必要だ。`JSON`型のフィールドに対してインデックスを張る場合、`JSON_VALUE`関数を使って抽出した値をインデックス化することになる。

— JSON内の特定キーに対するインデックス定義
CREATE INDEX idx_user_email ON Users (JSON_VALUE(metadata, ‘$.email’));

このインデックスは、JSON全体をスキャンするコストを排除するが、書き込み時のオーバーヘッドは増大する。高頻度で更新されるテーブルに対してJSONを多用し、安易にインデックスを張るのはアーキテクト失格と言わざるを得ない。

4. ARRAY型:ネストした構造の物理配置

`ARRAY`はSpannerにおける「非正規化」の強力な武器だ。RDBの伝統的な「1対多」をテーブル結合なしで実現できる。

内部的には、`ARRAY`の中身は同じ行内のローカルに保持される(SSTableのレコードに含まれる)。つまり、親レコードを読み込むと、付随するARRAYのデータも一括でメモリにロードされる。

極限の知見:
配列が巨大化すると、1行あたりのサイズが肥大化し、スキャン効率が悪化する。数千要素を超える配列を1行に詰め込むと、特定のフィールドだけを読み取りたいクエリでも、巨大な配列のデシリアライズコストを支払うことになる。「1行のサイズは数MB以内に収める」という鉄則は、Spannerにおいても健在だ。

5. NUMERIC型:精度と計算コストの狭間で

金融系システムで必須の`NUMERIC`型(最大38桁の10進数)は、内部的には可変精度のバイナリ表現だ。`FLOAT64`のような浮動小数点演算の誤差はないが、比較演算は`INT64`よりも高コストになる。

もしパフォーマンスがボトルネックなら、スケーリング係数(例: 円単位なら100倍してINT64で持つ)を検討すべきだ。ただし、ビジネスロジックの複雑化と引き換えになる。アーキテクチャとは、常に「トレードオフの最適解を出す」作業である。

—

まとめ:アーキテクトへの提言

Spannerにおいて、データ型を選択するということは、「そのデータが物理的にどう並び、どの程度のCPUを消費し、どの程度の頻度でキャッシュから追い出されるか」を決定するということだ。

  • 頻繁にアクセスするキーは固定長に寄せる。
  • インデックスのリーフサイズを意識する。
  • ARRAYの肥大化はスキャン性能を殺す。
  • JSONは便利だが、その代償は「書き込みレイテンシ」で支払う。

ツール(型)を盲目的に使うのではなく、それがSpannerという巨大な分散エンジンの中でどう振る舞うかを想像せよ。それが、システムを「動くもの」から「止まらない、速いもの」へと昇華させる唯一の道だ。

コメント

タイトルとURLをコピーしました