Cloud Spannerの深淵:NULLという「不在」が物理レイヤーでどう振る舞うか
Cloud Spannerを単なる「SQLが使える分散DB」と定義しているうちは、その真価の半分も見えていない。Spannerは分散合意アルゴリズムであるPaxosと、分散トランザクションプロトコルである2PCを極限までチューニングし、グローバル規模での強整合性を実現した「分散分散OS」に近い存在だ。
今回は、一見単純に見える`NULL`値が、Spannerの内部ストレージエンジン(Colossus上で動作するSSTable)やクエリ実行計画において、いかに「最適化の対象」となり、あるいは「パフォーマンスのボトルネック」になり得るか、その深層を解き明かす。
—
1. 物理ストレージにおける「NULL」の正体
Spannerのデータは、内部でキーバリューストア(KV)構造として管理されている。具体的には、行データは「主キーをエンコードしたバイト列」をキーとし、「カラム値の集合」をバリューとするSSTable形式で物理ディスクに書き込まれる。
ここで重要なのは、SpannerはNULL値を物理的に保存しないということだ。
- スパース性の活用: Spannerの列指向ストレージにおいて、NULL値は「存在しないこと」として扱われる。データ構造上、NULLが挿入されたカラムは、その行の該当フィールドにおける値のオフセットが存在しないか、空の状態としてマークされる。
- ストレージ効率: これは非常に強力な特性だ。数千の列を持つ巨大なテーブルにおいて、多くのカラムがNULLである場合、物理的なストレージ消費量は劇的に削減される。大規模な分析クエリを投げる際、ストレージエンジン側でこの「NULLの欠如」を早期にスキップできれば、I/O負荷は低減する。
2. インデックスにおけるNULLの挙動:隠れた「トリアージ」
Spannerにおいてインデックスは、テーブルそのものと同じく「キーの順序付きマップ」である。ここにNULLが混ざると、クエリ実行計画に致命的な影響を与える可能性がある。
- NULLは「最小」か「最大」か: Spannerのインデックスにおいて、NULLは昇順(ASC)ソートにおいて「最小値」として扱われる。
- インデックス・スキャンへの影響:
`WHERE column_a IS NULL` というクエリを投げたとき、オプティマイザが適切にインデックスを選定できれば、インデックスの先頭(または末尾)からスキャンを開始するだけで済む。しかし、`IS NOT NULL` の場合、インデックス全体を走査するフルスキャンに近いコストが発生する場合がある。
- 複合インデックスの罠:
`INDEX(a, b)` を作成した場合、`(NULL, 1)` というエントリは、`(1, 1)` よりも手前に配置される。この「NULLの物理配置」を計算に入れずにインデックス設計をすると、特定のクエリでオプティマイザがインデックスを無視(フルテーブルスキャン)する事態を招く。アーキテクトであれば、インデックスのカーディナリティだけでなく、NULLの分布率まで計算に入れて設計すべきだ。
3. クエリ実行時のNULL最適化と三値論理のコスト
SQL標準に従い、Spannerのクエリエンジンは三値論理(TRUE, FALSE, UNKNOWN)をサポートしている。だが、この「UNKNOWN」を処理するための比較ロジックは、CPUサイクルを消費する。
— 最適化の観点からの注意
— NULLが含まれる可能性のあるカラムに対する比較は、
— 内部的に IS NULL チェックを伴う分岐を生成する可能性がある。
SELECT FROM Users WHERE age > 20;
— もし age が NULL を許容する場合、オプティマイザは
— “age IS NOT NULL AND age > 20” という論理に分解して評価する。
高度なクエリにおいて、NULL判定が頻発すると、JITコンパイルされた実行プランが複雑化する。特に数百万件の結合が発生するクエリでは、NULLの有無がスキャン速度に直結する。可能な限り `NOT NULL` 制約を付与し、オプティマイザに「NULLは存在しない」という確信を与えることが、最高速のクエリを生む秘訣だ。
4. 伝説的アーキテクトからの提言
実務において、NULLを単なる「値がない状態」と捉えて放置してはならない。
1. 物理データ分布の観測: `STATS_TOP_KEY_PREFIXES` などの統計情報を使い、NULLがインデックスの特定範囲に偏っていないかを確認せよ。
2. デフォルト値の戦略的採用: 「NULL」よりも「デフォルト値(例: 0や空文字)」を入れるべきケースは明確に存在する。特に、算術演算が絡むカラムでNULLを使うと、実行計画の再利用性が下がる可能性がある。
3. オプティマイザとの対話: `EXPLAIN ANALYZE` を実行し、NULLの存在によって「Predicate pushdown」が阻害されていないか確認せよ。もし阻害されているなら、それはNULLを許容したスキーマ設計そのものがボトルネックになっている証拠だ。
Spannerは、分散システムにおける「物理的な制約」と「論理的なSQL」の境界線上に存在する。NULLを制御する者は、SpannerのストレージI/Oを制御し、結果としてシステムのレイテンシを支配する。
エンジニアよ、NULLの不在にこそ、性能向上の鍵が隠されていることを忘れるな。
コメント