Operators

Query execution operators は複数ページに分割されており、以前より多くの operator がドキュメント化されている。一方で metadata やそれぞれの child links については未解説の部分も多いためここにまとめる。 なお、公式ドキュメントにない事柄や実行計画の細部は、間違っていたり今後予告なく変更される可能性がある。

この文書の再現 SQL と実行計画は、特定の schema、データ量、統計情報、optimizer version(オプティマイザーバージョン)、hint の組み合わせで観測した例である。実行計画は、特記がない限り Spanner Omni 2026.r1-beta で出力したもので、spannerplan のデフォルト出力を掲載している。Spanner はコストベース最適化を行うため、optimizer version、統計情報、データ分布が変わると同じ SQL でも違う実行計画になることがあり、今後も同じ結果である保証はない。特に、テーブルを作成した直後など統計情報が存在しない状態では、実質的にルールベースに近い選択になっていたと考えられる例がある。

再現例で使用したスキーマ

以下は、この文書の再現例で主に使用したスキーマである。一部の例では、各 details 内に個別の DDL を示している。

CREATE TABLE Singers (
  SingerId INT64 NOT NULL,
  FirstName STRING(1024),
  LastName STRING(1024),
  BirthDate DATE,
  SingerInfo BYTES(MAX),
  ReleaseDate DATE,
  ModificationTime TIMESTAMP OPTIONS (allow_commit_timestamp = true),
) PRIMARY KEY(SingerId);

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);
CREATE INDEX SingersByLastName ON Singers(LastName) STORING (FirstName);

CREATE TABLE Albums (
  SingerId INT64 NOT NULL,
  AlbumId INT64 NOT NULL,
  AlbumTitle STRING(MAX),
  MarketingBudget INT64,
  ReleaseDate DATE,
) PRIMARY KEY(SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);
CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);
CREATE INDEX AlbumsByReleaseDateTitleDesc ON Albums(ReleaseDate, AlbumTitle DESC);

CREATE TABLE Songs (
  SingerId INT64 NOT NULL,
  AlbumId INT64 NOT NULL,
  TrackId INT64 NOT NULL,
  SongName STRING(MAX),
  Duration INT64,
  SongGenre STRING(25),
) PRIMARY KEY(SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC),
  INTERLEAVE IN Albums;
CREATE INDEX SongsBySongName ON Songs(SongName);

CREATE TABLE Concerts (
  VenueId INT64 NOT NULL,
  SingerId INT64 NOT NULL,
  ConcertDate DATE NOT NULL,
  BeginTime TIMESTAMP,
  EndTime TIMESTAMP,
  TicketPrices ARRAY<INT64>,
) PRIMARY KEY(VenueId, SingerId, ConcertDate);

CREATE TABLE Collaborations (
  SingerId INT64 NOT NULL,
  FeaturingSingerId INT64 NOT NULL,
  AlbumTitle STRING(MAX) NOT NULL,
) PRIMARY KEY(SingerId, FeaturingSingerId, AlbumTitle);

CREATE OR REPLACE PROPERTY GRAPH MusicGraph
  NODE TABLES(
    Singers
      KEY(SingerId)
      LABEL Singers PROPERTIES(
        BirthDate,
        FirstName,
        LastName,
        SingerId,
        SingerInfo)
  )
  EDGE TABLES(
    Collaborations AS CollabWith
      KEY(SingerId, FeaturingSingerId, AlbumTitle)
      SOURCE KEY(SingerId) REFERENCES Singers(SingerId)
      DESTINATION KEY(FeaturingSingerId) REFERENCES Singers(SingerId)
      LABEL CollabWith PROPERTIES(
        AlbumTitle,
        FeaturingSingerId,
        SingerId)
  );

DML の再現例では、必要に応じて以下も追加している。

ALTER TABLE Singers ADD COLUMN Status STRING(1024) DEFAULT ("active");
ALTER TABLE Singers ADD COLUMN LastUpdated TIMESTAMP DEFAULT (PENDING_COMMIT_TIMESTAMP())
  ON UPDATE (PENDING_COMMIT_TIMESTAMP())
  OPTIONS (allow_commit_timestamp = true);
CREATE UNIQUE INDEX UniqueIndex_SingerName ON Singers(FirstName, LastName);

CREATE TABLE AckworthSingers (
  SingerId INT64 NOT NULL,
  FirstName STRING(1024),
  LastName STRING(1024),
  BirthDate DATE,
) PRIMARY KEY(SingerId);

CREATE TABLE Fans (
  FanId STRING(36) DEFAULT (GENERATE_UUID()),
  FirstName STRING(1024),
  LastName STRING(1024),
) PRIMARY KEY(FanId);

対象: 実行計画を可視化や解析のために処理するツール作成者や、含まれる情報全てをクエリの理解に役立てたいと考えるユーザ

TODO: Metadata や ChildLinks の表の形式化を進める。

実行計画の構造

実行計画の実体は google.spanner.v1.QueryPlan であり、各クライアントや Web UI が表示するものは REST API や gRPC API の ExecuteSql もしくは ExecuteStreamingSql API 経由で QueryModePLAN もしくは PROFILE を指定することで取得した QueryPlan そのものである。

QueryPlanPlanNode の集合であり、 PlanNode は operator と一対一で対応する。 各 PlanNode の動作は display_name によって特定できる operator の種類と、 operator の動作を変える metadata によって決まり、child_links に入力として使う子の operator が列挙されている。

Scalar operator とは kindSCALAR の operator である。Spanner Studio の query plan visualizer は rows を消費して親へ rows を生成する iterator を graph node として表示し、Spanner CLIEXPLAIN / EXPLAIN ANALYZE も同様に行を返す operator tree を中心に表示する。子に Relational operator を持つ Subquery のみは例外として表示されるが、その他の Scalar operator は、公式ツールでも OSS の spanner-cli、spannerplan の rendertree、spannerplanviz でも、一般的な実行計画ツリーの可視化手法では表示されない。

test

例えば上記の Table Scan operator の実体は Scan operator であり、 Table Scan: Songs の部分及び full scan: true は metadata からの情報を合わせて表示している。また、デフォルトでは折りたたまれている変数名とスキャン対象の列名の対応関係は全て Scalar operator である。この文書で Scalar operator を扱う場合、tree 表示上の operator 行として現れることを意味するのではなく、QueryPlan の生データを読むことで観測できる raw PlanNode としての語彙を説明している箇所がある。

上記 Table Scan に対応する生の PlanNode の YAML 表現
- childLinks:
  - childIndex: 4
    variable: SingerId
  - childIndex: 5
    variable: AlbumId
  - childIndex: 6
    variable: TrackId
  - childIndex: 7
    variable: SongName
  - childIndex: 8
    variable: Duration
  - childIndex: 9
    variable: SongGenre
  displayName: Scan
  index: 3
  kind: RELATIONAL
  metadata:
    Full scan: 'true'
    scan_target: Songs
    scan_type: TableScan

Relational operators

kind: RELATIONAL なもので、行のストリームを返す operator である。

Distributed operators

分散実行される operator 群であり、 subquery_cluster_node が指す方の子の Relational operator からなる実行計画のサブツリーを Split Range の条件を満たす remote server で実行することで、 server を跨ぐ replica から結果を得るという共通点がある。 ただし call_type: Local の Distributed Union にも subquery_cluster_node が付くことがあるため、root partitionable かどうかの判定などで分散実行の有無を機械的に判断する場合は subquery_cluster_node の有無だけでなく call_type も確認する必要がある。観測した DML の実行計画では Apply Mutations 自体には subquery_cluster_node は付かず、その内部の distributed apply / union に付く。

Distributed Anti Semi Apply

NOT EXISTS などを処理するために分散 Anti Semi Join を行う。Distributed Cross Apply と似た構造を持つ。

Metadata
key values description
subquery_cluster_node 分散実行する対象の Relation operator の ID
kind type variable? multiple? description
RELATIONAL (Input) いわゆる駆動表に対応する入力側のサブツリーであり、実際には type を持たないが Web UI やドキュメント等で Input と表示される。通常 Create Batch を持つ。
RELATIONAL Map Input 側の値に応じて分散実行されるサブツリーであり、通常 Batch Scan と Cross Apply を含む。
SCALAR Split Range 分散実行する対象の replica をキーから限定するための Function
SCALAR Batch Yes Yes Input 側の Batch から生成する行の定義?
Distributed Anti Semi Apply の再現クエリと実行計画
@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=TRUE}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE NOT EXISTS (
  SELECT 1
  FROM Albums AS a
  WHERE a.SingerId = s.SingerId
);
=== subquery-join-hint-matrix/not_exists/apply_join_batch_true ===
@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=TRUE}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE NOT EXISTS (SELECT 1 FROM Albums AS a WHERE a.SingerId = s.SingerId)
+-----+--------------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                         |
+-----+--------------------------------------------------------------------------------------------------+
|   0 | Distributed Union on SingersByFirstLastName <Row>                                                |
|   1 | +- Serialize Result <Row>                                                                        |
|  *2 |    +- Distributed Anti Semi Apply <Row>                                                          |
|   3 |       +- [Input] Create Batch <Row>                                                              |
|   4 |       |  +- Local Distributed Union <Row>                                                        |
|   5 |       |     +- Compute Struct <Row>                                                              |
|   6 |       |        +- Index Scan on SingersByFirstLastName <Row> (Full scan, scan_method: Automatic) |
|  13 |       +- [Map] Semi Apply <Row>                                                                  |
|  14 |          +- [Input] KeyRangeAccumulator <Row>                                                    |
|  15 |          |  +- Batch Scan on $v2 <Row> (scan_method: Row)                                        |
|  19 |          +- [Map] Local Distributed Union <Row>                                                  |
|  20 |             +- Filter Scan <Row> (seekable_key_size: 0)                                          |
| *21 |                +- Table Scan on Albums <Row> (scan_method: Row)                                  |
+-----+--------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  2: Split Range: ($SingerId_1 = $SingerId)
 21: Seek Condition: ($SingerId_1 = $batched_SingerId)

Distributed Cross Apply

分散 Apply Join を行う。Input 側の Relational operator から取り出した値を使って、対応する Map 側の Relational operator を適切な replica で実行することで分散 JOIN を実現する。

Metadata
key values description
subquery_cluster_node 分散実行する対象の Relation operator の ID
kind type variable? multiple? description
RELATIONAL (Input) いわゆる駆動表に対応する入力側のサブツリーであり、実際には type を持たないが Web UI やドキュメント等で Input と表示される。通常 Create Batch を持つ。
RELATIONAL Map Input 側の値に応じて分散実行されるサブツリーであり、通常 Batch Scan と Cross Apply を含む。
SCALAR Split Range 分散実行する対象の replica をキーから限定するための Function
Distributed Cross Apply の再現クエリと実行計画
SELECT s.SongName, s.Duration
FROM Songs@{FORCE_INDEX=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");
=== execution-plans/index-with-back-join ===
SELECT s.SongName, s.Duration FROM Songs@{FORCE_INDEX=SongsBySongName} AS s WHERE STARTS_WITH(s.SongName, "B")
+-----+--------------------------------------------------------------------------+
| ID  | Operator                                                                 |
+-----+--------------------------------------------------------------------------+
|  *0 | Distributed Union on SongsBySongName <Row>                               |
|  *1 | +- Distributed Cross Apply <Row>                                         |
|   2 |    +- [Input] Create Batch <Batch>                                       |
|   3 |    |  +- RowToDataBlock                                                  |
|   4 |    |     +- Local Distributed Union <Row>                                |
|   5 |    |        +- Filter Scan <Row> (seekable_key_size: 1)                  |
|  *6 |    |           +- Index Scan on SongsBySongName <Row> (scan_method: Row) |
|  19 |    +- [Map] Serialize Result <Row>                                       |
|  20 |       +- Cross Apply <Row>                                               |
|  21 |          +- [Input] KeyRangeAccumulator <Row>                            |
|  22 |          |  +- DataBlockToRow                                            |
|  23 |          |     +- Batch Scan on $v2 <Batch> (scan_method: Batch)         |
|  32 |          +- [Map] Local Distributed Union <Row>                          |
|  33 |             +- Filter Scan <Row> (seekable_key_size: 0)                  |
| *34 |                +- Table Scan on Songs <Row> (scan_method: Row)           |
+-----+--------------------------------------------------------------------------+
Predicates(identified by ID):
  0: Split Range: STARTS_WITH($SongName, 'B')
  1: Split Range: (($Songs_key_SingerId' = $Songs_key_SingerId) AND ($Songs_key_AlbumId' = $Songs_key_AlbumId) AND ($Songs_key_TrackId' = $Songs_key_TrackId))
  6: Seek Condition: STARTS_WITH($SongName, 'B')
 34: Seek Condition: (($Songs_key_SingerId' = $batched_Songs_key_SingerId') AND ($Songs_key_AlbumId' = $batched_Songs_key_AlbumId') AND ($Songs_key_TrackId' = $batched_Songs_key_TrackId'))

Distributed Outer Apply

LEFT OUTER JOIN などを処理するために分散 OUTER JOIN を行う。Distributed Cross Apply と似た構造を持つ。

Metadata
key values description
subquery_cluster_node 分散実行する対象の Relation operator の ID
kind type variable? multiple? description
RELATIONAL (Input) いわゆる駆動表に対応する入力側のサブツリーであり、実際には type を持たないが Web UI やドキュメント等で Input と表示される。通常 Create Batch を持つ。
RELATIONAL Map Input 側の値に応じて分散実行されるサブツリーであり、通常 Batch Scan と Cross Apply を含む。
SCALAR Split Range 分散実行する対象の replica をキーから限定するための Function
SCALAR Batch Yes Yes 結合条件を満たさなかった時に Input 側の Batch から生成する行の定義
Distributed Outer Apply の再現クエリと実行計画
SELECT a.AlbumTitle, s.SongName
FROM Albums AS a
LEFT JOIN@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=TRUE} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId;
=== join-matrix/left/apply_join_batch_true ===
SELECT a.AlbumTitle, s.SongName
FROM Albums AS a LEFT JOIN@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=TRUE} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId
+-----+----------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                     |
+-----+----------------------------------------------------------------------------------------------+
|   0 | Distributed Union on AlbumsByAlbumTitle <Row>                                                |
|   1 | +- Serialize Result <Row>                                                                    |
|  *2 |    +- Distributed Outer Apply <Row>                                                          |
|   3 |       +- [Input] Create Batch <Row>                                                          |
|   4 |       |  +- Local Distributed Union <Row>                                                    |
|   5 |       |     +- Compute Struct <Row>                                                          |
|   6 |       |        +- Index Scan on AlbumsByAlbumTitle <Row> (Full scan, scan_method: Automatic) |
|  15 |       +- [Map] Cross Apply <Row>                                                             |
|  16 |          +- [Input] KeyRangeAccumulator <Row>                                                |
|  17 |          |  +- Batch Scan on $v2 <Row> (scan_method: Row)                                    |
|  22 |          +- [Map] Local Distributed Union <Row>                                              |
|  23 |             +- Filter Scan <Row> (seekable_key_size: 0)                                      |
| *24 |                +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (scan_method: Row)      |
+-----+----------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  2: Split Range: (($SingerId_1 = $SingerId) AND ($AlbumId_1 = $AlbumId))
 24: Seek Condition: (($SingerId_1 = $batched_SingerId) AND ($AlbumId_1 = $batched_AlbumId))

Distributed Semi Apply

EXISTS などを処理するために分散 Semi Join を行う。Distributed Cross Apply と似た構造を持つ。

Metadata
key values description
subquery_cluster_node 分散実行する対象の Relation operator の ID
kind type variable? multiple? description
RELATIONAL (Input) いわゆる駆動表に対応する入力側のサブツリーであり、実際には type を持たないが Web UI やドキュメント等で Input と表示される。通常 Create Batch を持つ。
RELATIONAL Map Input 側の値に応じて分散実行されるサブツリーであり、通常 Batch Scan と Cross Apply を含む。
SCALAR Split Range 分散実行する対象の replica をキーから限定するための Function
SCALAR Batch Yes Yes Input 側の Batch から生成する行の定義?
Distributed Semi Apply の再現クエリと実行計画
@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=TRUE}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE s.SingerId IN (
  SELECT a.SingerId
  FROM Albums AS a
);
=== subquery-join-hint-matrix/in/apply_join_batch_true ===
@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=TRUE}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE s.SingerId IN (SELECT a.SingerId FROM Albums AS a)
+-----+--------------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                         |
+-----+--------------------------------------------------------------------------------------------------+
|   0 | Distributed Union on SingersByFirstLastName <Row>                                                |
|   1 | +- Serialize Result <Row>                                                                        |
|  *2 |    +- Distributed Semi Apply <Row>                                                               |
|   3 |       +- [Input] Create Batch <Row>                                                              |
|   4 |       |  +- Local Distributed Union <Row>                                                        |
|   5 |       |     +- Compute Struct <Row>                                                              |
|   6 |       |        +- Index Scan on SingersByFirstLastName <Row> (Full scan, scan_method: Automatic) |
|  13 |       +- [Map] Semi Apply <Row>                                                                  |
|  14 |          +- [Input] KeyRangeAccumulator <Row>                                                    |
|  15 |          |  +- Batch Scan on $v2 <Row> (scan_method: Row)                                        |
|  19 |          +- [Map] Local Distributed Union <Row>                                                  |
|  20 |             +- Filter Scan <Row> (seekable_key_size: 0)                                          |
| *21 |                +- Table Scan on Albums <Row> (scan_method: Row)                                  |
+-----+--------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  2: Split Range: ($SingerId_1 = $SingerId)
 21: Seek Condition: ($SingerId_1 = $batched_SingerId)

