github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/docs/RFCS/20170608_query_cancellation.md (about) 1 - Feature Name: Query and session cancellation 2 - Status: completed 3 - Start Date: 2017-06-05 4 - Authors: Bilal Akhtar 5 - RFC PR: [#16417](https://github.com/cockroachdb/cockroach/pull/16417) 6 - Cockroach Issue: [#15593](https://github.com/cockroachdb/cockroach/issues/15593) 7 8 # Summary 9 10 This feature adds the ability to cancel an in-progress query, transaction, or session 11 using new `CANCEL` statements. Initially, this project starts off with a focus solely 12 on query cancellation, and adds a new query identifier and a new `CANCEL QUERY` statement 13 for this purpose. 14 15 # Motivation 16 17 Currently, a DBA can see what queries are running and for how long, using `SHOW QUERIES`: 18 19 ```sql 20 root@:26257/> SHOW CLUSTER QUERIES; 21 +---------+----------+----------------------------------+----------------------------------------------------------------------------------------------------------+-----------------+------------------+-------------+-----------+ 22 | node_id | username | start | query | client_address | application_name | distributed | phase | 23 +---------+----------+----------------------------------+----------------------------------------------------------------------------------------------------------+-----------------+------------------+-------------+-----------+ 24 | 1 | root | 2017-06-07 16:14:37.38177+00:00 | SHOW CLUSTER QUERIES | [::1]:54421 | | NULL | preparing | 25 | 2 | root | 2017-06-07 16:14:37.380942+00:00 | UPDATE accounts SET balance = CASE id WHEN $1 THEN $3::INT WHEN $2 THEN $4::INT END WHERE id IN ($1, $2) | 127.0.0.1:54887 | | false | executing | 26 | 2 | root | 2017-06-07 16:14:37.382055+00:00 | UPDATE accounts SET balance = CASE id WHEN $1 THEN $3::INT WHEN $2 THEN $4::INT END WHERE id IN ($1, $2) | 127.0.0.1:54936 | | false | executing | 27 | 2 | root | 2017-06-07 16:14:37.380492+00:00 | UPDATE accounts SET balance = CASE id WHEN $1 THEN $3::INT WHEN $2 THEN $4::INT END WHERE id IN ($1, $2) | 127.0.0.1:54920 | | false | executing | 28 | 2 | root | 2017-06-07 16:14:37.381263+00:00 | UPDATE accounts SET balance = CASE id WHEN $1 THEN $3::INT WHEN $2 THEN $4::INT END WHERE id IN ($1, $2) | 127.0.0.1:54912 | | false | executing | 29 | 2 | root | 2017-06-07 16:14:37.3805+00:00 | UPDATE accounts SET balance = CASE id WHEN $1 THEN $3::INT WHEN $2 THEN $4::INT END WHERE id IN ($1, $2) | 127.0.0.1:54928 | | false | executing | 30 +---------+----------+----------------------------------+----------------------------------------------------------------------------------------------------------+-----------------+------------------+-------------+-----------+ 31 (6 rows) 32 ``` 33 34 Similarly, `SHOW SESSIONS` lists active sessions along with start timestamps: 35 36 ```sql 37 root@:26257/> SHOW SESSIONS; 38 +---------+----------+----------------+------------------+-------------------------+----------------------------------+----------------------------------+--------+ 39 | node_id | username | client_address | application_name | active_queries | session_start | oldest_query_start | kv_txn | 40 +---------+----------+----------------+------------------+-------------------------+----------------------------------+----------------------------------+--------+ 41 | 1 | root | [::1]:57557 | | SHOW CLUSTER SESSIONS; | 2017-06-14 19:09:50.609823+00:00 | 2017-06-14 19:09:54.480242+00:00 | NULL | 42 +---------+----------+----------------+------------------+-------------------------+----------------------------------+----------------------------------+--------+ 43 (1 row) 44 ``` 45 46 Extending that functionality with cancellation would be a logical next step. 47 48 Query, transaction and session cancellation all have different motivations and use-cases: 49 50 - Query cancellation is particularly useful for long-running queries hogging up cluster resources. 51 Upon cancellation, a query would immediately return an error and leave the transaction in a state 52 where it can be rolled back. 53 - Transaction cancellation would involve cancelling any active queries under that transaction, 54 in addition to rolling it back and cleaning up intents and leases held by that transaction. Cancelling 55 idle transactions would also be useful for this reason. 56 - Session cancellation would involve cancelling any active transactions, followed by termination 57 of the connection with the client. Would be useful for revoking unauthorized access to db. 58 59 Due to the interdependent nature of these different levels of cancellation, and the significant 60 technical overlap between each feature, query cancellation will be the first feature to be implemented. 61 Query cancellation by itself solves the fewest number of use-cases compared to transaction 62 or session cancellation, but once it has been implemented well, its mechanism can be easily 63 levereged for transaction and session cancellation. 64 65 This RFC currently only describes query cancellation in detail; it will be expanded with details 66 about transaction and session cancellation once query cancellation has been implemented. 67 68 # Detailed design 69 70 ## Query identifiers 71 72 Query identifiers are intended to provide a unique way to identify a query across 73 the cluster, preferably with an easy way to recover the node ID from the query ID without issuing 74 requests to other nodes. They will be used primarily in: 75 76 - `SHOW QUERIES`, presented under the `id` column. 77 - `CANCEL QUERY <query-id>` statements as the only argument. 78 79 Query identifiers could also be added to other parts of the code, such as to log tags, however 80 those changes will be out of scope for this project. 81 82 Query identifiers will be stored as 128-bit unsigned integers (two `uint64`s) - composed of the 83 96-bit HLC timestamp plus 32 bits of node ID. [There already is a uint128 type in the code 84 base](https://github.com/cockroachdb/cockroach/tree/master/pkg/util/uint128) that can be levereged. 85 Using the full HLC timestamp along with the node ID makes this ID unique across the cluster. 86 87 Query IDs will be presented in the output of `SHOW QUERIES` as 32-character hex strings. 88 89 Example output of `SHOW QUERIES` with query ID (see the `id` column): 90 91 ```sql 92 root@:26257/> SELECT node_id, id, query FROM [SHOW CLUSTER QUERIES]; 93 +---------+----------------------------------+--------------------------------------------------------------------+ 94 | node_id | id | query | 95 +---------+----------------------------------+--------------------------------------------------------------------+ 96 | 1 | 2834b4f8a230ce2394e4f12a8c9236c1 | SHOW CLUSTER QUERIES | 97 | 2 | 827f91ab89c3e72d10154fbec8293de1 | INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --') | 98 +---------+----------------------------------+--------------------------------------------------------------------+ 99 (2 rows) 100 ``` 101 102 ## Context forking / query cancellation mechanism 103 104 We will reuse the transaction's context at the query level; all query cancellations will close the entire transaction's 105 context. Forking a query-specific context would be technically challenging due to [context scoping assumptions made in 106 the TxnCoordSender](https://github.com/cockroachdb/cockroach/blob/8ff5ff97df139fa5958e15a2fd5ffa65e09b49ff/pkg/kv/txn_coord_sender.go#L619). The 107 `queryMeta` will store a reference to the txn context. PlanNodes that do in-memory 108 processing such as insertNode's insert batching, and sortNode's sorting, will periodically check 109 that context for cancellation (such as once every some tens of thousands of rows). 110 111 Any `CANCEL QUERY` statements directed at that query will close the txn context's `Done` channel, which would 112 error out any RPCs being made for that query. Any planNodes doing processing on the gateway node will check 113 for that context closure and short-circuit execution at that point, returning an error. The error would 114 propagate to the SQL executor which would then mark the SQL transaction as aborted. The client would 115 be expected to issue a `ROLLBACK` upon seeing the errored-out query. The client's connection will _not_ be closed. 116 117 The executor would re-throw a more user-friendly "Execution canceled upon user request" error instead of 118 the "Context canceled" one. 119 120 ## New CANCEL QUERY statement 121 122 The syntax of the actual `CANCEL` statement has already been approved in 123 [this RFC](https://github.com/cockroachdb/cockroach/pull/16273), and will be of the form 124 `CANCEL [QUERY|JOB] <query_id>`. 125 126 A non-root database user can only issue `CANCEL QUERY` statements to queries run by that same 127 user. The root user can cancel any queries on the cluster. 128 129 When this statement is executed, the query ID will be parsed to determine the node ID, and an RPC call 130 will be made to that node if it is not the current node. That node would look up and cancel the 131 context related to that query. 132 133 The `CANCEL QUERY` would return an error if no query was found for that ID. However, if the 134 `CANCEL QUERY` statement succeeds, there is no guarantee that the query in question was actually 135 canceled - and did not commit any changes (if applicable). 136 137 Example use case: Assume there are two concurrently running sessions, session 1 and 2. 138 139 Session 1 (long running query): 140 141 ```sql 142 root@:26257/> SELECT * FROM students WHERE long_bio LIKE '%ips%or'; 143 ``` 144 145 Session 2 (DB admin wishing to cancel the query above): 146 147 ```sql 148 root@:26257/> SELECT node_id, id, query FROM [SHOW CLUSTER QUERIES]; 149 +---------+----------------------------------+-----------------------------------------------------------+ 150 | node_id | id | query | 151 +---------+----------------------------------+-----------------------------------------------------------+ 152 | 1 | 2834b4f8a230ce2394e4f12a8c9236c1 | SHOW CLUSTER QUERIES | 153 | 2 | 827f91ab89c3e72d10154fbec8293de1 | SELECT * FROM students WHERE long_bio LIKE '%ips%or'; | 154 +---------+----------------------------------+-----------------------------------------------------------+ 155 (2 rows) 156 157 root@:26257/> CANCEL QUERY '827f91ab89c3e72d10154fbec8293de1'; 158 CANCEL QUERY 159 160 root@:26257/> SELECT node_id, id, query FROM [SHOW CLUSTER QUERIES]; 161 +---------+----------------------------------+-----------------------------------------------------------+ 162 | node_id | id | query | 163 +---------+----------------------------------+-----------------------------------------------------------+ 164 | 1 | 2834b4f8a230ce2394e4f12a8c9236c1 | SHOW CLUSTER QUERIES | 165 +---------+----------------------------------+-----------------------------------------------------------+ 166 (1 row) 167 ``` 168 169 Going back to session 1: 170 171 ```sql 172 root@:26257/> SELECT * FROM students WHERE long_bio LIKE '%ips%or'; 173 pq: query canceled by user 'root' 174 175 root%:26257/> 176 ``` 177 178 ## DistSQL cancellation 179 180 The DistSQL flow on the gateway node has a context that's directly derived from that of the transaction. 181 Since the gateway is the final consumer node in the flow graph, we need to propagate this context 182 cancellation to producer nodes. Every flow (the portion of the data flow graph on a specific node) will 183 have its own context, and the `FlowStream` calls running on a consumer node will 184 check for context cancellation on its node and return an error to the outbox on its producer node. 185 This outbox would then cancel the flow context on its node, and this cancellation could then be 186 picked up by any `FlowStream` calls running on that node, further propagating the error to other 187 upstream producer nodes. 188 189 Each outbox would be marked as closed as it picks up the context cancellation error from its consumer. 190 Any processor pushing rows to that outbox will get a `ConsumerClosed` consumer signal and error out. However, 191 some processors (eg. hash joiner, sorter) do a lot of processing before they emit any rows, so these 192 processors will manually check for context cancellation after every 1000 or so iterations, and error out 193 if it is canceled. 194 195 The final row receiver, a `DistSQLReceiver` which is the `syncFlowConsumer` on the gateway 196 node's flow, does not have any `FlowStream` calls associated with it. So to ensure processors 197 on the gateway node get a `ConsumerClosed` when they push rows, the `syncFlowConsumer` will be closed 198 by manually pushing an error to it in the cancellation code. 199 200 In short, cancellation will flow in these directions: 201 202 - Consumer node's inbound stream to producer node's outbox: When `FlowStream` returns an error to the 203 producer's outbox, causing it to close. 204 - Consumer to producer processor: When producer processor tries to push a row to its consumer and gets 205 a ConsumerClosed status, it will stop processing and mark itself as closed to its producers. 206 - Producer node to remote consumer node: Once a flow's context is canceled, all RPCs from that node 207 to any other nodes are also canceled (by grpc). 208 - Producer processor to consumer: This is a special case only for those processors that 209 do a lot of processing before emitting any rows (such as the sorter). These processors will check the 210 local flow context for cancellation, and return an error to their consumer if it gets canceled. 211 - syncFlowConsumer special case: Since `syncFlowConsumer` on the gateway node does not have any streams that 212 cross node boundaries or call `FlowStream`, an error will be manually pushed to it 213 upon a cancellation request on the gateway node, marking it as closed to all of its producers. 214 215 # Drawbacks 216 217 # Alternatives 218 219 ## Query ID alternatives 220 221 One alternative that was considered for query IDs was an in-memory counter combined with the 222 node ID in one string. This would have led to issues with duplicate query IDs if the counter 223 resets to 0 upon node restart, so it wasn't chosen. `unique_rowid()`s were considered but 224 were also rejected due to a lack of a strong uniqueness guarantee with it. 225 226 Not combining the node ID into the query ID was once suggested, but that would have required 227 the user to supply both the node and query IDs to the `CANCEL` statement. Since the query ID 228 would make little sense out of the context of a node ID, and both IDs will be presented together 229 in `SHOW QUERIES`, it makes more sense from a UI perspective to combine them. 230 231 ## Supporting postgres' cancellation syntax 232 233 Postgres has a built-in function that has cancellation as a side-effect: 234 235 ``` 236 SELECT pg_cancel_backend(<query-id>); 237 ``` 238 239 Supporting this syntax was considered for compatibility reasons but decided against - since 240 functions should ideally not have side-effects, and also because postgres' query IDs are `int32`s 241 which would be hard to reconcile with our larger query IDs. 242 243 # Unresolved questions 244