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