Push Broadcast Hash Join

JOIN_METHOD=PUSH_BROADCAST_HASH_JOIN で現れる分散 hash join 系の operator。 通常 join では Push Broadcast Hash Join、outer join では Push Broadcast Hash Join Outer Apply、subquery predicate では Push Broadcast Hash Join Semi ApplyPush Broadcast Hash Join Anti Semi Apply として表示される。 内部に通常の Hash JoinCreate BatchRowToDataBlockDataBlockToRow などが現れるため、descendant に Hash Join があることだけで通常の Hash Join と解釈しない方がよい。

kind type variable? multiple? description
RELATIONAL (Input) broadcast する入力側のサブツリー。通常 Create Batch を含む。
RELATIONAL Map broadcast された batch と probe 側を使って実行されるサブツリー。通常 Hash Join を含む。
SCALAR Split Range 分散実行する対象の replica をキーから限定するための Function
Push Broadcast Hash Join 系の再現クエリと実行計画

Push Broadcast Hash Join:

SELECT a.AlbumTitle, s.SongName
FROM Albums AS a
JOIN@{JOIN_METHOD=PUSH_BROADCAST_HASH_JOIN} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId;
+-----+-------------------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                              |
+-----+-------------------------------------------------------------------------------------------------------+
|   0 | Distributed Union on AlbumsByAlbumTitle <Row>                                                         |
|  *1 | +- Push Broadcast Hash Join <Row>                                                                     |
|   2 |    +- Create Batch <Batch>                                                                            |
|   3 |    |  +- RowToDataBlock                                                                               |
|   4 |    |     +- Local Distributed Union <Row>                                                             |
|   5 |    |        +- Index Scan on AlbumsByAlbumTitle <Row> (Full scan, scan_method: Automatic)             |
|  12 |    +- [Map] Serialize Result <Row>                                                                    |
| *13 |       +- Hash Join <Row> (join_type: INNER)                                                           |
|  14 |          +- [Build] DataBlockToRow                                                                    |
|  15 |          |  +- Batch Scan on $v2 <Batch> (scan_method: Batch)                                         |
|  22 |          +- [Probe] Local Distributed Union <Row>                                                     |
|  23 |             +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+-----+-------------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  1: Split Range: (($SingerId_1 = $SingerId) AND ($AlbumId_1 = $AlbumId))
 13: Condition: (($batched_SingerId' = $SingerId_1) AND ($batched_AlbumId' = $AlbumId_1))

Outer Apply:

SELECT a.AlbumTitle, s.SongName
FROM Albums AS a
LEFT JOIN@{JOIN_METHOD=PUSH_BROADCAST_HASH_JOIN} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId;
+-----+-------------------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                              |
+-----+-------------------------------------------------------------------------------------------------------+
|   0 | Distributed Union on AlbumsByAlbumTitle <Row>                                                         |
|   1 | +- Serialize Result <Row>                                                                             |
|  *2 |    +- Push Broadcast Hash Join Outer Apply <Row>                                                      |
|   3 |       +- [Input] Create Batch <Row>                                                                   |
|   4 |       |  +- Local Distributed Union <Row>                                                             |
|   5 |       |     +- Compute Struct <Row>                                                                   |
|   6 |       |        +- Index Scan on AlbumsByAlbumTitle <Row> (Full scan, scan_method: Automatic)          |
| *15 |       +- [Map] Hash Join <Row> (join_type: INNER)                                                     |
|  16 |          +- [Build] Batch Scan on $v2 <Row> (scan_method: Row)                                        |
|  21 |          +- [Probe] Local Distributed Union <Row>                                                     |
|  22 |             +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+-----+-------------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  2: Split Range: (($SingerId_1 = $SingerId) AND ($AlbumId_1 = $AlbumId))
 15: Condition: (($batched_SingerId = $SingerId_1) AND ($batched_AlbumId = $AlbumId_1))

Semi Apply:

@{JOIN_METHOD=PUSH_BROADCAST_HASH_JOIN}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE s.SingerId IN (SELECT a.SingerId FROM Albums AS a);
+-----+--------------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                         |
+-----+--------------------------------------------------------------------------------------------------+
|   0 | Distributed Union on SingersByFirstLastName <Row>                                                |
|   1 | +- Serialize Result <Row>                                                                        |
|  *2 |    +- Push Broadcast Hash Join Semi Apply <Row>                                                  |
|   3 |       +- [Input] Create Batch <Row>                                                              |
|   4 |       |  +- Local Distributed Union <Row>                                                        |
|   5 |       |     +- Compute Struct <Row>                                                              |
|   6 |       |        +- Index Scan on SingersByFirstLastName <Row> (Full scan, scan_method: Automatic) |
| *13 |       +- [Map] Hash Join <Row> (join_type: INNER)                                                |
|  14 |          +- [Build] Batch Scan on $v2 <Row> (scan_method: Row)                                   |
|  18 |          +- [Probe] Local Distributed Union <Row>                                                |
|  19 |             +- Table Scan on Albums <Row> (Full scan, scan_method: Automatic)                    |
+-----+--------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  2: Split Range: ($SingerId_1 = $SingerId)
 13: Condition: ($batched_SingerId = $SingerId_1)

Anti Semi Apply:

@{JOIN_METHOD=PUSH_BROADCAST_HASH_JOIN}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE NOT EXISTS (SELECT 1 FROM Albums AS a WHERE a.SingerId = s.SingerId);
+-----+--------------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                         |
+-----+--------------------------------------------------------------------------------------------------+
|   0 | Distributed Union on SingersByFirstLastName <Row>                                                |
|   1 | +- Serialize Result <Row>                                                                        |
|  *2 |    +- Push Broadcast Hash Join Anti Semi Apply <Row>                                             |
|   3 |       +- [Input] Create Batch <Row>                                                              |
|   4 |       |  +- Local Distributed Union <Row>                                                        |
|   5 |       |     +- Compute Struct <Row>                                                              |
|   6 |       |        +- Index Scan on SingersByFirstLastName <Row> (Full scan, scan_method: Automatic) |
| *13 |       +- [Map] Hash Join <Row> (join_type: INNER)                                                |
|  14 |          +- [Build] Batch Scan on $v2 <Row> (scan_method: Row)                                   |
|  18 |          +- [Probe] Local Distributed Union <Row>                                                |
|  19 |             +- Table Scan on Albums <Row> (Full scan, scan_method: Automatic)                    |
+-----+--------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  2: Split Range: ($SingerId_1 = $SingerId)
 13: Condition: ($SingerId_1 = $batched_SingerId)

Distributed Union

各 replica で子の Relation operator を実行し、結果をまとめる。 クエリ対象の replica を他の server(remote server) が持つ場合、remote server を呼び出すため remote call が発生し、 executionStats に記録される。 call_type が Local なものは、特定の server 内の結果をまとめる。

Metadata
key values description
call_type Local, 未指定
subquery_cluster_node 分散実行する対象の Relation operator の ID
kind type variable? multiple? description
RELATIONAL 入力として分散実行されるサブツリー
SCALAR Split Range 分散実行する対象の replica をキーから限定するための Function
Distributed Union / Scan / Serialize Result の再現クエリと実行計画
SELECT s.SongName
FROM Songs AS s;
=== execution-plans/simple-scan ===
SELECT s.SongName FROM Songs AS s
+----+-------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                        |
+----+-------------------------------------------------------------------------------------------------+
|  0 | Distributed Union on SongsBySingerAlbumSongNameDesc <Row>                                       |
|  1 | +- Local Distributed Union <Row>                                                                |
|  2 |    +- Serialize Result <Row>                                                                    |
|  3 |       +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+----+-------------------------------------------------------------------------------------------------+

Distributed Merge Union

複数の remote server に分散した subquery の結果を、指定された順序で merge して返す Distributed Union。 PlanNode.displayName として Distributed Merge Union という別名の operator が出るのではなく、spannerplan の表形式出力では Distributed Unionpreserve_subquery_order: true metadata が付いた形で表示される。 入力側には SortSort Limit、または順序を満たす scan など、順序付け済みの subquery が現れる。 公式ドキュメントでは distributed merge sort として説明されており、Spanner Version 3 以降ではデフォルトで有効とされている。

Distributed Merge Union 相当の再現クエリと実行計画
SELECT s.SongGenre
FROM Songs AS s
ORDER BY SongGenre;
=== unary/sort ===
SELECT s.SongGenre FROM Songs AS s ORDER BY SongGenre
+----+---------------------------------------------------------------------------+
| ID | Operator                                                                  |
+----+---------------------------------------------------------------------------+
|  0 | Distributed Union on Songs <Row> (preserve_subquery_order: true)          |
|  1 | +- Serialize Result <Row>                                                 |
|  2 |    +- Sort <Row>                                                          |
|  3 |       +- Local Distributed Union <Row>                                    |
|  4 |          +- Table Scan on Songs <Row> (Full scan, scan_method: Automatic) |
+----+---------------------------------------------------------------------------+

Leaf operators

公式ドキュメントで Leaf operators に分類されている operator 群。

Array Unnest

配列の値と添字を元に Relation を作り出す operator。

kind type variable? multiple? description
SCALAR Yes 配列の値に対応する変数名を指示する
SCALAR Yes 配列の添字に対応する変数名を指示する
Array Unnest / Array Constructor の再現クエリと実行計画
SELECT a, b
FROM UNNEST([1, 2, 3]) a WITH OFFSET b;
=== leaf/array-unnest ===
SELECT a, b FROM UNNEST([1,2,3]) a WITH OFFSET b
+----+------------------------+
| ID | Operator               |
+----+------------------------+
|  0 | Serialize Result <Row> |
|  1 | +- Array Unnest <Row>  |
+----+------------------------+

Empty Relation

空の Relation を生成する。LIMIT 0 を指定した際には常に結果は 0 行で何も Scan 等の入力をする必要がないが、 Relation operator ではある必要があるので使われる。

kind type variable? multiple? description
SCALAR 0 を意味する Constant
Empty Relation の再現クエリと実行計画
SELECT *
FROM Albums
LIMIT 0;
=== leaf/empty-relation ===
SELECT * FROM Albums LIMIT 0
+----+-------------------------+
| ID | Operator                |
+----+-------------------------+
|  0 | Serialize Result <Row>  |
|  1 | +- Empty Relation <Row> |
+----+-------------------------+

Generate Relation

0 行以上の relation を生成する operator。 現時点のフィードバックでは、Spanner Omni 2026.r1-beta でこの operator 名を単独で安定して表示する最小再現クエリは確認できていない。 SELECT 1 + 2 のような定数式だけのクエリは、現在の実行計画では Generate Relation ではなく Unit Relation として表示される。

Scan

各入力からのスキャンを行う。PlanNode.displayName としては Scan だが、一般的に scan_type の値と合わせて Index Scan, Table Scan などと表示される。

Metadata
key values description
Full scan true もしくは未指定
scan_target スキャン対象の名前を指示する。
scan_type IndexScan, TableScan, SearchIndexScan, BatchScan スキャン対象の種類を指示する。
kind type variable? multiple? description
SCALAR Yes Yes スキャン対象の列を表現する
SCALAR Search Predicate Full Text Search の search index scan で使う検索条件を表現する

