Skip to content

Commit d3e894c

Browse files
craig[bot]yuzefovich
andcommitted
Merge #155655
155655: sql: don't run EXPLAIN ANALYZE via pausable portals r=yuzefovich a=yuzefovich This commit disables running EXPLAIN ANALYZE stmts via pausable portals model even if it's enabled. Previously, this led to a crash since different assumptions of how EXPLAIN ANALYZE is handled at the connExecutor level were broken, but it also doesn't make much sense to actually support this. After all, the result rows can only be produced _after_ the query execution effectively completed, which contradicts the spirit of the pausable portal feature. I briefly looked into making it work, but it doesn't seem worth it. There is a fundamental contradiction right now that we defer finishing the instrumentationHelper (which populates the output of EXPLAIN ANALYZE), and it currently happens after we close the commandResult in which we can write the output. Fixes: #137597. Release note (bug fix): Previously, CockroachDB would crash when executing EXPLAIN ANALYZE statements with the pausable portal model (meaning that it was run via the extended PGWire protocol using Parse, Bind, Execute, when `multiple_active_portals_enabled` session variable is enabled). The bug has been present since 23.2 and is now fixed. Co-authored-by: Yahor Yuzefovich <[email protected]>
2 parents baf2f0b + a4d8381 commit d3e894c

File tree

3 files changed

+121
-8
lines changed

3 files changed

+121
-8
lines changed

