Cloud Spanner Query Plan Visualizer using goccy/go-graphviz.
(Possibly) remote calls are rendered as dashed lines.
go install github.com/apstndb/spannerplanviz@latest
It can read various types in JSON and YAML.
- QueryPlan
- Can get easily by client libraries
- ResultSetStats
- Output of DOWNLOAD JSON in the official query plan visualizer
- ResultSet
- Output of
gcloud spanner databases execute-sql
and execspansql
- Output of
$ gcloud spanner databases execute-sql --instance=sampleinstance sampledb --query-mode=PLAN --format=yaml \
--sql="SELECT SongName FROM Songs WHERE STARTS_WITH(SongName, @prefix)" |
spannerplanviz --full --type=svg --output plan.svg
You see verbose profile information. (Currently, histograph
is not shown.)
$ gcloud spanner databases execute-sql --instance=sampleinstance sampledb --query-mode=PROFILE --format=yaml \
--sql "SELECT * FROM Singers JOIN Songs USING(SingerId) WHERE SongName LIKE 'Th%e'" |
spannerplanviz --full --type=svg --output profile.svg
You can emit Mermaid.js using --type mermaid
(EXPERIMENTAL).
spannerplanviz --full --type=mermaid --output profile.mermaid < dca_profile.json
%%{ init: {"theme": "neutral",
"themeVariables": { "wrap": "false" },
"flowchart": { "curve": "linear",
"markdownAutoWrap":"false",
"wrappingWidth": "2000" }
}
}%%
graph TD
node0["<b>Distributed Cross Apply</b>
Split Range\: \(\$SingerId\_1 \= \$SingerId\)
execution\_method: Row
<i>Number of Batches: 1 batches</i>
<i>cpu\_time: 376\.8 msecs</i>
<i>latency: 1\.08 secs</i>
<i>remote\_calls: 0 calls</i>
<i>rows: 3069 rows</i>
<i>execution\_summary\:
checkpoint\_time\: 0\.28 msecs
execution\_end\_timestamp\: 2025\-06\-06T20\:52\:18\.231573Z
execution\_start\_timestamp\: 2025\-06\-06T20\:52\:17\.148944Z
num\_checkpoints\: 19
num\_executions\: 1</i>"]
style node0 text-align:left;
node1["<b>Create Batch</b>
execution\_method: Row
\$v2\.Batch\:\=\$v1"]
style node1 text-align:left;
node2["<b>Compute Struct</b>
execution\_method: Row
\$v1\.BirthDate\:\=\$BirthDate
\$v1\.FirstName\:\=\$FirstName
\$v1\.LastName\:\=\$LastName
\$v1\.SingerId\:\=\$SingerId
\$v1\.SingerInfo\:\=\$SingerInfo
<i>cpu\_time: 31\.2 msecs</i>
<i>latency: 79\.04 msecs</i>
<i>rows: 1000 rows</i>
<i>execution\_summary\:
checkpoint\_time\: 0\.01 msecs
num\_checkpoints\: 1
num\_executions\: 1</i>"]
style node2 text-align:left;
node3["<b>Distributed Union</b>
Split Range\: true
distribution\_table: Singers
execution\_method: Row
split\_ranges\_aligned: false
<i>cpu\_time: 30\.2 msecs</i>
<i>latency: 78\.03 msecs</i>
<i>remote\_calls: 0 calls</i>
<i>rows: 1000 rows</i>
<i>execution\_summary\:
checkpoint\_time\: 0\.01 msecs
num\_checkpoints\: 1
num\_executions\: 1</i>"]
style node3 text-align:left;
node4["<b>Local Distributed Union</b>
execution\_method: Row
<i>cpu\_time: 29\.97 msecs</i>
<i>latency: 77\.8 msecs</i>
<i>remote\_calls: 0 calls</i>
<i>rows: 1000 rows</i>
<i>execution\_summary\:
checkpoint\_time\: 0\.01 msecs
execution\_end\_timestamp\: 2025\-06\-06T20\:52\:17\.228881Z
execution\_start\_timestamp\: 2025\-06\-06T20\:52\:17\.14899Z
num\_checkpoints\: 1
num\_executions\: 1</i>"]
style node4 text-align:left;
node5["<b>Table Scan</b>
Table\: Singers
Full scan: true
execution\_method: Row
scan\_method: Automatic
\$SingerId\:\=SingerId
\$FirstName\:\=FirstName
\$LastName\:\=LastName
\$SingerInfo\:\=SingerInfo
\$BirthDate\:\=BirthDate
<i>cpu\_time: 29\.84 msecs</i>
<i>deleted\_rows: 0\@0±0 rows</i>
<i>filesystem\_delay\_seconds: 48\.16\@24\.08±24\.08 msecs</i>
<i>filtered\_rows: 0\@0±0 rows</i>
<i>latency: 77\.66 msecs</i>
<i>rows: 1000 rows</i>
<i>scanned\_rows: 1000\@500±500 rows</i>
<i>execution\_summary\:
checkpoint\_time\: 0 msecs
num\_checkpoints\: 1
num\_executions\: 1</i>"]
style node5 text-align:left;
node18["<b>Serialize Result</b>
Result\.SingerId\:\$batched\_SingerId
Result\.FirstName\:\$batched\_FirstName
Result\.LastName\:\$batched\_LastName
Result\.SingerInfo\:\$batched\_SingerInfo
Result\.BirthDate\:\$batched\_BirthDate
Result\.AlbumId\:\$AlbumId
Result\.TrackId\:\$TrackId
Result\.SongName\:\$SongName
Result\.Duration\:\$Duration
Result\.SongGenre\:\$SongGenre
execution\_method: Row
<i>cpu\_time: 342\.95 msecs</i>
<i>latency: 998\.11 msecs</i>
<i>rows: 3069 rows</i>
<i>execution\_summary\:
checkpoint\_time\: 0\.18 msecs
execution\_end\_timestamp\: 2025\-06\-06T20\:52\:18\.231497Z
execution\_start\_timestamp\: 2025\-06\-06T20\:52\:17\.229908Z
num\_checkpoints\: 19
num\_executions\: 1</i>"]
style node18 text-align:left;
node19["<b>Cross Apply</b>
execution\_method: Row
<i>cpu\_time: 341\.43 msecs</i>
<i>latency: 996\.58 msecs</i>
<i>rows: 3069 rows</i>
<i>execution\_summary\:
checkpoint\_time\: 0\.17 msecs
num\_checkpoints\: 19
num\_executions\: 1</i>"]
style node19 text-align:left;
node20["<b>KeyRangeAccumulator</b>
execution\_method: Row
<i>cpu\_time: 0\.62 msecs</i>"]
style node20 text-align:left;
node21["<b>Batch Scan</b>
Batch\: \$v2
execution\_method: Row
scan\_method: Row
\$batched\_BirthDate\:\=BirthDate
\$batched\_FirstName\:\=FirstName
\$batched\_LastName\:\=LastName
\$batched\_SingerId\:\=SingerId
\$batched\_SingerInfo\:\=SingerInfo"]
style node21 text-align:left;
node27["<b>Local Distributed Union</b>
execution\_method: Row
<i>cpu\_time: 340\.03\@0\.34±0\.06 msecs</i>
<i>latency: 995\.19\@1±8\.12 msecs</i>
<i>remote\_calls: 0\@0±0 calls</i>
<i>rows: 3069\@3\.07±1\.72 rows</i>
<i>execution\_summary\:
checkpoint\_time\: 0\.16 msecs
num\_checkpoints\: 19
num\_executions\: 1000</i>"]
style node27 text-align:left;
node28["<b>Filter Scan</b>
Residual Condition\: \(\$SongName LIKE \'Th\%e\'\)
execution\_method: Row
seekable\_key\_size: 0"]
style node28 text-align:left;
node29["<b>Table Scan</b>
Table\: Songs
Seek Condition\: \(\$SingerId\_1 \= \$batched\_SingerId\)
execution\_method: Row
scan\_method: Row
\$SingerId\_1\:\=SingerId
\$AlbumId\:\=AlbumId
\$TrackId\:\=TrackId
\$SongName\:\=SongName
\$Duration\:\=Duration
\$SongGenre\:\=SongGenre
<i>cpu\_time: 339\.21\@0\.34±0\.06 msecs</i>
<i>deleted\_rows: 0 rows</i>
<i>filesystem\_delay\_seconds: 521\.29 msecs</i>
<i>filtered\_rows: 1020931 rows</i>
<i>latency: 994\.3\@0\.99±8\.12 msecs</i>
<i>rows: 3069\@3\.07±1\.72 rows</i>
<i>scanned\_rows: 1024000 rows</i>
<i>execution\_summary\:
checkpoint\_time\: 0\.05 msecs
num\_checkpoints\: 19
num\_executions\: 1000</i>"]
style node29 text-align:left;
node0 -->|Input| node1
node1 --> node2
node2 --> node3
node3 -.-> node4
node4 --> node5
node0 -.->|Map| node18
node18 --> node19
node19 -->|Input| node20
node20 --> node21
node19 -->|Map| node27
node27 --> node28
node28 --> node29
This tool is Alpha quality.