Full Text Search の search index access は SearchIndexScan という独立した PlanNode.displayName ではなく、通常の Scanscan_type: SearchIndexScanscan_target: <search index name> が付く形で表現される。SEARCH(...)Search Query Conversion という TVFVerifyDeterminism を伴うことがある一方、観測した SEARCH_SUBSTRING(...) の例では Search Query Conversion は現れず、substring search index scan と Search Predicate が直接現れた。 Search Predicate 自体は scalar operator であり tree の行としては表示されないが、spannerplan の tree 表示では SearchIndex Scan 行に * が付き、Predicates(identified by ID)Search Predicate: として出力される。複数列に対する AND / OR のような合成検索条件も、SQUERY(...) の AND / OR として predicate section に表示される。raw QueryPlan では、単純な検索条件では Search Predicate child link の先が scalar Search Predicate node になり、合成検索条件では同じ child link の先が scalar Function node になって、その子孫に複数の Search Predicate node が現れることがある。ツールでは child node の display_name だけでなく child link の type を見る方がよい。 TOKENIZE_NUMBER(..., comparison_type=>"equality") で生成した token column に search index を作成した例では、ARRAY_INCLUDES_ANY(...)ARRAY_INCLUDES_ALL(...) でも SearchIndexScan が観測された。Full Text Search の検索条件と非テキスト条件を混在させた例でも SearchIndexScan が使われ、search index 側で処理しきれない条件は Filter Scan の residual condition として現れることがある。 SNIPPET(...) や search index に stored されていない列の参照は、観測した実行計画では base table への back join を発生させた。ORDER BY SCORE(...) DESCSort を発生させ、TOKENLIST_CONCAT(...)LIMIT を組み合わせた ranked query では Sort Limit が現れた。一方、PARTITION BY SingerId ORDER BY ReleaseTimestamp DESC を持つ search index に対して同じ SingerId 条件と ORDER BY ReleaseTimestamp DESC LIMIT ... を使う query では、明示的な Sort は観測されなかった。

Scan の再現クエリと実行計画
SELECT s.SongName
FROM Songs AS s;
=== execution-plans/simple-scan ===
SELECT s.SongName FROM Songs AS s
+----+-------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                        |
+----+-------------------------------------------------------------------------------------------------+
|  0 | Distributed Union on SongsBySingerAlbumSongNameDesc <Row>                                       |
|  1 | +- Local Distributed Union <Row>                                                                |
|  2 |    +- Serialize Result <Row>                                                                    |
|  3 |       +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+----+-------------------------------------------------------------------------------------------------+
Full Text Search の追加再現クエリと実行計画

この例では Full Text Search 用に以下の schema を使用している。

CREATE TABLE SearchAlbums (
  SingerId INT64 NOT NULL,
  AlbumId STRING(MAX) NOT NULL,
  AlbumTitle STRING(MAX),
  AlbumStudio STRING(MAX),
  Rating FLOAT64,
  ReleaseTimestamp INT64 NOT NULL,
  Likes INT64,
  Genres ARRAY<STRING(MAX)>,
  Cover BYTES(MAX),
  Ratings ARRAY<INT64>,
  AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
  AlbumTitle_SubstringTokens TOKENLIST AS (TOKENIZE_SUBSTRING(AlbumTitle)) HIDDEN,
  AlbumStudio_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumStudio)) HIDDEN,
  Rating_Tokens TOKENLIST AS (TOKENIZE_NUMBER(Rating)) HIDDEN,
  Genres_Tokens TOKENLIST AS (TOKEN(Genres)) HIDDEN,
  Ratings_Tokens TOKENLIST AS (TOKENIZE_NUMBER(Ratings, comparison_type=>"equality")) HIDDEN,
) PRIMARY KEY(SingerId, AlbumId);

CREATE SEARCH INDEX SearchAlbumsTitleStudioIndex
ON SearchAlbums(AlbumTitle_Tokens, AlbumStudio_Tokens);

CREATE SEARCH INDEX SearchAlbumsRatingsIndex
ON SearchAlbums(Ratings_Tokens);

CREATE SEARCH INDEX SearchAlbumsMixedIndex
ON SearchAlbums(AlbumTitle_Tokens, Rating_Tokens, Genres_Tokens)
STORING (Likes);

数値配列に対する ARRAY_INCLUDES_ANY(...):

SELECT AlbumId
FROM SearchAlbums
WHERE ARRAY_INCLUDES_ANY(Ratings, [1, 2]);
=== full-text-search/numeric-array-any ===
SELECT AlbumId FROM SearchAlbums WHERE ARRAY_INCLUDES_ANY(Ratings, [1, 2])
+----+--------------------------------------------------------------------------------+
| ID | Operator                                                                       |
+----+--------------------------------------------------------------------------------+
|  0 | Distributed Union on _Search2aryIndex_SearchAlbumsRatingsIndex <Row>           |
|  1 | +- Local Distributed Union <Row>                                               |
|  2 |    +- Serialize Result <Row>                                                   |
| *3 |       +- SearchIndex Scan on SearchAlbumsRatingsIndex <Row> (scan_method: Row) |
+----+--------------------------------------------------------------------------------+
Predicates(identified by ID):
 3: Search Predicate: SQUERY(index_name:Ratings_Tokens in SearchAlbumsRatingsIndex predicate:SPAN_NUMBER_QUERY_TO_SQUERY(9, false, false, SPAN_MAKE_NUMRANGE(9, [1, 2])))

数値配列に対する ARRAY_INCLUDES_ALL(...):

SELECT AlbumId
FROM SearchAlbums
WHERE ARRAY_INCLUDES_ALL(Ratings, [1, 5]);
=== full-text-search/numeric-array-all ===
SELECT AlbumId FROM SearchAlbums WHERE ARRAY_INCLUDES_ALL(Ratings, [1, 5])
+----+--------------------------------------------------------------------------------+
| ID | Operator                                                                       |
+----+--------------------------------------------------------------------------------+
|  0 | Distributed Union on _Search2aryIndex_SearchAlbumsRatingsIndex <Row>           |
|  1 | +- Local Distributed Union <Row>                                               |
|  2 |    +- Serialize Result <Row>                                                   |
| *3 |       +- SearchIndex Scan on SearchAlbumsRatingsIndex <Row> (scan_method: Row) |
+----+--------------------------------------------------------------------------------+
Predicates(identified by ID):
 3: Search Predicate: SQUERY(index_name:Ratings_Tokens in SearchAlbumsRatingsIndex predicate:SPAN_NUMBER_QUERY_TO_SQUERY(8, false, false, SPAN_MAKE_NUMRANGE(8, [1, 5])))

複数列の検索条件では Search Predicate が AND / OR で合成された predicate として表示される。

SELECT AlbumId
FROM SearchAlbums
WHERE SEARCH(AlbumTitle_Tokens, "car")
  AND SEARCH(AlbumStudio_Tokens, "sun");
=== full-text-search/multi-column-conjunction ===
SELECT AlbumId FROM SearchAlbums WHERE SEARCH(AlbumTitle_Tokens, "car") AND SEARCH(AlbumStudio_Tokens, "sun")
+-----+---------------------------------------------------------------------------------------+
| ID  | Operator                                                                              |
+-----+---------------------------------------------------------------------------------------+
|   0 | Cross Apply <Row>                                                                     |
|   1 | +- [Input] VerifyDeterminism <Row>                                                    |
|   2 | |  +- TVF <Row> (Name: Search Query Conversion)                                       |
|   3 | |     +- Unit Relation <Row>                                                          |
|   9 | +- [Map] Distributed Union on _Search2aryIndex_SearchAlbumsTitleStudioIndex <Row>     |
|  10 |    +- Local Distributed Union <Row>                                                   |
|  11 |       +- Serialize Result <Row>                                                       |
| *12 |          +- SearchIndex Scan on SearchAlbumsTitleStudioIndex <Row> (scan_method: Row) |
+-----+---------------------------------------------------------------------------------------+
Predicates(identified by ID):
 12: Search Predicate: (SQUERY(index_name:AlbumTitle_Tokens in SearchAlbumsTitleStudioIndex predicate:$oo_tvf_0) AND SQUERY(index_name:AlbumStudio_Tokens in SearchAlbumsTitleStudioIndex predicate:$oo_tvf_1))

Full Text Search と非テキスト条件を混在させた場合、条件の一部は search index scan の上の Filter Scan に残ることがある。

SELECT AlbumId
FROM SearchAlbums@{FORCE_INDEX=SearchAlbumsMixedIndex}
WHERE SEARCH(AlbumTitle_Tokens, "car")
  AND Rating > 4
  AND Likes >= 1000;
=== full-text-search/mixed-stored-filter ===
SELECT AlbumId FROM SearchAlbums@{FORCE_INDEX=SearchAlbumsMixedIndex} WHERE SEARCH(AlbumTitle_Tokens, "car") AND Rating > 4 AND Likes >= 1000
+-----+------------------------------------------------------------------------------------+
| ID  | Operator                                                                           |
+-----+------------------------------------------------------------------------------------+
|   0 | Cross Apply <Row>                                                                  |
|   1 | +- [Input] VerifyDeterminism <Row>                                                 |
|   2 | |  +- TVF <Row> (Name: Search Query Conversion)                                    |
|   3 | |     +- Unit Relation <Row>                                                       |
|   7 | +- [Map] Distributed Union on _Search2aryIndex_SearchAlbumsMixedIndex <Row>        |
|   8 |    +- Local Distributed Union <Row>                                                |
|   9 |       +- Serialize Result <Row>                                                    |
| *10 |          +- Filter Scan <Row> (seekable_key_size: 0)                               |
| *11 |             +- SearchIndex Scan on SearchAlbumsMixedIndex <Row> (scan_method: Row) |
+-----+------------------------------------------------------------------------------------+
Predicates(identified by ID):
 10: Residual Condition: (($Rating > 4) AND ($Likes >= 1000))
 11: Search Predicate: (SQUERY(index_name:Rating_Tokens in SearchAlbumsMixedIndex predicate:'(o r%01%0E5%FA%93%1A%00%00%01%04 r%01%1Ck%F5&4%00%00%01%02...(length 1371)') AND SQUERY(index_name:AlbumTitle_Tokens in SearchAlbumsMixedIndex predicate:$oo_tvf_0))

search index に stored されていない列を参照すると、base table への back join が現れることがある。

SELECT AlbumId, Cover
FROM SearchAlbums@{FORCE_INDEX=SearchAlbumsMixedIndex}
WHERE SEARCH(AlbumTitle_Tokens, "car")
  AND Rating > 4;
=== full-text-search/mixed-back-join ===
SELECT AlbumId, Cover FROM SearchAlbums@{FORCE_INDEX=SearchAlbumsMixedIndex} WHERE SEARCH(AlbumTitle_Tokens, "car") AND Rating > 4
+-----+------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                 |
+-----+------------------------------------------------------------------------------------------+
|   0 | Cross Apply <Row>                                                                        |
|   1 | +- [Input] VerifyDeterminism <Row>                                                       |
|   2 | |  +- TVF <Row> (Name: Search Query Conversion)                                          |
|   3 | |     +- Unit Relation <Row>                                                             |
|   7 | +- [Map] Distributed Union on _Search2aryIndex_SearchAlbumsMixedIndex <Row>              |
|  *8 |    +- Distributed Cross Apply <Row>                                                      |
|   9 |       +- [Input] Create Batch <Batch>                                                    |
|  10 |       |  +- RowToDataBlock                                                               |
|  11 |       |     +- Local Distributed Union <Row>                                             |
| *12 |       |        +- Filter Scan <Row> (seekable_key_size: 0)                               |
| *13 |       |           +- SearchIndex Scan on SearchAlbumsMixedIndex <Row> (scan_method: Row) |
|  29 |       +- [Map] Serialize Result <Row>                                                    |
|  30 |          +- Cross Apply <Row>                                                            |
|  31 |             +- [Input] KeyRangeAccumulator <Row>                                         |
|  32 |             |  +- DataBlockToRow                                                         |
|  33 |             |     +- Batch Scan on $v2 <Batch> (scan_method: Batch)                      |
|  38 |             +- [Map] Local Distributed Union <Row>                                       |
|  39 |                +- Filter Scan <Row> (seekable_key_size: 0)                               |
| *40 |                   +- Table Scan on SearchAlbums <Row> (scan_method: Row)                 |
+-----+------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  8: Split Range: (($SearchAlbums_key_SingerId' = $SearchAlbums_key_SingerId) AND ($AlbumId' = $AlbumId))
 12: Residual Condition: ($Rating > 4)
 13: Search Predicate: (SQUERY(index_name:Rating_Tokens in SearchAlbumsMixedIndex predicate:'(o r%01%0E5%FA%93%1A%00%00%01%04 r%01%1Ck%F5&4%00%00%01%02...(length 1371)') AND SQUERY(index_name:AlbumTitle_Tokens in SearchAlbumsMixedIndex predicate:$oo_tvf_0))
 40: Seek Condition: (($SearchAlbums_key_SingerId' = $batched_SearchAlbums_key_SingerId') AND ($AlbumId' = $batched_AlbumId'))

Filter Scan

Scan のすぐ上に位置し、スキャンに伴って処理できるフィルタを行う。現在の QueryPlan では display_name / displayName 自体が Filter Scan とスペース入りで返る。 以前の実行計画や古い説明では FilterScan と表記されることがあったが、現行の operator name としては Filter Scan を使う。 Scan の一部として働くため executionStats を持たず、実行時の挙動は Scan 側の rows, filtered_rows などを通して確認できる。

kind type variable? multiple? description
RELATIONAL フィルタの入力となる Scan
SCALAR Seek Condition スキャン対象のキー範囲を絞るシークに使う Function であり、 アクセス述語に対応する。
SCALAR Residual Condition スキャン後のフィルタに使う Function であり、フィルタ述語に対応する。
SCALAR Timestamp Condition ALLOW_TIMESTAMP_PREDICATE_PUSHDOWN=TRUE の場合に、commit timestamp column への timestamp predicate を Scan 側へ push down する条件

ALLOW_TIMESTAMP_PREDICATE_PUSHDOWN=TRUE を使うと、allow_commit_timestamp = true の timestamp column に対する filter が Timestamp Condition child link として table scan に付くことがある。ALLOW_TIMESTAMP_PREDICATE_PUSHDOWN=FALSE では、同じ query でも residual condition のみになる。locality group や age-based tiered storage は performance goal には関係するが、少なくとも plan 上の Timestamp Condition を観測するだけなら必須ではなかった。

Filter Scan の再現クエリと実行計画
SELECT LastName
FROM Singers
WHERE SingerId = 1;
+----+------------------------------------------------------------+
| ID | Operator                                                   |
+----+------------------------------------------------------------+
| *0 | Distributed Union on Singers <Row>                         |
|  1 | +- Local Distributed Union <Row>                           |
|  2 |    +- Serialize Result <Row>                               |
|  3 |       +- Filter Scan <Row> (seekable_key_size: 0)          |
| *4 |          +- Table Scan on Singers <Row> (scan_method: Row) |
+----+------------------------------------------------------------+
Predicates(identified by ID):
 0: Split Range: ($SingerId = 1)
 4: Seek Condition: ($SingerId = 1)

Recursive Spool Scan

Graph query の recursive path などで、Recursive Union の再帰ステップから前回までの中間結果を参照するために現れる。 通常の repeated CTE では SpoolScan という表示名で観測されるが、recursive plan では Recursive Spool Scan という別の表示名の leaf operator として観測される。 公式ドキュメントでは独立した operator 節はないが、Recursive Union の説明内で recursive spool scan として言及されている。

確認できている範囲では Relational operator の子を持たない。