pkg/sql/conn_executor_exec.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1476,7 +1476,13 @@ func (ex *connExecutor) execStmtInOpenStateWithPausablePortal(
14761476
if portal.pauseInfo.execStmtInOpenState.ihWrapper == nil {
14771477
portal.pauseInfo.execStmtInOpenState.ihWrapper = &instrumentationHelperWrapper{
14781478
ctx: ctx,
1479-
ih: *ih,
1479+
// TODO(yuzefovich): we're capturing the instrumentationHelper
1480+
// by value here, meaning that modifications that happen later
1481+
// (notably in makeExecPlan) aren't captured. For example,
1482+
// explainPlan field will remain unset. However, so far we've
1483+
// only observed this impact EXPLAIN ANALYZE which doesn't run
1484+
// through the pausable portal path.
1485+
ih: *ih,
14801486
}
14811487
} else {
14821488
p.instrumentation = portal.pauseInfo.execStmtInOpenState.ihWrapper.ih

pkg/sql/pgwire/testdata/pgtest/multiple_active_portals

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,3 +1629,100 @@ ReadyForQuery
16291629
{"Type":"ReadyForQuery","TxStatus":"I"}
16301630

16311631
subtest end
1632+
1633+
subtest explain_analyze
1634+
1635+
# Regression test that EXPLAIN ANALYZE doesn't run through the pausable portal
1636+
# path even when the feature is enabled (#137597).
1637+
1638+
send crdb_only
1639+
Query {"String": "SET multiple_active_portals_enabled = 'true';"}
1640+
----
1641+
1642+
until crdb_only
1643+
ReadyForQuery
1644+
----
1645+
{"Type":"CommandComplete","CommandTag":"SET"}
1646+
{"Type":"ReadyForQuery","TxStatus":"I"}
1647+
1648+
send
1649+
Query {"String": "BEGIN"}
1650+
Parse {"Name": "qea1", "Query": "EXPLAIN ANALYZE SELECT 1;"}
1651+
Bind {"DestinationPortal": "pea1", "PreparedStatement": "qea1"}
1652+
Execute {"Portal": "pea1", "MaxRows": 1}
1653+
Sync
1654+
----
1655+
1656+
until crdb_only ignore=DataRow
1657+
ReadyForQuery
1658+
ReadyForQuery
1659+
----
1660+
{"Type":"CommandComplete","CommandTag":"BEGIN"}
1661+
{"Type":"ReadyForQuery","TxStatus":"T"}
1662+
{"Type":"ParseComplete"}
1663+
{"Type":"BindComplete"}
1664+
{"Type":"PortalSuspended"}
1665+
{"Type":"ReadyForQuery","TxStatus":"T"}
1666+
1667+
send
1668+
Execute {"Portal": "pea1"}
1669+
Sync
1670+
----
1671+
1672+
until crdb_only ignore=DataRow
1673+
ReadyForQuery
1674+
----
1675+
{"Type":"CommandComplete","CommandTag":"EXPLAIN"}
1676+
{"Type":"ReadyForQuery","TxStatus":"T"}
1677+
1678+
send
1679+
Query {"String": "COMMIT"}
1680+
----
1681+
1682+
until
1683+
ReadyForQuery
1684+
----
1685+
{"Type":"CommandComplete","CommandTag":"COMMIT"}
1686+
{"Type":"ReadyForQuery","TxStatus":"I"}
1687+
1688+
send
1689+
Query {"String": "BEGIN"}
1690+
Parse {"Name": "qea2", "Query": "EXPLAIN ANALYZE (DEBUG) SELECT 1;"}
1691+
Bind {"DestinationPortal": "pea2", "PreparedStatement": "qea2"}
1692+
Execute {"Portal": "pea2", "MaxRows": 1}
1693+
Sync
1694+
----
1695+
1696+
until crdb_only ignore=DataRow
1697+
ReadyForQuery
1698+
ReadyForQuery
1699+
----
1700+
{"Type":"CommandComplete","CommandTag":"BEGIN"}
1701+
{"Type":"ReadyForQuery","TxStatus":"T"}
1702+
{"Type":"ParseComplete"}
1703+
{"Type":"BindComplete"}
1704+
{"Type":"PortalSuspended"}
1705+
{"Type":"ReadyForQuery","TxStatus":"T"}
1706+
1707+
send
1708+
Execute {"Portal": "pea2"}
1709+
Sync
1710+
----
1711+
1712+
until crdb_only ignore=DataRow
1713+
ReadyForQuery
1714+
----
1715+
{"Type":"CommandComplete","CommandTag":"EXPLAIN"}
1716+
{"Type":"ReadyForQuery","TxStatus":"T"}
1717+
1718+
send
1719+
Query {"String": "COMMIT"}
1720+
----
1721+
1722+
until
1723+
ReadyForQuery
1724+
----
1725+
{"Type":"CommandComplete","CommandTag":"COMMIT"}
1726+
{"Type":"ReadyForQuery","TxStatus":"I"}
1727+
1728+
subtest end

pkg/sql/prepared_stmt.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,23 @@ func (ex *connExecutor) makePreparedPortal(
120120
OutFormats: outFormats,
121121
}
122122

123-
if ex.sessionData().MultipleActivePortalsEnabled && ex.executorType != executorTypeInternal {
124-
telemetry.Inc(sqltelemetry.StmtsTriedWithPausablePortals)
125-
// We will check whether the statement itself is pausable (i.e., that it
126-
// doesn't contain DDL or mutations) when we build the plan.
127-
portal.pauseInfo = &portalPauseInfo{}
128-
portal.pauseInfo.dispatchToExecutionEngine.queryStats = &topLevelQueryStats{}
129-
portal.portalPausablity = PausablePortal
123+
if ex.sessionData().MultipleActivePortalsEnabled {
124+
// Do not even try running EXPLAIN ANALYZE statements via the pausable
125+
// portal path since it doesn't make much sense to do so - the result
126+
// rows can only be produced _after_ the query execution completes, so
127+
// there are no actual pauses during the execution (plus the
128+
// implementation of EXPLAIN ANALYZE in the connExecutor is quite
129+
// special, and it seems hard to make it work with pausable portals
130+
// model).
131+
_, isExplainAnalyze := stmt.AST.(*tree.ExplainAnalyze)
132+
if ex.executorType != executorTypeInternal && !isExplainAnalyze {
133+
telemetry.Inc(sqltelemetry.StmtsTriedWithPausablePortals)
134+
// We will check whether the statement itself is pausable (i.e., that it
135+
// doesn't contain DDL or mutations) when we build the plan.
136+
portal.pauseInfo = &portalPauseInfo{}
137+
portal.pauseInfo.dispatchToExecutionEngine.queryStats = &topLevelQueryStats{}
138+
portal.portalPausablity = PausablePortal
139+
}
130140
}
131141
return portal, portal.accountForCopy(ctx, &ex.extraTxnState.prepStmtsNamespaceMemAcc, name)
132142
}

0 commit comments

Comments
 (0)