Recursive Spool Scan の再現 SQL

以下は該当 operator を観測できる再現 SQL の例である。対応する実行計画は Recursive Union の details に含めている。

GRAPH MusicGraph
MATCH (singer:Singers {singerId:42})-[c:CollabWith]->{1,2}(featured:Singers)
RETURN singer.SingerId AS singer, featured.SingerId AS featured;

Unit Relation

特に値を持たない単一の行を生成する。 Unit Relation を受ける Compute や Serialize Result で実際の列の値が設定される。 例: SELECT 42, SELECT 42 UNION ALL SELECT 43

kind type variable? multiple? description
SCALAR Yes 1 を表現する Constant が常に指定される。
Unit Relation / Constant / Function の再現クエリと実行計画
SELECT 1 + 2 AS Result;
=== leaf/unit-relation-constant-function ===
SELECT 1 + 2 AS Result
+----+------------------------+
| ID | Operator               |
+----+------------------------+
|  0 | Serialize Result <Row> |
|  1 | +- Unit Relation <Row> |
+----+------------------------+

Unary operators

Relational operator の子を1つだけ持つ Relational operator 群。

Aggregate

GROUP BY に対応する集約を行う。 入力がインデックス等で既にソート済であり、その順序で集約することでハッシュテーブルを使わなくて良い時は iterator_type が Stream となり Stream Aggregate と呼ばれる。 GROUP@{GROUP_METHOD=HASH_GROUP}GROUP@{GROUP_METHOD=STREAM_GROUP} で同じ Aggregate operator の iterator_type が Hash / Stream に切り替わるため、実行計画の検査では Aggregate という operator 名だけでなく metadata も確認する必要がある。

Metadata
key values description
call_type Local もしくは Global
iterator_type Hash, Stream, もしくは未指定 Stream か Hash による処理方法の区別を示す。
scalar_aggregate true か未指定
kind type variable? multiple? description
RELATIONAL 入力
SCALAR Key Yes Yes scalar_aggregate=true の時には存在しない。集約に使うキーを示す。
SCALAR Agg Yes Yes Aggregate 対象の値を示す。
Hash Aggregate / Stream Aggregate の再現クエリと実行計画

Hash Aggregate:

SELECT s.SingerId, COUNT(*) AS SongCount
FROM Songs AS s
GROUP@{GROUP_METHOD=HASH_GROUP} BY s.SingerId;
+----+----------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                           |
+----+----------------------------------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row> (split_ranges_aligned)                                          |
|  1 | +- Serialize Result <Row>                                                                          |
|  2 |    +- Hash Aggregate <Row>                                                                         |
|  3 |       +- Local Distributed Union <Row>                                                             |
|  4 |          +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+----+----------------------------------------------------------------------------------------------------+

Stream Aggregate:

SELECT s.SingerId, COUNT(*) AS SongCount
FROM Songs AS s
GROUP@{GROUP_METHOD=STREAM_GROUP} BY s.SingerId;
+----+----------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                           |
+----+----------------------------------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row> (split_ranges_aligned)                                          |
|  1 | +- Serialize Result <Row>                                                                          |
|  2 |    +- Stream Aggregate <Row>                                                                       |
|  3 |       +- Local Distributed Union <Row>                                                             |
|  4 |          +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+----+----------------------------------------------------------------------------------------------------+

Apply Mutations

DML である INSERT, UPDATE, DELETE を処理する。サブツリーから row として取得した主キーと更新後の値を適用すると考えられるが、どの列をどのような式で更新するかのような定義は実行計画上は見えない。 INSERT IGNOREINSERT OR IGNOREINSERT OR UPDATEON CONFLICT DO NOTHINGON CONFLICT DO UPDATETHEN RETURNApply Mutations を含む plan shape として観測できた。Spanner Omni 2026.r1-beta では、PLAN において INSERT ... ASSERT_ROWS_MODIFIED 1ASSERT_ROWS_MODIFIED is not supported. で失敗した。

Metadata
key values description
operation_type INSERT, UPDATE, DELETE
table 更新対象のテーブル
kind type variable multiple? description
RELATIONAL 入力
Apply Mutations の再現クエリと実行計画
UPDATE Singers
SET LastName = "Smith"
WHERE SingerId = 1;
+----+---------------------------------------------------------------+
| ID | Operator                                                      |
+----+---------------------------------------------------------------+
|  0 | Apply Mutations on Singers <Row> (operation_type: UPDATE)     |
|  1 | +- Serialize Result <Row>                                     |
| *2 |    +- Distributed Union on Singers <Row>                      |
|  3 |       +- Local Distributed Union <Row>                        |
|  4 |          +- Filter Scan <Row> (seekable_key_size: 0)          |
| *5 |             +- Table Scan on Singers <Row> (scan_method: Row) |
+----+---------------------------------------------------------------+
Predicates(identified by ID):
 2: Split Range: ($SingerId = 1)
 5: Seek Condition: ($SingerId = 1)

BloomFilterBuild

(Undocumented) Bloom Filter を構築する。通常 Hash Join の Build 側に現れる。後に BLOOM_FILTER_MATCH を Condition に持つ Filter で使われる。

kind type variable? multiple? description
RELATIONAL 入力
BloomFilterBuild の再現クエリと実行計画
SELECT AlbumTitle
FROM Songs
JOIN Albums ON Albums.AlbumId = Songs.AlbumId;
=== distributed/distributed-apply ===
SELECT AlbumTitle FROM Songs JOIN Albums ON Albums.AlbumId = Songs.AlbumId
+-----+-------------------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                              |
+-----+-------------------------------------------------------------------------------------------------------+
|   0 | Serialize Result <Row>                                                                                |
|  *1 | +- Hash Join <Row> (join_type: INNER)                                                                 |
|   2 |    +- [Build] BloomFilterBuild <Row>                                                                  |
|   3 |    |  +- Distributed Union on SongsBySingerAlbumSongNameDesc <Row>                                    |
|   4 |    |     +- Local Distributed Union <Row>                                                             |
|   5 |    |        +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
|   9 |    +- [Probe] Distributed Union on AlbumsByAlbumTitle <Row>                                           |
|  10 |       +- Local Distributed Union <Row>                                                                |
| *11 |          +- Filter Scan <Row> (seekable_key_size: 0)                                                  |
|  12 |             +- Index Scan on AlbumsByAlbumTitle <Row> (Full scan, scan_method: Automatic)             |
+-----+-------------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  1: Condition: ($AlbumId_1 = $AlbumId)
 11: Residual Condition: BLOOM_FILTER_MATCH($existence_filter, $AlbumId_1)

Compute

入力のそれぞれの行に対して新しい列を追加する。

kind type variable? multiple? description
RELATIONAL 入力
SCALAR Yes Yes 新しく計算する値を示す
Compute の再現クエリと実行計画
SELECT 1 AS a, 2 AS b
UNION ALL SELECT 3 AS a, 4 AS b
UNION ALL SELECT 5 AS a, 6 AS b;
=== n-ary/union-all ===
SELECT 1 a, 2 b UNION ALL SELECT 3 a, 4 b UNION ALL SELECT 5 a, 6 b
+----+---------------------------------+
| ID | Operator                        |
+----+---------------------------------+
|  0 | Serialize Result <Row>          |
|  1 | +- Union All <Row>              |
|  2 |    +- Union Input               |
|  3 |    |  +- Compute <Row>          |
|  4 |    |     +- Unit Relation <Row> |
| 10 |    +- Union Input               |
| 11 |    |  +- Compute <Row>          |
| 12 |    |     +- Unit Relation <Row> |
| 18 |    +- Union Input               |
| 19 |       +- Compute <Row>          |
| 20 |          +- Unit Relation <Row> |
+----+---------------------------------+

Compute Struct

入力のそれぞれの行に対して STRUCT を生成する。 Compute Batch の入力や AS STRUCT を使ったサブクエリなどで現れる。

kind type variable? multiple? description
RELATIONAL 入力
SCALAR Yes Yes STRUCT の各フィールドを表す
SCALAR Scalar Yes 式で参照される Scalar Subquery(or Array Subquery) を指す。
Compute Struct / Array Subquery の再現クエリと実行計画
SELECT FirstName,
       ARRAY(
         SELECT AS STRUCT song.SongName, song.SongGenre
         FROM Songs AS song
         WHERE song.SingerId = singer.SingerId
       )
FROM Singers AS singer
WHERE singer.SingerId = 1;
=== unary/compute-struct ===
SELECT FirstName, ARRAY(SELECT AS STRUCT song.SongName, song.SongGenre FROM Songs AS song WHERE song.SingerId = singer.SingerId) FROM Singers AS singer WHERE singer.SingerId = 1
+-----+-------------------------------------------------------------------+
| ID  | Operator                                                          |
+-----+-------------------------------------------------------------------+
|  *0 | Distributed Union on Singers <Row> (split_ranges_aligned)         |
|   1 | +- Local Distributed Union <Row>                                  |
|   2 |    +- Serialize Result <Row>                                      |
|   3 |       +- Filter Scan <Row> (seekable_key_size: 0)                 |
|  *4 |       |  +- Table Scan on Singers <Row> (scan_method: Row)        |
|  12 |       +- [Scalar] Array Subquery                                  |
|  13 |          +- Local Distributed Union <Row>                         |
|  14 |             +- Compute Struct <Row>                               |
|  15 |                +- Filter Scan <Row> (seekable_key_size: 0)        |
| *16 |                   +- Table Scan on Songs <Row> (scan_method: Row) |
+-----+-------------------------------------------------------------------+
Predicates(identified by ID):
  0: Split Range: ($SingerId = 1)
  4: Seek Condition: ($SingerId = 1)
 16: Seek Condition: ($SingerId_1 = 1)

Create Batch

入力から batch を作成する。主に Distributed Cross Apply で入力をまとめて対応する replica に送り、 Batch Scan で参照するために使われる。 具体例は Distributed Cross ApplyPush Broadcast Hash JoinRecursive Union の再現クエリと実行計画で確認できる。

kind type variable multiple? description
RELATIONAL 入力
SCALAR variable Yes 生成される batch relation の field を定義する。v2.Batch.SingerIdv2.Batch.__row_id のような variable が付き、broadcast key、back join key、sort value、graph traversal key などが context に応じて現れる。

DataBlockToRow

DataBlockToRow は batch/data-block 形式の入力を行指向の relational stream に変換する。 index back join、batch/distributed 系の Apply Join、Graph query、Push Broadcast Hash Join などの内部で観測される。 Batch Scan から得た <Batch>Cross ApplyHash Join の入力に戻す箇所に現れることが多い。 公式ドキュメント上の operator 名は DataBlockToRowAdapter である。 これらの変換 operator は row-oriented execution と batch-oriented execution の境界で現れるため、EXECUTION_METHOD=ROW を指定すると消えることがある。 一方で SCAN_METHOD は scan 処理の row/batch/columnar を制御する hint であり、SCAN_METHOD=ROW だけでこれらの変換 operator が消えるとは限らない。 また、SCAN_METHOD=BATCH は apply join の右側などではサポートされない場合がある。

kind type variable? multiple? description
RELATIONAL data-block 形式の入力
DataBlockToRow / RowToDataBlock の再現クエリと実行計画
SELECT s.SongName, s.Duration
FROM Songs@{FORCE_INDEX=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");
+-----+--------------------------------------------------------------------------+
| ID  | Operator                                                                 |
+-----+--------------------------------------------------------------------------+
|  *0 | Distributed Union on SongsBySongName <Row>                               |
|  *1 | +- Distributed Cross Apply <Row>                                         |
|   2 |    +- [Input] Create Batch <Batch>                                       |
|   3 |    |  +- RowToDataBlock                                                  |
|   4 |    |     +- Local Distributed Union <Row>                                |
|   5 |    |        +- Filter Scan <Row> (seekable_key_size: 1)                  |
|  *6 |    |           +- Index Scan on SongsBySongName <Row> (scan_method: Row) |
|  19 |    +- [Map] Serialize Result <Row>                                       |
|  20 |       +- Cross Apply <Row>                                               |
|  21 |          +- [Input] KeyRangeAccumulator <Row>                            |
|  22 |          |  +- DataBlockToRow                                            |
|  23 |          |     +- Batch Scan on $v2 <Batch> (scan_method: Batch)         |
|  32 |          +- [Map] Local Distributed Union <Row>                          |
|  33 |             +- Filter Scan <Row> (seekable_key_size: 0)                  |
| *34 |                +- Table Scan on Songs <Row> (scan_method: Row)           |
+-----+--------------------------------------------------------------------------+
Predicates(identified by ID):
  0: Split Range: STARTS_WITH($SongName, 'B')
  1: Split Range: (($Songs_key_SingerId' = $Songs_key_SingerId) AND ($Songs_key_AlbumId' = $Songs_key_AlbumId) AND ($Songs_key_TrackId' = $Songs_key_TrackId))
  6: Seek Condition: STARTS_WITH($SongName, 'B')
 34: Seek Condition: (($Songs_key_SingerId' = $batched_Songs_key_SingerId') AND ($Songs_key_AlbumId' = $batched_Songs_key_AlbumId') AND ($Songs_key_TrackId' = $batched_Songs_key_TrackId'))

Filter

Scan とは独立して任意の箇所で Condition 述語で行をフィルタする。フィルタプッシュダウンができないようなサブクエリの外側の WHERE や、 GROUP BY の結果に対して適用する必要がある HAVING は Filter Scan ではなく Filter として処理される。

kind type variable multiple? description
RELATIONAL フィルタの入力となる Scan
SCALAR Condition 入力からフィルタする Function
Filter の再現クエリと実行計画
SELECT s.LastName
FROM (SELECT s.LastName FROM Singers AS s LIMIT 3) s
WHERE s.LastName LIKE 'Rich%';
=== unary/filter ===
SELECT s.LastName FROM (SELECT s.LastName FROM Singers AS s LIMIT 3) s WHERE s.LastName LIKE 'Rich%'
+----+--------------------------------------------------------------------------------------------+
| ID | Operator                                                                                   |
+----+--------------------------------------------------------------------------------------------+
|  0 | Serialize Result <Row>                                                                     |
| *1 | +- Filter <Row>                                                                            |
|  2 |    +- Global Limit <Row>                                                                   |
|  3 |       +- Distributed Union on SingersByFirstLastName <Row>                                 |
|  4 |          +- Local Limit <Row>                                                              |
|  5 |             +- Local Distributed Union <Row>                                               |
|  6 |                +- Index Scan on SingersByFirstLastName <Row> (Full scan, scan_method: Row) |
+----+--------------------------------------------------------------------------------------------+
Predicates(identified by ID):
 1: Condition: STARTS_WITH($LastName, 'Rich')

Limit

Limit のみを行う。 ORDER BY を指定しないか、キー順と一致する順序で指定して LIMIT を指定した際に現れる。

Metadata
key values description
call_type Local もしくは Global
kind type variable multiple? description
RELATIONAL ソート対象の入力
SCALAR Limit 取得する行数を指定する
SCALAR Offset OFFSET 指定時に読み飛ばす行数を指定する
Limit の再現クエリと実行計画
SELECT s.SongName
FROM Songs AS s
LIMIT 3;
=== unary/limit ===
SELECT s.SongName FROM Songs AS s LIMIT 3
+----+-------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                        |
+----+-------------------------------------------------------------------------------------------------+
|  0 | Global Limit <Row>                                                                              |
|  1 | +- Distributed Union on SongsBySingerAlbumSongNameDesc <Row>                                    |
|  2 |    +- Serialize Result <Row>                                                                    |
|  3 |       +- Local Limit <Row>                                                                      |
|  4 |          +- Local Distributed Union <Row>                                                       |
|  5 |             +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Row) |
+----+-------------------------------------------------------------------------------------------------+

Local Split Union

ローカル server に保存されている table split を探し、それぞれの split 上で subquery を実行して結果を union する operator。 公式ドキュメントでは placement table の scan で現れる operator として説明されている。 現時点のフィードバックでは、サンプルスキーマだけで Local Split Union を安定して出す再現クエリは確認できていない。placement や locality を含む構成が必要と考えられる。

MiniBatchAssign

(Undocumented) MiniBatchKeyOrder より下にある以外はよく分かっていない。

Shard 最適化クエリ などで確認されている。 この operator を含む実行計画は MiniBatchAssign / MiniBatchKeyOrder / RowCount の再現クエリと実行計画で確認できる。

kind type variable? multiple? description
RELATIONAL 入力となる Relation operator。
SCALAR Scalar operator。バッチサイズを指示している?

MiniBatchKeyOrder

(Undocumented)

MiniBatchAssign より上にある以外はよく分かっていない。

Shard 最適化クエリ などで確認されている。 この operator を含む実行計画は MiniBatchAssign / MiniBatchKeyOrder / RowCount の再現クエリと実行計画で確認できる。

kind type variable? multiple? description
RELATIONAL 入力となる Relation operator。

Minor Sort

(Undocumented)

ストリームの一部に対して ORDER BY の処理をする。Sort とほぼ同じだが、テーブルやインデックスとソート順の prefix が一致して全体の Sort が必要ない場合に使われる。

Metadata
key values description
call_type Local もしくは Global
kind type variable? multiple? description
RELATIONAL ソート対象の入力となる Relation operator。
SCALAR MajorKey Yes Yes ソートキーのうち、入力でソート済な部分が順に指定される。
SCALAR MinorKey Yes Yes ソートキーのうち、入力でソートされていない部分が順に指定される。
SCALAR Value Yes Yes ソートキー以外で取り出す列が順に指定される。
Minor Sort の再現クエリと実行計画

ORDER BY が先頭キーの順序とは部分的に合っているが、残りのキーで追加の局所的な sort が必要になる例:

SELECT SingerId, AlbumTitle
FROM Albums
ORDER BY SingerId, AlbumTitle;
+----+----------------------------------------------------------------------------+
| ID | Operator                                                                   |
+----+----------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row> (split_ranges_aligned)                  |
|  1 | +- Serialize Result <Row>                                                  |
|  2 |    +- Minor Sort <Row>                                                     |
|  3 |       +- Local Distributed Union <Row>                                     |
|  4 |          +- Table Scan on Albums <Row> (Full scan, scan_method: Automatic) |
+----+----------------------------------------------------------------------------+

STREAM_GROUP の入力順序を整える例:

SELECT SingerId, SongGenre
FROM Songs
GROUP@{GROUP_METHOD=STREAM_GROUP} BY SingerId, SongGenre;
+----+------------------------------------------------------------------------------+
| ID | Operator                                                                     |
+----+------------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row> (split_ranges_aligned)                    |
|  1 | +- Serialize Result <Row>                                                    |
|  2 |    +- Stream Aggregate <Row>                                                 |
|  3 |       +- Minor Sort <Row>                                                    |
|  4 |          +- Local Distributed Union <Row>                                    |
|  5 |             +- Table Scan on Songs <Row> (Full scan, scan_method: Automatic) |
+----+------------------------------------------------------------------------------+

Minor Sort Limit

(Undocumented)

ORDER BY と LIMIT 両方の処理をする operator。Sort Limit とほぼ同じだが、テーブルやインデックスとソート順の prefix が一致して全体の Sort が必要ない場合に使われる。

Metadata
key values description
call_type Local もしくは Global
kind type variable? multiple? description
RELATIONAL ソート対象の入力
SCALAR Limit 取得する行数
SCALAR MajorKey Yes Yes ソートキーのうち、入力でソート済な部分が順に指定される。
SCALAR MinorKey Yes Yes ソートキーのうち、入力でソートされていない部分が順に指定される。
SCALAR Value Yes Yes ソートキー以外で取り出す列が順に指定される。
Minor Sort Limit の再現クエリと実行計画
SELECT SingerId, AlbumTitle
FROM Albums
WHERE SingerId > 0
ORDER BY SingerId, AlbumTitle
LIMIT 3;
+----+-----------------------------------------------------------------+
| ID | Operator                                                        |
+----+-----------------------------------------------------------------+
|  0 | Global Limit <Row>                                              |
| *1 | +- Distributed Union on Singers <Row> (split_ranges_aligned)    |
|  2 |    +- Serialize Result <Row>                                    |
|  3 |       +- Local Minor Sort Limit <Row>                           |
|  4 |          +- Local Distributed Union <Row>                       |
|  5 |             +- Filter Scan <Row> (seekable_key_size: 1)         |
| *6 |                +- Table Scan on Albums <Row> (scan_method: Row) |
+----+-----------------------------------------------------------------+
Predicates(identified by ID):
 1: Split Range: ($SingerId > 0)
 6: Seek Condition: ($SingerId > 0)

Random Id Assign

TABLESAMPLE を使用した際に現れる。 Filter operator と組み合わせることで、ランダムに割り当てた値を元にフィルタすることでサンプリングを実現する。

kind type variable? multiple? description
RELATIONAL 入力
SCALAR Yes description が <random id> となる Reference を指す variable であり、後に Filter で名前が参照される。
Random Id Assign の再現クエリと実行計画
SELECT s.SongName
FROM Songs AS s TABLESAMPLE BERNOULLI (10 PERCENT);
=== unary/tablesample-bernoulli ===
SELECT s.SongName FROM Songs AS s TABLESAMPLE BERNOULLI (10 PERCENT)
+----+-------------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                              |
+----+-------------------------------------------------------------------------------------------------------+
|  0 | Distributed Union on SongsBySingerAlbumSongNameDesc <Row>                                             |
|  1 | +- Serialize Result <Row>                                                                             |
| *2 |    +- Filter <Row>                                                                                    |
|  3 |       +- Random Id Assign <Row>                                                                       |
|  4 |          +- Local Distributed Union <Row>                                                             |
|  5 |             +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+----+-------------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
 2: Condition: ($v1 < 900719925474099U)

RowCount

(Undocumented)

Shard 最適化クエリ などで確認されている。

kind type variable position description
RELATIONAL 入力
MiniBatchAssign / MiniBatchKeyOrder / RowCount の再現クエリと実行計画

この例の実行計画だけは Cloud Spanner の optimizer version 5 で出力したものである。Spanner Omni 2026.r1-beta では同じ SQL でもこれらの operator を含まない形になることがある。

@{OPTIMIZER_VERSION=5}
SELECT *
FROM Songs@{FORCE_INDEX=SongsBySongName}
ORDER BY SongName DESC
LIMIT 1;
+-----+-------------------------------------------------------------------------------------------------+
| ID  | Operator <execution_method>                                                                     |
+-----+-------------------------------------------------------------------------------------------------+
|   0 | Global Limit <Row>                                                                              |
|  *1 | +- Distributed Cross Apply <Row> (order_preserving: true)                                       |
|   2 |    +- [Input] Create Batch <Row>                                                                |
|   3 |    |  +- Compute Struct <Row>                                                                   |
|   4 |    |     +- Local Limit <Row>                                                                   |
|   5 |    |        +- Distributed Union on SongsBySongName <Row> (preserve_subquery_order: true)       |
|   6 |    |           +- Local Sort Limit <Row>                                                        |
|   7 |    |              +- Local Distributed Union <Row>                                              |
|   8 |    |                 +- Index Scan on SongsBySongName <Row> (Full scan, scan_method: Automatic) |
|  28 |    +- [Map] Serialize Result <Row>                                                              |
|  29 |       +- MiniBatchKeyOrder <Row>                                                                |
|  30 |          +- Minor Sort Limit <Row>                                                              |
|  31 |             +- RowCount <Row>                                                                   |
|  32 |                +- Cross Apply <Row>                                                             |
|  33 |                   +- [Input] RowCount <Row>                                                     |
|  34 |                   |  +- KeyRangeAccumulator <Row>                                               |
|  35 |                   |     +- Local Minor Sort <Row>                                               |
|  36 |                   |        +- MiniBatchAssign <Row>                                             |
|  37 |                   |           +- Batch Scan on $v2 <Row> (scan_method: Row)                     |
|  53 |                   +- [Map] Local Distributed Union <Row>                                        |
|  54 |                      +- Filter Scan <Row> (seekable_key_size: 0)                                |
| *55 |                         +- Table Scan on Songs <Row> (scan_method: Row)                         |
+-----+-------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  1: Split Range: (($SingerId' = $sort_SingerId) AND ($AlbumId' = $sort_AlbumId) AND ($TrackId' = $sort_TrackId))
 55: Seek Condition: (($SingerId' = $sort_batched_SingerId) AND ($AlbumId' = $sort_batched_AlbumId) AND ($TrackId' = $sort_batched_TrackId))

RowToDataBlock

RowToDataBlock は行指向の relational stream を batch/data-block 形式に変換する。 DataBlockToRow と対になって、Distributed Cross Apply、Push Broadcast Hash Join、Graph query などで remote execution や batch execution に渡す入力を作る箇所に現れる。 公式ドキュメント上の operator 名は RowToDataBlockAdapter である。

kind type variable? multiple? description
RELATIONAL 行指向の入力
RowToDataBlock の再現 SQL

以下は該当 operator を観測できる再現 SQL の例である。 同じクエリの実行計画は DataBlockToRow / RowToDataBlock の再現クエリと実行計画で示している。

SELECT s.SongName, s.Duration
FROM Songs@{FORCE_INDEX=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");

Serialize Result

最終的に ResultSet に含まれる値を組み立てる。これよりも上の operator で row の値を操作することはない。Compute Struct の特殊なケースであることが公式ドキュメントでも説明されている通り、同様の構造を持つ。

kind type variable? multiple? description
RELATIONAL 入力
SCALAR Yes metadata.rowType.fields に現れる順で対応する式を表現する
SCALAR Scalar Yes 式で参照される Scalar Subquery(or Array Subquery) を指す。
Serialize Result の再現クエリと実行計画
SELECT s.SongName
FROM Songs AS s;
=== execution-plans/simple-scan ===
SELECT s.SongName FROM Songs AS s
+----+-------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                        |
+----+-------------------------------------------------------------------------------------------------+
|  0 | Distributed Union on SongsBySingerAlbumSongNameDesc <Row>                                       |
|  1 | +- Local Distributed Union <Row>                                                                |
|  2 |    +- Serialize Result <Row>                                                                    |
|  3 |       +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+----+-------------------------------------------------------------------------------------------------+

Sort

ORDER BY によるソートのみをする operator。Sort Limit とほぼ同じだが、 LIMIT を設定しない場合はこちらになる。

kind type variable? multiple? description
RELATIONAL ソート対象の入力
SCALAR Key Yes Yes ソートキーとなる列が Reference で順に指定される。
SCALAR Value Yes Yes ソートキー以外で取り出す列が Reference で順に指定される。
Sort の再現クエリと実行計画
SELECT s.SongGenre
FROM Songs AS s
ORDER BY SongGenre;
=== unary/sort ===
SELECT s.SongGenre FROM Songs AS s ORDER BY SongGenre
+----+---------------------------------------------------------------------------+
| ID | Operator                                                                  |
+----+---------------------------------------------------------------------------+
|  0 | Distributed Union on Songs <Row> (preserve_subquery_order: true)          |
|  1 | +- Serialize Result <Row>                                                 |
|  2 |    +- Sort <Row>                                                          |
|  3 |       +- Local Distributed Union <Row>                                    |
|  4 |          +- Table Scan on Songs <Row> (Full scan, scan_method: Automatic) |
+----+---------------------------------------------------------------------------+

Sort Limit

ORDER BY と LIMIT 両方の処理をする operator。Sort とほぼ同じだが、 LIMIT を使う場合はこちらになる。

Metadata
key values description
call_type Local もしくは Global
kind type variable? multiple? description
RELATIONAL ソート対象の入力
SCALAR Limit 取得する行数
SCALAR Offset 読み飛ばす行数
SCALAR Key Yes Yes ソートキーが順に指定される。
SCALAR Value Yes Yes ソートキー以外で取り出す列が順に指定される。
Sort Limit の再現クエリと実行計画
SELECT s.SongGenre
FROM Songs AS s
ORDER BY SongGenre
LIMIT 3;
=== unary/sort-limit ===
SELECT s.SongGenre FROM Songs AS s ORDER BY SongGenre LIMIT 3
+----+------------------------------------------------------------------------------+
| ID | Operator                                                                     |
+----+------------------------------------------------------------------------------+
|  0 | Global Limit <Row>                                                           |
|  1 | +- Distributed Union on Songs <Row> (preserve_subquery_order: true)          |
|  2 |    +- Serialize Result <Row>                                                 |
|  3 |       +- Local Sort Limit <Row>                                              |
|  4 |          +- Local Distributed Union <Row>                                    |
|  5 |             +- Table Scan on Songs <Row> (Full scan, scan_method: Automatic) |
+----+------------------------------------------------------------------------------+

TVF

Table-valued function の入力を読み、指定された関数を適用して出力を生成する operator。 入力と同じ行数を返す mapping のほか、入力より多い行を返す generator や、入力より少ない行を返す filter としても動作し得る。 Change Stream のほか、Full Text Search の SEARCH(...) では Search Query Conversion という TVF が観測されることがある。この TVF は検索文字列を search index 用の query expression に変換し、その出力が SearchIndex ScanSearch Predicate から参照される。観測した SEARCH_SUBSTRING(...) の実行計画ではこの TVF は現れなかった。

ChangeStream TVF の再現クエリと実行計画

必要な DDL:

CREATE TABLE Singers (
  SingerId INT64 NOT NULL,
  FirstName STRING(1024)
) PRIMARY KEY(SingerId);

CREATE CHANGE STREAM EverythingStream
FOR ALL;

再現 SQL:

SELECT ChangeRecord
FROM READ_EverythingStream (
  start_timestamp => TIMESTAMP "2026-05-06T00:00:00Z"
);
+----+---------------------------+
| ID | Operator                  |
+----+---------------------------+
|  0 | Serialize Result <Row>    |
|  1 | +- ChangeStream TVF <Row> |
+----+---------------------------+
Search Query Conversion TVF の再現クエリと実行計画

必要な DDL:

CREATE TABLE SearchAlbums (
  SingerId INT64 NOT NULL,
  AlbumId STRING(MAX) NOT NULL,
  AlbumTitle STRING(MAX),
  AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
) PRIMARY KEY(SingerId, AlbumId);

CREATE SEARCH INDEX SearchAlbumsTitleIndex
ON SearchAlbums(AlbumTitle_Tokens);

再現 SQL:

SELECT AlbumId
FROM SearchAlbums
WHERE SEARCH(AlbumTitle_Tokens, "friday OR monday");
=== full-text-search/search ===
SELECT AlbumId FROM SearchAlbums WHERE SEARCH(AlbumTitle_Tokens, "friday OR monday")
+-----+---------------------------------------------------------------------------------+
| ID  | Operator                                                                        |
+-----+---------------------------------------------------------------------------------+
|   0 | Cross Apply <Row>                                                               |
|   1 | +- [Input] VerifyDeterminism <Row>                                              |
|   2 | |  +- TVF <Row> (Name: Search Query Conversion)                                 |
|   3 | |     +- Unit Relation <Row>                                                    |
|   7 | +- [Map] Distributed Union on _Search2aryIndex_SearchAlbumsTitleIndex <Row>     |
|   8 |    +- Local Distributed Union <Row>                                             |
|   9 |       +- Serialize Result <Row>                                                 |
| *10 |          +- SearchIndex Scan on SearchAlbumsTitleIndex <Row> (scan_method: Row) |
+-----+---------------------------------------------------------------------------------+
Predicates(identified by ID):
 10: Search Predicate: SQUERY(index_name:AlbumTitle_Tokens in SearchAlbumsTitleIndex predicate:$oo_tvf_0)

SpoolBuild

(Undocumented) WITH などによる一時テーブルを保存する。SpoolScan によって読み取られる。 通常の repeated CTE で観測した SpoolScan は、Scan operator に metadata.scan_type: SpoolScan が付く形ではなく、raw PlanNode.display_name: SpoolScanmetadata.spool_name を持つ独立した表示名として現れた。

Metadata
key values description
spool_name 構築する spool の名前
kind type variable? multiple? description
RELATIONAL 保存対象の入力
SCALAR 一時テーブルの列
SpoolBuild の再現クエリと実行計画
WITH CTE AS (
  SELECT 1 AS PK, "foo" AS col
)
SELECT *
FROM CTE c1
JOIN CTE c2 USING (PK);
+-----+----------------------------------------------------+
| ID  | Operator                                           |
+-----+----------------------------------------------------+
|   0 | Serialize Result <Row>                             |
|   1 | +- Cross Apply <Row>                               |
|   2 |    +- [Input] SpoolBuild <Row> (spool_name: CTE)   |
|   3 |    |  +- Compute <Row>                             |
|   4 |    |     +- Unit Relation <Row>                    |
|  10 |    +- [Map] Cross Apply <Row>                      |
|  11 |       +- [Input] SpoolScan <Row> (spool_name: CTE) |
| *14 |       +- [Map] Filter <Row>                        |
|  15 |          +- SpoolScan <Row> (spool_name: CTE)      |
+-----+----------------------------------------------------+
Predicates(identified by ID):
 14: Condition: ($PK_2 = $PK_1)

Union Input

Union All operator のそれぞれの枝からの入力を揃えるための operator。

kind type variable? multiple? description
RELATIONAL それぞれの枝の本体
SCALAR input_{n} Union All operator の結果の n 列目となる式
Union Input の再現クエリと実行計画
SELECT 1 AS a, 2 AS b
UNION ALL SELECT 3 AS a, 4 AS b
UNION ALL SELECT 5 AS a, 6 AS b;
=== n-ary/union-all ===
SELECT 1 a, 2 b UNION ALL SELECT 3 a, 4 b UNION ALL SELECT 5 a, 6 b
+----+---------------------------------+
| ID | Operator                        |
+----+---------------------------------+
|  0 | Serialize Result <Row>          |
|  1 | +- Union All <Row>              |
|  2 |    +- Union Input               |
|  3 |    |  +- Compute <Row>          |
|  4 |    |     +- Unit Relation <Row> |
| 10 |    +- Union Input               |
| 11 |    |  +- Compute <Row>          |
| 12 |    |     +- Unit Relation <Row> |
| 18 |    +- Union Input               |
| 19 |       +- Compute <Row>          |
| 20 |          +- Unit Relation <Row> |
+----+---------------------------------+

Binary operators

Relational operator の子を2つ持つ Relational operator 群。

Anti-Semi Apply

replica 内にローカルな Anti Semi Apply Join を行う。 NOT INNOT EXISTS など、Input 側の行に対して Map 側に対応する行が存在しないことを判定する subquery predicate で現れる。 BATCH_MODE=TRUE では外側に Distributed Anti Semi Apply が現れ、その Map 側の内部でローカルな Anti-Semi Apply が使われることがある。

kind type variable multiple? description
RELATIONAL (Input) 駆動表となる入力側のサブツリー
RELATIONAL Map Input 側の値に応じて実行されるサブツリー
Anti-Semi Apply / Semi Apply の再現クエリと実行計画

Semi Apply:

@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=FALSE}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE s.SingerId IN (SELECT a.SingerId FROM Albums AS a);
+----+-------------------------------------------------------------------------------------+
| ID | Operator                                                                            |
+----+-------------------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row> (split_ranges_aligned)                           |
|  1 | +- Local Distributed Union <Row>                                                    |
|  2 |    +- Serialize Result <Row>                                                        |
|  3 |       +- Semi Apply <Row>                                                           |
|  4 |          +- [Input] Table Scan on Singers <Row> (Full scan, scan_method: Automatic) |
|  7 |          +- [Map] Local Distributed Union <Row>                                     |
|  8 |             +- Filter Scan <Row> (seekable_key_size: 0)                             |
| *9 |                +- Table Scan on Albums <Row> (scan_method: Row)                     |
+----+-------------------------------------------------------------------------------------+
Predicates(identified by ID):
 9: Seek Condition: ($SingerId_1 = $SingerId)

Anti-Semi Apply:

@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=FALSE}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE NOT EXISTS (SELECT 1 FROM Albums AS a WHERE a.SingerId = s.SingerId);
+----+-------------------------------------------------------------------------------------+
| ID | Operator                                                                            |
+----+-------------------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row> (split_ranges_aligned)                           |
|  1 | +- Local Distributed Union <Row>                                                    |
|  2 |    +- Serialize Result <Row>                                                        |
|  3 |       +- Anti-Semi Apply <Row>                                                      |
|  4 |          +- [Input] Table Scan on Singers <Row> (Full scan, scan_method: Automatic) |
|  7 |          +- [Map] Local Distributed Union <Row>                                     |
|  8 |             +- Filter Scan <Row> (seekable_key_size: 0)                             |
| *9 |                +- Table Scan on Albums <Row> (scan_method: Row)                     |
+----+-------------------------------------------------------------------------------------+
Predicates(identified by ID):
 9: Seek Condition: ($SingerId_1 = $SingerId)

Cross Apply

replica 内にローカルな Apply Join を行う。Input 側の Relational operator から取り出した値を使って、対応する Map 側の Relational operator を実行することで JOIN を実現する。 主に Distributed Cross Apply の中で使われる場合と、 INTERLEAVE されたテーブル間の JOIN で使われる場合がある。 古い query plan examples では JOIN_TYPE=APPLY_JOIN という hint spelling も見られ、観測した matrix では JOIN_METHOD=APPLY_JOIN と同様に Apply Join 系の plan shape になった。現行のドキュメントに合わせるなら JOIN_METHOD=APPLY_JOIN を使う。

kind type variable multiple? description
RELATIONAL (Input) いわゆる駆動表となる入力側のサブツリーであり、実際には type を持たないが Web UI やドキュメント等で Input と表示される。
RELATIONAL Map Input 側の値に応じて実行されるサブツリー
Cross Apply の再現クエリと実行計画
SELECT si.FirstName,
       (
         SELECT so.SongName
         FROM Songs AS so
         WHERE so.SingerId = si.SingerId
         LIMIT 1
       )
FROM Singers AS si;
=== binary/cross-apply ===
SELECT si.FirstName, (SELECT so.SongName FROM Songs AS so WHERE so.SingerId = si.SingerId LIMIT 1) FROM Singers AS si
+-----+-----------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                      |
+-----+-----------------------------------------------------------------------------------------------+
|   0 | Distributed Union on Singers <Row> (split_ranges_aligned)                                     |
|   1 | +- Local Distributed Union <Row>                                                              |
|   2 |    +- Serialize Result <Row>                                                                  |
|   3 |       +- Cross Apply <Row>                                                                    |
|   4 |          +- [Input] Table Scan on Singers <Row> (Full scan, scan_method: Automatic)           |
|   7 |          +- [Map] Stream Aggregate <Row> (scalar_aggregate: true)                             |
|   8 |             +- Global Limit <Row>                                                             |
|   9 |                +- Local Distributed Union <Row>                                               |
|  10 |                   +- Filter Scan <Row> (seekable_key_size: 0)                                 |
| *11 |                      +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (scan_method: Row) |
+-----+-----------------------------------------------------------------------------------------------+
Predicates(identified by ID):
 11: Seek Condition: ($SingerId_1 = $SingerId)

Semi Apply

replica 内にローカルな Semi Apply Join を行う。 INEXISTS など、Input 側の行に対して Map 側に対応する行が存在することを判定する subquery predicate で現れる。 BATCH_MODE=TRUE では外側に Distributed Semi Apply が現れ、その Map 側の内部でローカルな Semi Apply が使われることがある。

kind type variable multiple? description
RELATIONAL (Input) 駆動表となる入力側のサブツリー
RELATIONAL Map Input 側の値に応じて実行されるサブツリー
Semi Apply の再現クエリと実行計画
@{JOIN_METHOD=APPLY_JOIN}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE s.SingerId IN (
  SELECT a.SingerId
  FROM Albums AS a
);
=== subquery-join-hint-matrix/in/join_method_apply_join ===
@{JOIN_METHOD=APPLY_JOIN}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE s.SingerId IN (SELECT a.SingerId FROM Albums AS a)
+----+-------------------------------------------------------------------------------------+
| ID | Operator                                                                            |
+----+-------------------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row> (split_ranges_aligned)                           |
|  1 | +- Local Distributed Union <Row>                                                    |
|  2 |    +- Serialize Result <Row>                                                        |
|  3 |       +- Semi Apply <Row>                                                           |
|  4 |          +- [Input] Table Scan on Singers <Row> (Full scan, scan_method: Automatic) |
|  7 |          +- [Map] Local Distributed Union <Row>                                     |
|  8 |             +- Filter Scan <Row> (seekable_key_size: 0)                             |
| *9 |                +- Table Scan on Albums <Row> (scan_method: Row)                     |
+----+-------------------------------------------------------------------------------------+
Predicates(identified by ID):
 9: Seek Condition: ($SingerId_1 = $SingerId)

Outer Apply

replica 内にローカルな Outer Apply Join を行う。Input 側の Relational operator から取り出した値を使って、対応する Map 側の Relational operator を実行することで JOIN を実現する。 主に Distributed Outer Apply の中で使われる場合と、 INTERLEAVE されたテーブル間の JOIN で使われる場合がある。

kind type variable multiple? description
RELATIONAL (Input) いわゆる駆動表となる入力側のサブツリーであり、実際には type を持たないが Web UI やドキュメント等で Input と表示される。
RELATIONAL Map Input 側の値に応じて実行されるサブツリー
SCALAR Yes * 結合条件を満たさなかった時に Input 側から生成する行の定義
Outer Apply の再現クエリと実行計画
SELECT a.AlbumTitle, s.SongName
FROM Albums AS a
LEFT JOIN@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=FALSE} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId;
=== join-matrix/left/apply_join_batch_false ===
SELECT a.AlbumTitle, s.SongName
FROM Albums AS a LEFT JOIN@{JOIN_METHOD=APPLY_JOIN, BATCH_MODE=FALSE} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId
+-----+-----------------------------------------------------------------------------------------+
| ID  | Operator                                                                                |
+-----+-----------------------------------------------------------------------------------------+
|   0 | Distributed Union on Albums <Row> (split_ranges_aligned)                                |
|   1 | +- Local Distributed Union <Row>                                                        |
|   2 |    +- Serialize Result <Row>                                                            |
|   3 |       +- Outer Apply <Row>                                                              |
|   4 |          +- [Input] Table Scan on Albums <Row> (Full scan, scan_method: Automatic)      |
|   8 |          +- [Map] Local Distributed Union <Row>                                         |
|   9 |             +- Filter Scan <Row> (seekable_key_size: 0)                                 |
| *10 |                +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (scan_method: Row) |
+-----+-----------------------------------------------------------------------------------------+
Predicates(identified by ID):
 10: Seek Condition: (($SingerId_1 = $SingerId) AND ($AlbumId_1 = $AlbumId))

Hash Join

ハッシュ結合を行う。 Build 側の全 row を元にハッシュマップを構築してから Probe 側の各 row の値を使ってハッシュマップを引くことで Condition を評価して JOIN を行う。 subquery predicate に JOIN_METHOD=HASH_JOIN を指定した場合、通常の INNER/OUTER join だけでなく IN/EXISTS/NOT IN/NOT EXISTS 由来の semi/anti-semi 系にも Hash Join が使われ、join_typeBUILD_SEMIBUILD_ANTI_SEMI が現れる。 HASH_JOIN_BUILD_SIDE=BUILD_RIGHT などで物理的な Build / Probe 側を反転させると、LEFT OUTER JOIN で PROBE_OUTER、semi/anti-semi 系で PROBE_SEMIPROBE_ANTI_SEMI が現れることがある。

Metadata
key values description
join_type INNER, BUILD_OUTER, PROBE_OUTER, BUILD_SEMI, BUILD_ANTI_SEMI, … INNER 以外は Build と Probe がどちらかで意味が変わるので、 BUILD か PROBE が prefix になる。
kind type variable multiple? description
RELATIONAL Build 構築するハッシュマップ側になるサブツリー
RELATIONAL Probe ハッシュマップに通す側のサブツリー
SCALAR Condition JOIN 条件のうち、ハッシュテーブルを使える等値の条件を表す Function
SCALAR Residual Condition JOIN 条件のうち、ハッシュテーブルを使えない非等値の条件を表す Function
SCALAR Build Yes Yes Build 側からハッシュマップに含める列を指定
SCALAR Probe Yes Yes Probe 側から variable を定義
Hash Join の join_type の再現クエリと実行計画

通常の Hash Join:

SELECT a.AlbumTitle, s.SongName
FROM Albums AS a
JOIN@{JOIN_METHOD=HASH_JOIN} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId;
+----+----------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                           |
+----+----------------------------------------------------------------------------------------------------+
|  0 | Distributed Union on Albums <Row> (split_ranges_aligned)                                           |
|  1 | +- Serialize Result <Row>                                                                          |
| *2 |    +- Hash Join <Row> (join_type: INNER)                                                           |
|  3 |       +- [Build] Local Distributed Union <Row>                                                     |
|  4 |       |  +- Table Scan on Albums <Row> (Full scan, scan_method: Automatic)                         |
|  8 |       +- [Probe] Local Distributed Union <Row>                                                     |
|  9 |          +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+----+----------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
 2: Condition: (($SingerId = $SingerId_1) AND ($AlbumId = $AlbumId_1))

semi 系:

@{JOIN_METHOD=HASH_JOIN}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE s.SingerId IN (SELECT a.SingerId FROM Albums AS a);
+----+-----------------------------------------------------------------------------+
| ID | Operator                                                                    |
+----+-----------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row> (split_ranges_aligned)                   |
|  1 | +- Serialize Result <Row>                                                   |
| *2 |    +- Hash Join <Row> (join_type: BUILD_SEMI)                               |
|  3 |       +- [Build] Local Distributed Union <Row>                              |
|  4 |       |  +- Table Scan on Singers <Row> (Full scan, scan_method: Automatic) |
|  7 |       +- [Probe] Local Distributed Union <Row>                              |
|  8 |          +- Table Scan on Albums <Row> (Full scan, scan_method: Automatic)  |
+----+-----------------------------------------------------------------------------+
Predicates(identified by ID):
 2: Condition: ($SingerId = $SingerId_1)

anti-semi 系:

@{JOIN_METHOD=HASH_JOIN}
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE NOT EXISTS (SELECT 1 FROM Albums AS a WHERE a.SingerId = s.SingerId);
+----+-----------------------------------------------------------------------------+
| ID | Operator                                                                    |
+----+-----------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row> (split_ranges_aligned)                   |
|  1 | +- Serialize Result <Row>                                                   |
| *2 |    +- Hash Join <Row> (join_type: BUILD_ANTI_SEMI)                          |
|  3 |       +- [Build] Local Distributed Union <Row>                              |
|  4 |       |  +- Table Scan on Singers <Row> (Full scan, scan_method: Automatic) |
|  7 |       +- [Probe] Local Distributed Union <Row>                              |
|  8 |          +- Table Scan on Albums <Row> (Full scan, scan_method: Automatic)  |
+----+-----------------------------------------------------------------------------+
Predicates(identified by ID):
 2: Condition: ($SingerId_1 = $SingerId)

Merge Join

ソート済みの入力同士を join 条件のキー順に突き合わせる join operator。 JOIN@{JOIN_METHOD=MERGE_JOIN} や subquery predicate に対する JOIN_METHOD=MERGE_JOIN で観測される。 入力が join key 順に利用できる場合はそのまま使われるが、必要な順序が揃っていない場合は入力側に SortMinor Sort が追加される。

Metadata
key values description
join_configuration ONE_TO_MANY, MANY_TO_MANY, … join key の重複可能性に基づく構成
join_type INNER, … join の種類
kind type variable multiple? description
RELATIONAL Left 左辺の入力
RELATIONAL Right 右辺の入力
SCALAR Condition JOIN 条件を表す Function
Merge Join の再現クエリと実行計画

入力の順序をそのまま使える例:

SELECT a.AlbumTitle, s.SongName
FROM Albums AS a
JOIN@{JOIN_METHOD=MERGE_JOIN} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId;
+----+----------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                           |
+----+----------------------------------------------------------------------------------------------------+
|  0 | Distributed Union on Albums <Row> (split_ranges_aligned)                                           |
|  1 | +- Serialize Result <Row>                                                                          |
| *2 |    +- Merge Join <Row> (join_configuration: ONE_TO_MANY, join_type: INNER)                         |
|  3 |       +- [Left] Local Distributed Union <Row>                                                      |
|  4 |       |  +- Table Scan on Albums <Row> (Full scan, scan_method: Automatic)                         |
|  8 |       +- [Right] Local Distributed Union <Row>                                                     |
|  9 |          +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic) |
+----+----------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
 2: Condition: (($SingerId = $SingerId_1) AND ($AlbumId = $AlbumId_1))

Sort が追加される例:

SELECT a.AlbumTitle, s.SongName
FROM Albums AS a
JOIN@{JOIN_METHOD=MERGE_JOIN} Songs AS s
ON a.AlbumId = s.AlbumId;
+----+---------------------------------------------------------------------------------------------------------+
| ID | Operator                                                                                                |
+----+---------------------------------------------------------------------------------------------------------+
|  0 | Serialize Result <Row>                                                                                  |
| *1 | +- Merge Join <Row> (join_configuration: MANY_TO_MANY, join_type: INNER)                                |
|  2 |    +- [Left] Distributed Union on AlbumsByAlbumTitle <Row> (preserve_subquery_order: true)              |
|  3 |    |  +- Sort <Row>                                                                                     |
|  4 |    |     +- Local Distributed Union <Row>                                                               |
|  5 |    |        +- Index Scan on AlbumsByAlbumTitle <Row> (Full scan, scan_method: Automatic)               |
| 12 |    +- [Right] Distributed Union on SongsBySingerAlbumSongNameDesc <Row> (preserve_subquery_order: true) |
| 13 |       +- Sort <Row>                                                                                     |
| 14 |          +- Local Distributed Union <Row>                                                               |
| 15 |             +- Index Scan on SongsBySingerAlbumSongNameDesc <Row> (Full scan, scan_method: Automatic)   |
+----+---------------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  1: Condition: ($sort_AlbumId = $sort_AlbumId_1)

Recursive Union

Graph query の recursive path などで、初期入力と再帰ステップの入力を結合しながら繰り返し評価する operator。 公式ドキュメントでは binary operator として分類されており、base case と recursive case の 2 つの入力を持つ。 再帰ステップ側では Recursive Spool Scan を通して前回までの中間結果を参照する。

kind type variable? multiple? description
RELATIONAL base case を表す入力
RELATIONAL recursive case を表す入力
Recursive Union / Recursive Spool Scan の再現クエリと実行計画
GRAPH MusicGraph
MATCH (singer:Singers {singerId:42})-[c:CollabWith]->{1,2}(featured:Singers)
RETURN singer.SingerId AS singer, featured.SingerId AS featured;
+-----+-------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                  |
+-----+-------------------------------------------------------------------------------------------+
|  *0 | Distributed Union on Singers <Row>                                                        |
|   1 | +- Serialize Result <Row>                                                                 |
|   2 |    +- DataBlockToRow                                                                      |
|   3 |       +- Recursive Union <Batch>                                                          |
|   4 |          +- Union Input                                                                   |
|   5 |          |  +- RowToDataBlock                                                             |
|   6 |          |     +- Local Distributed Union <Row>                                           |
|   7 |          |        +- Compute <Row>                                                        |
|   8 |          |           +- Filter Scan <Row> (seekable_key_size: 0)                          |
|  *9 |          |              +- Table Scan on Singers <Row> (scan_method: Row)                 |
|  18 |          +- Union Input                                                                   |
| *19 |             +- Distributed Cross Apply <Batch>                                            |
|  20 |                +- [Input] Create Batch <Batch>                                            |
| *21 |                |  +- Distributed Cross Apply <Batch>                                      |
|  22 |                |     +- [Input] Create Batch <Batch>                                      |
|  23 |                |     |  +- Recursive Spool Scan <Batch>                                   |
|  28 |                |     +- [Map] RowToDataBlock                                              |
|  29 |                |        +- Cross Apply <Row>                                              |
|  30 |                |           +- [Input] KeyRangeAccumulator <Row>                           |
|  31 |                |           |  +- DataBlockToRow                                           |
|  32 |                |           |     +- Batch Scan on $v26 <Batch> (scan_method: Batch)       |
|  37 |                |           +- [Map] Local Distributed Union <Row>                         |
|  38 |                |              +- Filter Scan <Row> (seekable_key_size: 0)                 |
| *39 |                |                 +- Table Scan on Collaborations <Row> (scan_method: Row) |
|  55 |                +- [Map] RowToDataBlock                                                    |
|  56 |                   +- Cross Apply <Row>                                                    |
|  57 |                      +- [Input] KeyRangeAccumulator <Row>                                 |
|  58 |                      |  +- DataBlockToRow                                                 |
|  59 |                      |     +- Batch Scan on $v28 <Batch> (scan_method: Batch)             |
|  64 |                      +- [Map] Local Distributed Union <Row>                               |
|  65 |                         +- Filter Scan <Row> (seekable_key_size: 0)                       |
| *66 |                            +- Table Scan on Singers <Row> (scan_method: Row)              |
+-----+-------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  0: Split Range: ($SingerId'5 = 42)
  9: Seek Condition: ($SingerId'5 = 42)
 19: Split Range: ($SingerId_4'5 = $FeaturingSingerId'6)
 21: Split Range: ($SingerId_3'5 = $tail'_SingerId'16)
 39: Seek Condition: ($SingerId_3'5 = $batched_tail'_SingerId'17)
 66: Seek Condition: ($SingerId_4'5 = $batched_FeaturingSingerId'7)

N-ary operators

任意の数の Relational operator の子を持つ Relational operator 群。Union All 以外確認されていない。

Union All

UNION ALL を表現する operator で、任意の数の子の Union Input が返す行を合わせて返す。

kind type variable? multiple? description
RELATIONAL Yes UNION 対象を指す任意個数の Union Input operator
SCALAR Yes Yes Union All operator の結果の n 列目の名前を持ち、 input_{n} と対応付ける Scalar operator
Union All / Union Input の再現クエリと実行計画
SELECT 1 AS a, 2 AS b
UNION ALL SELECT 3 AS a, 4 AS b
UNION ALL SELECT 5 AS a, 6 AS b;
=== n-ary/union-all ===
SELECT 1 a, 2 b UNION ALL SELECT 3 a, 4 b UNION ALL SELECT 5 a, 6 b
+----+---------------------------------+
| ID | Operator                        |
+----+---------------------------------+
|  0 | Serialize Result <Row>          |
|  1 | +- Union All <Row>              |
|  2 |    +- Union Input               |
|  3 |    |  +- Compute <Row>          |
|  4 |    |     +- Unit Relation <Row> |
| 10 |    +- Union Input               |
| 11 |    |  +- Compute <Row>          |
| 12 |    |     +- Unit Relation <Row> |
| 18 |    +- Union Input               |
| 19 |       +- Compute <Row>          |
| 20 |          +- Unit Relation <Row> |
+----+---------------------------------+

Scalar operators

kindSCALAR の operator であり、ARRAY を含む値として評価されるサブクエリや式などを含む。 子に Relational operator を持つ Subquery のみは例外として一般的な実行計画ツリーでも表示されるが、その他の Scalar operator は QueryPlan の生データを読むことで観測できるものであり、一般的な実行計画ツリーの可視化手法では表示されない。この節の「その他の Scalar operators」は、主に raw PlanNode vocabulary の説明であり、同じ名前の行が実行計画ツリーに出ることを意味しない。

ツール製作者向け Note: Subquery であるかどうかは、さらに子を見なくても、親から Scalar link_type を使ってリンクされていることで判別できる。

Subqueries

サブクエリは1つの Relational operator を子に持ち、 ARRAY やスカラに変換する Scalar operator として処理される。Scalar subqueries で説明されているように最適化の結果 Cross Apply などで実現されることもある。

Array Subquery

子のサブクエリと式から配列を計算する。

kind type variable? multiple? description
RELATIONAL サブクエリとなるサブツリーで中で variable を定義する。
SCALAR サブクエリの中の variable を参照する式。サブクエリの各 row に対して配列の要素を計算するために使われる。
Array Subquery の再現クエリと実行計画
SELECT FirstName,
       ARRAY(
         SELECT AS STRUCT song.SongName, song.SongGenre
         FROM Songs AS song
         WHERE song.SingerId = singer.SingerId
       )
FROM Singers AS singer
WHERE singer.SingerId = 1;
=== array-subquery ===
SELECT a.AlbumId, ARRAY(SELECT ConcertDate FROM Concerts WHERE Concerts.SingerId = a.SingerId) FROM Albums AS a
+-----+-------------------------------------------------------------------------------------+
| ID  | Operator                                                                            |
+-----+-------------------------------------------------------------------------------------+
|   0 | Distributed Union on AlbumsByAlbumTitle <Row>                                       |
|   1 | +- Local Distributed Union <Row>                                                    |
|   2 |    +- Serialize Result <Row>                                                        |
|   3 |       +- Index Scan on AlbumsByAlbumTitle <Row> (Full scan, scan_method: Automatic) |
|   7 |       +- [Scalar] Array Subquery                                                    |
|  *8 |          +- Distributed Union on Concerts <Row>                                     |
|   9 |             +- Local Distributed Union <Row>                                        |
| *10 |                +- Filter Scan <Row> (seekable_key_size: 0)                          |
|  11 |                   +- Table Scan on Concerts <Row> (Full scan, scan_method: Row)     |
+-----+-------------------------------------------------------------------------------------+
Predicates(identified by ID):
  8: Split Range: ($SingerId_1 = $SingerId)
 10: Residual Condition: ($SingerId_1 = $SingerId)

Scalar Subquery

子のサブクエリと式からスカラ値を計算する。

kind type variable? multiple? description
RELATIONAL サブクエリとなるサブツリーで、中で variable を定義する。
SCALAR サブクエリの中の variable を参照する。
Scalar Subquery の再現クエリと実行計画
SELECT FirstName,
       IF(
         FirstName = 'Alice',
         (SELECT COUNT(*) FROM Songs WHERE Duration > 300),
         0
       )
FROM Singers;
=== scalar-subquery/conditional ===
SELECT FirstName, IF(FirstName = 'Alice', (SELECT COUNT(*) FROM Songs WHERE Duration > 300), 0) FROM Singers
+-----+------------------------------------------------------------------------------------------+
| ID  | Operator                                                                                 |
+-----+------------------------------------------------------------------------------------------+
|   0 | Distributed Union on SingersByFirstLastName <Row>                                        |
|   1 | +- Local Distributed Union <Row>                                                         |
|   2 |    +- Serialize Result <Row>                                                             |
|   3 |       +- Index Scan on SingersByFirstLastName <Row> (Full scan, scan_method: Automatic)  |
|  10 |       +- [Scalar] Scalar Subquery                                                        |
|  11 |          +- Global Stream Aggregate <Row> (scalar_aggregate: true)                       |
|  12 |             +- Distributed Union on Songs <Row>                                          |
|  13 |                +- Local Stream Aggregate <Row> (scalar_aggregate: true)                  |
|  14 |                   +- Local Distributed Union <Row>                                       |
| *15 |                      +- Filter Scan <Row> (seekable_key_size: 0)                         |
|  16 |                         +- Table Scan on Songs <Row> (Full scan, scan_method: Automatic) |
+-----+------------------------------------------------------------------------------------------+
Predicates(identified by ID):
 15: Residual Condition: ($Duration > 300)

その他の Scalar operators

実行計画の内部表現としてはグラフ構造をなしているが、 Subquery と違って一般的に tree 表示の構造的な child としては表示されず、ドキュメンテーションもされていない。

Array Constructor

(Undocumented) 配列リテラルに対応し、配列値を表現する。 shortRepresentation.description は配列のリテラル表記となる。

kind type variable? multiple? description
SCALAR Yes 配列の各値を表現する式。
Array Constructor の再現クエリと実行計画
SELECT a, b
FROM UNNEST([1, 2, 3]) a WITH OFFSET b;
=== leaf/array-unnest ===
SELECT a, b FROM UNNEST([1,2,3]) a WITH OFFSET b
+----+------------------------+
| ID | Operator               |
+----+------------------------+
|  0 | Serialize Result <Row> |
|  1 | +- Array Unnest <Row>  |
+----+------------------------+

Constant

(Undocumented) 定数を表す。shortRepresentation.description に値のリテラル表記や <typed null> などが文字列として入っている。

Constant の再現クエリと実行計画
SELECT 1 + 2 AS Result;
=== leaf/unit-relation-constant-function ===
SELECT 1 + 2 AS Result
+----+------------------------+
| ID | Operator               |
+----+------------------------+
|  0 | Serialize Result <Row> |
|  1 | +- Unit Relation <Row> |
+----+------------------------+

Field

STRUCT のフィールド参照を表す。

Metadata
key values description
name 参照する列名
kind type variable? multiple? description
SCALAR 対象の STRUCT を指す式
Field / Struct Constructor の再現クエリと実行計画
SELECT IF(TRUE, STRUCT(1 AS A, 1 AS B), STRUCT(2 AS A, 2 AS B)).A;
=== struct-constructor ===
SELECT IF(TRUE, STRUCT(1 AS A, 1 AS B), STRUCT(2 AS A, 2 AS B)).A
+----+------------------------+
| ID | Operator               |
+----+------------------------+
|  0 | Serialize Result <Row> |
|  1 | +- Unit Relation <Row> |
+----+------------------------+

Function

(Undocumented) 演算式と関数呼び出しを含む関数を表現する。shortRepresentation.description に演算子や関数名を含む式が文字列として入っている。 function hint の DISABLE_INLINE は relational plan shape に影響することがある。観測した例では default と @{DISABLE_INLINE=FALSE} は同じ operator shape になり、@{DISABLE_INLINE=TRUE} では Compute が追加されて SHA512($SingerInfo) を一度 materialize してから外側の式が参照する形になった。scalar-level の差分は tree 表示よりも nodes、JSON、YAML の出力で確認しやすい。

kind type variable? multiple? description
SCALAR Yes 各オペランド
Function の再現クエリと実行計画
SELECT 1 + 2 AS Result;
=== leaf/unit-relation-constant-function ===
SELECT 1 + 2 AS Result
+----+------------------------+
| ID | Operator               |
+----+------------------------+
|  0 | Serialize Result <Row> |
|  1 | +- Unit Relation <Row> |
+----+------------------------+
DISABLE_INLINE function hint の再現クエリと実行計画

Default:

SELECT
  SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
  SUBSTRING(CAST(x AS STRING), 3, 7) AS y
FROM (
  SELECT SHA512(s.SingerInfo) AS x
  FROM Singers AS s
);
=== function-hint/default_inline ===
SELECT
  SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
  SUBSTRING(CAST(x AS STRING), 3, 7) AS y
FROM (
  SELECT SHA512(s.SingerInfo) AS x
  FROM Singers AS s
)
+----+--------------------------------------------------------------------------+
| ID | Operator                                                                 |
+----+--------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row>                                       |
|  1 | +- Local Distributed Union <Row>                                         |
|  2 |    +- Serialize Result <Row>                                             |
|  3 |       +- Table Scan on Singers <Row> (Full scan, scan_method: Automatic) |
+----+--------------------------------------------------------------------------+

DISABLE_INLINE=FALSE:

SELECT
  SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
  SUBSTRING(CAST(x AS STRING), 3, 7) AS y
FROM (
  SELECT SHA512(s.SingerInfo) @{DISABLE_INLINE=FALSE} AS x
  FROM Singers AS s
);
=== function-hint/disable_inline_false ===
SELECT
  SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
  SUBSTRING(CAST(x AS STRING), 3, 7) AS y
FROM (
  SELECT SHA512(s.SingerInfo) @{DISABLE_INLINE=FALSE} AS x
  FROM Singers AS s
)
+----+--------------------------------------------------------------------------+
| ID | Operator                                                                 |
+----+--------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row>                                       |
|  1 | +- Local Distributed Union <Row>                                         |
|  2 |    +- Serialize Result <Row>                                             |
|  3 |       +- Table Scan on Singers <Row> (Full scan, scan_method: Automatic) |
+----+--------------------------------------------------------------------------+

DISABLE_INLINE=TRUE:

SELECT
  SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
  SUBSTRING(CAST(x AS STRING), 3, 7) AS y
FROM (
  SELECT SHA512(s.SingerInfo) @{DISABLE_INLINE=TRUE} AS x
  FROM Singers AS s
);
=== function-hint/disable_inline_true ===
SELECT
  SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
  SUBSTRING(CAST(x AS STRING), 3, 7) AS y
FROM (
  SELECT SHA512(s.SingerInfo) @{DISABLE_INLINE=TRUE} AS x
  FROM Singers AS s
)
+----+-----------------------------------------------------------------------------+
| ID | Operator                                                                    |
+----+-----------------------------------------------------------------------------+
|  0 | Distributed Union on Singers <Row>                                          |
|  1 | +- Local Distributed Union <Row>                                            |
|  2 |    +- Serialize Result <Row>                                                |
|  3 |       +- Compute <Row>                                                      |
|  4 |          +- Table Scan on Singers <Row> (Full scan, scan_method: Automatic) |
+----+-----------------------------------------------------------------------------+

Parameter

(Undocumented) クエリパラメータに対応する Scalar operator であり、実行時に Statement.params の name metadata と一致する名前の値として評価される。

通常はパラメータはスカラ値である前提でクエリが評価されるので、 Working with STRUCT objects にあるような配列や構造体型のパラメータを使うクエリは param_types に型を渡さないとエラーとなり、実行計画の取得はできない点に注意する必要がある。

Metadata
key values description
name パラメータ名
type array, scalar, … クエリパラメータが配列かスカラ値かを示す。 STRUCT 型の値も scalar となる。
Parameter の再現 SQL

以下は該当 operator を観測できる再現 SQL の例である。この形では @singer_id の型は Singers.SingerId との比較から INT64 として推論できるため、値や params を渡さずに PLAN できる。 Parameter は scalar operator であり、QueryPlan の生データを読むことで観測できるが、一般的な実行計画ツリーの可視化手法では単独行として表示されない。

SELECT s.LastName
FROM Singers AS s
WHERE s.SingerId = @singer_id;

Reference

(Undocumented) shortRepresentation.description に名前を持つ参照で metadata も子も持たない。 Sort 系の operator の Key で降順の場合は shortRepresentation.description$ItemId (DESC) のように (DESC) が含まれる。

Reference の再現クエリと実行計画
SELECT s.SongGenre
FROM Songs AS s
ORDER BY SongGenre;
=== unary/sort ===
SELECT s.SongGenre FROM Songs AS s ORDER BY SongGenre
+----+---------------------------------------------------------------------------+
| ID | Operator                                                                  |
+----+---------------------------------------------------------------------------+
|  0 | Distributed Union on Songs <Row> (preserve_subquery_order: true)          |
|  1 | +- Serialize Result <Row>                                                 |
|  2 |    +- Sort <Row>                                                          |
|  3 |       +- Local Distributed Union <Row>                                    |
|  4 |          +- Table Scan on Songs <Row> (Full scan, scan_method: Automatic) |
+----+---------------------------------------------------------------------------+

Struct Constructor

shortRepresentation.descriptionStruct Constructor {FirstName:DECODE_STRUCT_FIELD(0, $v1);LastName:DECODE_STRUCT_FIELD(1, $v1)} のように、フィールド名とフィールドの式を列挙する形式となる。 フィールド値の式は childLinks で参照できるがフィールド名は shortRepresentation.description にしか含まれない。

kind type variable? multiple? description
SCALAR Yes 各フィールド値
Struct Constructor の再現クエリと実行計画
SELECT IF(TRUE, STRUCT(1 AS A, 1 AS B), STRUCT(2 AS A, 2 AS B)).A;
=== struct-constructor ===
SELECT IF(TRUE, STRUCT(1 AS A, 1 AS B), STRUCT(2 AS A, 2 AS B)).A
+----+------------------------+
| ID | Operator               |
+----+------------------------+
|  0 | Serialize Result <Row> |
|  1 | +- Unit Relation <Row> |
+----+------------------------+

QueryPlan=PROFILE の構造

Web UI 上でのプロファイル情報

上記のような実行統計は QueryMode=PROFILE でクエリを実行した際に付与される情報の一部をレンダリングしている。上部に表示されるクエリ全体の実行統計は ResultSetStats.queryStats, 下部の各 operator ごとに表示されている統計は PlanNode.executionStats を元の情報としている。

QueryPlan=PROFILE 時のレスポンスの YAML 表現からの抜粋
stats:
  queryPlan:
    planNodes:
    # (中略)
      - childLinks:
          - childIndex: 6
            variable: SongName
        displayName: Scan
        executionStats:
          cpu_time:
            total: "0.7"
            unit: msecs
          deleted_rows:
            total: "0"
            unit: rows
          execution_summary:
            num_executions: "1"
          filesystem_delay_seconds:
            total: "4.04"
            unit: msecs
          filtered_rows:
            total: "0"
            unit: rows
          latency:
            total: "4.51"
            unit: msecs
          rows:
            total: "100"
            unit: rows
          scanned_rows:
            total: "100"
            unit: rows
        index: 5
        kind: RELATIONAL
        metadata:
          Full scan: "true"
          scan_target: SongsBySingerAlbumSongNameDesc
          scan_type: IndexScan
    # (中略)
  queryStats:
    bytes_returned: "1486"
    cpu_time: 2.23 msecs
    data_bytes_read: "55163"
    deleted_rows_scanned: "0"
    elapsed_time: 7.3 msecs
    filesystem_delay_seconds: 4.04 msecs
    optimizer_statistics_package: ""
    optimizer_version: "2"
    query_plan_creation_time: 1.3 msecs
    query_text: SELECT s.SongName FROM Songs AS s LIMIT 100
    remote_server_calls: 0/0
    rows_returned: "100"
    rows_scanned: "100"
    runtime_creation_time: 0 msecs
    statistics_load_time: 0

含まれるプロファイル情報はほぼ何もドキュメンテーションされていないが、活用可能なものが多い。

既知のクエリ全体の実行統計

key example description
bytes_returned 1486 最終的にクライアントに返る結果のバイト数
cpu_time 2.23 msecs 使用した CPU 時間の合計で Web UI 上の CPU time に対応
data_bytes_read 55163 読み出したバイト数
deleted_rows_scanned 0 スキャンされた削除済の行数(いわゆる tombstone によるもの?)
elapsed_time 7.3 msecs 総経過時間で Web UI 上の Total elapsed time に対応
filesystem_delay_seconds 4.04 msecs スキャン時に発生したファイルシステム由来の待ち時間
optimizer_statistics_package "" 未リリース機能に関するもの
optimizer_version 2 クエリに利用された optimizer version
query_plan_creation_time 1.3 msecs クエリプランの作成に掛かった時間。Life of query に書かれている処理を行う時間で、同一のものはキャッシュされるのでクエリパラメータの使用により軽減される。
query_text SELECT s.SongName FROM Songs AS s LIMIT 100 統計の対象のクエリ本文
remote_server_calls 0/0 distributed operator で他のサーバを呼んだ数。分子が num_executions の合計、分母が remote_calls.total の合計?(未確定)
rows_returned 100 クライアントに返る結果の行数で Web UI 上の Rows returned に対応
rows_scanned 100 各 Scan が読み出した行数で Web UI 上の Rows scanned に対応
runtime_creation_time 0 msecs 不明
statistics_load_time 0 不明

既知の PlanNode ごとの実行統計

各 PlanNode は executionStats に実行統計を持つ。

key operator(例) description
Disk Usage (KBytes) Sort Limit, Sort, … 一時保存するために使ったディスク容量
Disk Write Latency (msecs) Sort Limit, Sort, … 一時保存のためのディスクアクセスに使った時間
Peak Memory Usage (KBytes) Hash Join, Sort Limit, Sort, … 最大利用メモリ量
Rows Spooled Sort Limit, Sort, … 一時保存した行数
cpu_time 使用した CPU 時間
deleted_rows Scan Scan したが削除されていた行
filesystem_delay_seconds Scan ファイルシステム側で発生した遅れ
filtered_rows Scan スキャンしたが Residual Condition でフィルタされた行数
latency オペレータの開始から終了までのレイテンシで、 latency.mean が Web UI 上の Latency として表示され、ツールチップに latency.total, latency.std_deviation も含まれる。
remote_calls Distributed Cross Apply, Distributed Union, … リモートコールの回数で呼び出し元と呼び出し先が同じ際はカウントされていない様子
rows operator が最終的に返した行数で、 rows.total が Web UI 上の Rows returned として表示
scanned_rows Scan スキャンした行数
execution_summary.checkpoint_time チェックポイント作成に必要とした時間
execution_summary.execution_end_timestamp UNIX time による実行が終了したタイムスタンプ
execution_summary.execution_start_timestamp UNIX time による実行が開始されたタイムスタンプ
execution_summary.num_checkpoints チェックポイントを作成した回数
execution_summary.num_executions このオペレータの実行回数で、 Web UI 上の Executions として表示

executionStats の各値の構造

各 operator は複数回実行されるため、 executionStats に含まれる各 key に対応する値は統計値を持つ struct になっている。 例外として、execution_summary に含まれる統計値は全実行を通してのサマリとなっているため単に string の値を持つ。

key description
mean 平均値
std_deviation 標準偏差
total 合計値
unit 各値の単位
histogram ヒストグラムのバケットを含む配列
histogram[*].count バケットの範囲に入った実行の数
histogram[*].percentage 全実行内でのそのバケットに分類されるパーセント
histogram[*].lower_bound バケットの下限値
histogram[*].upper_bound バケットの上限値