github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/docs/RFCS/20170124_sql_parallelization.md (about)

     1  - Feature Name: Parallel SQL Statement Execution
     2  - Status: completed
     3  - Start Date: 2017-01-18
     4  - Authors: Nathan VanBenschoten
     5  - RFC PR: [#13160](https://github.com/cockroachdb/cockroach/issues/13160)
     6  - Cockroach Issue: [#10057](https://github.com/cockroachdb/cockroach/issues/10057),
     7                     [#2210](https://github.com/cockroachdb/cockroach/issues/2210),
     8                     [#4403](https://github.com/cockroachdb/cockroach/issues/4403),
     9                     [#5259](https://github.com/cockroachdb/cockroach/issues/5259)
    10  
    11  # Summary
    12  
    13  This RFC proposes a method to support the batching of multiple SQL statements
    14  for parallel execution within a single transaction. If two or more adjacent SQL
    15  statements are provably independent, executing their KV operations in parallel
    16  can yield up to a linear speedup. This will serve as a critical optimization in
    17  cases where replicas have high link latencies and round-trip communication costs
    18  dominates system performance.
    19  
    20  
    21  # Motivation
    22  
    23  In positioning CockroachDB as a globally-distributed database, high
    24  communication latency between nodes in Cockroach clusters is likely to be a
    25  reality for many real-world deployments. This use case is, then, one that we
    26  should make sure to handle well. In this RFC, we focus on an individual aspect
    27  of this larger goal: reducing aggregate latency resulting from high
    28  communication cost for SQL transactions with multiple independent statements.
    29  
    30  A SQL transaction is generally composed of a series of SQL statements. In some
    31  cases, these statements depend on one another and rely on strictly ordered
    32  serial execution for correctness. However, in many other cases, the statements
    33  are either partially or fully independent of one another. This independence
    34  between statements provides an opportunity to exploit [intrinsic inter-statement
    35  parallelism](#independent-statements), where the statements can safely be
    36  executed concurrently. In a compute-bound system, this would provide little
    37  benefit. However, in a system where communication cost dominates performance,
    38  this parallel execution of SQL statements over the network could amortize
    39  round-trip latency between nodes and provide up to a linear speedup with respect
    40  to the number of statements in a fully-independent transaction.
    41  
    42  
    43  # Detailed design
    44  
    45  ## SQL Statement Dependencies
    46  
    47  ### Independent Statements
    48  
    49  We say that two SQL statements are _"independent"_ if their execution could be
    50  safely reordered without having an effect on their execution semantics or on
    51  their results. We can extend this definition to sets of ordered SQL statements,
    52  where internally the set may rely on the serial execution of statements, but
    53  externally the entire group may be reordered with another set of statements.
    54  
    55  To reason about statement independence, we first say that all statements have a
    56  (possibly empty) set of data that they read from and a (possibly empty) set of
    57  data that they write to. We call these the statement's **read-set (R)** and
    58  **write-set (W)**. We can then say that two statements s<sub>1</sub> and
    59  s<sub>2</sub> are independent, and therefore can be safely reordered, if the
    60  following three rules are met:
    61  
    62  1. W<sub>1</sub> ∩ W<sub>2</sub> = Ø
    63  2. R<sub>1</sub> ∩ W<sub>2</sub> = Ø
    64  3. R<sub>2</sub> ∩ W<sub>1</sub> = Ø
    65  
    66  In other words, two statements are independent if their write-sets do not
    67  overlap, and if each statement's read-set is disjoint from the other's
    68  write-set. The first consequence of these rules is straightforward and
    69  intuitive; two independent statements cannot modify the same data. The second
    70  consequence requires a bit more justification. If one statement modifies some
    71  piece of data, a second statement cannot safely base any decisions off of the
    72  same data, even if it does not plan to change the data itself. This means that
    73  sequential reads and writes to the same piece of data must always execute in
    74  order. Interestingly, these rules for statement independence are somewhat
    75  analogous to the rules we use for constructing the dependency graph in our
    76  `CommandQueue`.
    77  
    78  Again, we can extend this reasoning to groups of SQL statements. We also say
    79  that a group of statements has a **read-set (R)** and **write-set (W)** which
    80  are respectively the unions of the read-set and the write-set of all statements
    81  in the group. When determining if a group of statements is independent of
    82  another group of statements, we can use the same rules as above using each
    83  entity's read and write-sets.
    84  
    85  It is easy to reason that simple SELECT statements and other SQL statements that
    86  are strictly read-only are always independent of one another, regardless of any
    87  overlap in the data they touch. These statements perform no externally-visible
    88  state mutation, and will not interfere with one another (from a correctness
    89  standpoint at least, see
    90  [below](#disrupting-data-locality-and-other-unintended-performance-consequences)
    91  for a discussion on indirect statement interaction). Using our previous
    92  notation, each statement will have an empty write-set (W<sub>1</sub> =
    93  W<sub>2</sub> = Ø), so both W ∩ R intersections in the statement independence
    94  rules will produce the empty set.
    95  
    96  Perhaps more interestingly, we can also reason that two statements that perform
    97  mutations (INSERT, UPDATE, DELETE, etc.) but that touch disjoint sets of data
    98  are also independent of one another. It is important to note here that the set
    99  of data that a statement _touches_ includes any that the statement mutates and
   100  any that the statement reads while performing validation and filtering.
   101  
   102  Taking this a step further, we can use our statement independence rules to find
   103  that two mutating statements can be independent even if their touched data sets
   104  overlap, as long as only their read-sets overlap. A practical example of this
   105  result is a pair of statements that insert rows into two different tables, but
   106  each validates a foreign key in the same third table. These two statements are
   107  independent because only their read-sets overlap.
   108  
   109  Finally, we can reason that if there is overlap between the touched sets of data
   110  between any two statements where at least one of the two performs mutation in
   111  the overlapping region, then the statements are not independent.
   112  
   113  #### Read and Write-Set Entries
   114  
   115  Up until this point, we have been intentionally vague about the "data" stored in
   116  the read and write-sets of statements. The reason for this is that the
   117  definition of "independence" assumes a more general notion of data than just
   118  that stored in the rows of a SQL table. Instead, this data can include any
   119  physical or log­i­cal information that makes up the external-facing state of the
   120  database. Using this definition, all of the following can fall into the read or
   121  write-sets of statements:
   122  
   123  - Cells/rows in a table
   124  - Table indexes
   125  - Table constraint state
   126  - Table schemas
   127  - Table and database privileges
   128  - Inter-statement constraint state
   129  
   130  Defining these as different data values that can all be part of statements read
   131  and write-sets creates some interesting results. For instance, it creates a
   132  dependence between any statement that implicitly behaves based on some
   133  meta-level data and any statement that explicitly modifies that meta-level data.
   134  An example of this is a query statement to a table and a subsequent schema
   135  change statement on that table. In this example, the initial query's execution
   136  semantics are dictated by the table schema, so we add this data to the query's
   137  read set. Later, the other statement modifies this schema, so we add this data
   138  to the query's write set. Because the read-set of the first query intersects
   139  with the write-set of the second query, the two statements must be dependent. It
   140  becomes clear that even simple queries have a number of implicit data accesses,
   141  which need to be accounted for accurately in order to safely determine
   142  statements independence.
   143  
   144  #### Examples
   145  
   146  ```
   147  SELECT * FROM a
   148  SELECT * FROM b
   149  SELECT * FROM a
   150  ```
   151  All three statements are **independent** because they have empty write-sets.
   152  
   153  ```
   154  INSERT INTO a VALUES (1)
   155  INSERT INTO b VALUES (1)
   156  ```
   157  Statements are **independent** because they have disjoint write-sets and empty
   158  read-sets.
   159  
   160  ```
   161  UPDATE a SET x = 1 WHERE y = false
   162  UPDATE a SET x = 2 WHERE y = true
   163  ```
   164  Statements are **independent** because they have disjoint write-sets and their
   165  read-sets do not overlap with the other's write-sets. (note that parallelizing
   166  this example will not actually be supported by the initial proposal, see
   167  [Dependency Granularity](#dependency-granularity))
   168  
   169  ```
   170  UPDATE a SET b = 2 WHERE y = 1
   171  UPDATE a SET b = 3 WHERE y = 1
   172  ```
   173  Statements are **dependent** because write-sets overlap.
   174  
   175  ```
   176  UPDATE a SET y = true  WHERE y = false
   177  UPDATE a SET y = false WHERE y = true
   178  ```
   179  Statements are **dependent** because while write-sets are disjoint, the
   180  read-sets for each statement overlap with the other statement's write-set.
   181  
   182  ```
   183  UPDATE a SET x = 1    WHERE y = false
   184  UPDATE a SET y = true WHERE x = 1
   185  ```
   186  Statements are **dependent** because while write-sets are disjoint, the
   187  read-sets for each statement overlap with the other statement's write-set.
   188  
   189  ```
   190  INSERT INTO a VALUES (1)
   191  INSERT INTO a VALUES (2)
   192  ```
   193  Statements are **independent** because they write to different sets of data.
   194  
   195  ```
   196  SELECT * FROM a
   197  REVOKE SELECT ON a
   198  SELECT * FROM a
   199  ```
   200  All three statements are **dependent** because permission changes perform a
   201  write on the permissions for table *a* that the two queries implicitly read.
   202  
   203  ```
   204  SELECT * FROM a
   205  ALTER TABLE a DROP COLUMN x
   206  SELECT * FROM a
   207  ```
   208  All three statements are **dependent** because schema changes perform a write on
   209  the schema for table *a* that the two queries implicitly read.
   210  
   211  ```
   212  SELECT statement_timestamp()
   213  SELECT statement_timestamp()
   214  ```
   215  Statements are **dependent** because a call to `statement_timestamp` implicitly
   216  mutates state scoped to the current transaction. This falls under the category
   217  of "inter-statement constraint state".
   218  
   219  ### Dependency Detection
   220  
   221  #### Dependency Granularity
   222  
   223  These definitions hold for any granularity of data in a SQL database. For
   224  instance, the definitions provided above could apply to data at the
   225  _cell-level_, the _row-level_, the _table-level_, or even the _database-level_,
   226  as long as we are careful to track all reads and writes, explicit or otherwise.
   227  By increasing the granularity at which we track these statement dependencies, we
   228  can increase the number of statements that can be considered independent and
   229  thus run concurrently. However, increasing the granularity of dependency
   230  tracking also comes with added complexity and added risk of error.
   231  
   232  However, instead of working in the domain of SQL constructs, this RFC proposes
   233  to track dependencies between statements at the `roachpb.Key` level. The reason
   234  for this is that our SQL layer already maps SQL logic into the `roachpb.Key`
   235  domain, which is totally-ordered and is more straightforward to work with. In
   236  addition, `roachpb.Keys` can also be grouped into `roachpb.Span`, which already
   237  have set semantics defined for them. So, by first leveraging existing
   238  infrastructure to map SQL into reads and writes within `roachpb.Spans`, we can
   239  perform dependency analysis on SQL statements by determining if sets of
   240  `roachpb.Spans` overlap.
   241  
   242  #### Algorithm
   243  
   244  The algorithm to manage statement execution so that independent statements can
   245  be run in parallel is fairly straightforward. During statement evaluation, a
   246  list of asynchronously executable statements is maintained, comprised of both
   247  currently executing statements and pending statements. Parallelizable statements
   248  are appended to the list as they arrive, and they are only allowed to begin
   249  execution if they don't depend on any statement ahead of them in the list.
   250  Whenever a statement finishes execution, it is removed from the list, and we
   251  check if any pending statements can now begin execution. Note that because
   252  dependencies are detected at the table-level of granularity, the read and
   253  write-sets used for our statement independence detection within this list can
   254  simply hold table IDs.
   255  
   256  When new statements arrive, the `sql.Executor` iterates over them as it does now
   257  in `execStmtsInCurrentTxn`. For each statement, the Executor first checks if it
   258  is one of the [statement types that we
   259  support](#parallelizable-statements-types) 
   260  for statement parallelized execution.
   261  If the statement type is not parallelizable or if the statement has not [opted
   262  into](#programmatic-interface) parallelization, the executor blocks until the
   263  queue of parallelized statements clears before executing the new statement
   264  synchronously. Notice that these semantics reduce to our current statement
   265  execution rules when no statements opt-in to parallelization.
   266  
   267  If the current statement is supported through statement parallelization, we
   268  first create its `planNode` and then collect the
   269  [tables](#dependency-granularity) that it reads and writes to. This will be
   270  accomplished using a new `DependencyCollector` type that will implement the
   271  `planObserver` interface and traverse over the statement's `planNode`. The read
   272  and write-sets collected from the plan will then be compared against the current
   273  statement queue's read and write-sets using the rules for [independent
   274  statements](#independent-statements). If the statement is deemed to be
   275  independent of the current batch of statements, then it can immediately begin
   276  execution in a new goroutine. If the statement is deemed dependent on the
   277  statements ahead of it in the queue, then it is added as a pending statement in
   278  the queue. Note that because no DDL statement types will be parallelizable, the
   279  `planNode`s for statements in the queue will never need to be reinitialized.
   280  
   281  ## Parallelizable Statements Types
   282  
   283  Initially, there will be **4** types of statements that we will allow to be
   284  parallelized:
   285  - INSERT
   286  - UPDATE
   287  - DELETE
   288  - UPSERT
   289  
   290  These statements make up the majority of use cases where statement
   291  parallelization would be useful, and provide fairly straightforward semantics
   292  about read and write-sets. Another important quality of these statements is that
   293  none of them mutate SQL schemas, meaning that the execution or lack of execution
   294  of one of these statements will never necessitate the re-initialization of the
   295  `planNode` for another. This may not be true of UPDATEs on system tables, so we
   296  will need to take special care to disallow that case?
   297  
   298  ## Programmatic Interface
   299  
   300  Most SQL clients talk to databases using a single statement at-a-time
   301  conversational API. This means that the clients send a statement and wait for
   302  the response before proceeding. They expect at the point of a response for a
   303  statement that the statement has already been applied. To fit this execution
   304  model while allowing for multiple statements to run in parallel, we would need
   305  to mock out a successful response to parallelized statements and immediately
   306  send the value back to the client. The decision to either execute the statement
   307  or block on other asynchronously executing statements would come down to if the
   308  new statement was [independent](#independent-statements) from the previous set
   309  of statements. Additionally, we would always need to let all parallelized
   310  statements finish executing on read queries and transaction commit statements.
   311  
   312  To perform statement parallelization using this interface, users would need to
   313  specify which statements could be executed in parallel. They would do so using
   314  some new syntax, for instance appending `RETURNING NOTHING` to the end of INSERT
   315  and UPDATE statements. In turn, adding this syntax would indicate that the SQL
   316  executor could return a fake response (`RowsAffected = 1`) for this statement
   317  immediately and that the client would not expect the result to have any real
   318  meaning. A list of pros and cons to this approach are listed below:
   319  
   320  - [+] Fits the communication model ORMs usually work with
   321  - [+] Fits the communication model other SQL clients usually work with
   322  - [+] Users get more control over which statements are executed concurrently
   323  - [-] Requires us to mock out results.
   324  - [-] Mocking out result also means that the statement parallelization feature
   325    would be unusable for any situation where a client is interested in the real
   326    results of a statement, event when these results do not imply a dependence
   327    between statements.
   328  - [-] Complicates error handling if we need to associate errors with statements.
   329    If we allow ourselves to loosen error semantics for parallelized statements,
   330    as discussed [below](#error-handling) this issue goes away.
   331  - [-] Requires us to introduce new syntax
   332  - [-] Does not permit parallel execution of read queries
   333  - [-] Expected statement execution times become difficult to predict and reason
   334    about. For instance, a large update set in parallelized execution mode would
   335    return immediately. If later a small read query was issued, it would block for
   336    an unexpectedly large amount of time while under-the-hood the update would
   337    actually be executing. This is not a huge issue because this feature will be
   338    opt-in, but it could still lead to some surprising behavior for users in much
   339    the same way lazy evaluation can surprise users.
   340  
   341  ## SQL/KV Interaction
   342  
   343  In order to parallelize the execution of SQL statements, we need a way to allow
   344  multiple SQL statements working within the same `client.Txn` to concurrently
   345  interact with the KV layer.
   346  
   347  The canonical way to batch KV operations within a single `client.Txn` is to
   348  construct a `client.Batch`, add all necessary operations to the batch, and then
   349  run this batch through the `client.Txn`'s `Run` method. This is how the SQL
   350  layer currently interacts with the KV layer, and it works quite well when the
   351  execution of a SQL statement needs to perform a few operations. However, the
   352  current semantics of `client.Txn` and `client.Batch` have one property that
   353  makes it suboptimal for our use case here: only a single `client.Batch` can be
   354  executed at a time for a given `client.Txn`.
   355  
   356  If the restriction that only one `client.Batch` can execute in a `client.Txn` at
   357  a time was lifted then SQL statement execution logic would not have to change
   358  between serially executed statements and concurrently executed statements. Each
   359  independent statement could run in its own goroutine and use its own
   360  `client.Batch`
   361  
   362  This offers a number of benefits including that it is easier to reason about
   363  from the SQL layer, it eliminates the issue about statements that execute
   364  multiple batches, it is a generally useful improvement to the KV client API that
   365  could be beneficial elsewhere, and it removes the restriction of [coalesced
   366  batches](#coalesced-batches) that statements need to access the KV layer in
   367  lockstep. This last point means that this approach should also perform better
   368  than the alternative.
   369  
   370  However, it is unclear how difficult lifting this restriction would be. At the
   371  very least, it would require `client.Txn` to manage its state in a thread-safe
   372  manner, and would also require changes to `kv.TxnCoordSender`.
   373  
   374  ## Error Handling
   375  
   376  Error handling is a major concern with the parallelization of SQL statements.
   377  However, because Parallel SQL Statement Execution will be an opt-in feature, we
   378  deem it ok to relax constraints and simplify error handling. For instance, we
   379  may be able to loosen the guarantee that the earliest error seen in a parallel
   380  batch will be the one returned. This is not an unreasonable relaxation because
   381  parallel-executing statements will always be independent, and therefore the
   382  successful execution of one statement should never depend on the result of
   383  another earlier in the transaction executing at the same time.
   384  
   385  Furthermore, in CockroachDB errors invalidate the entire transaction, so there
   386  is little benefit to performing fine-grained error handling in the application.
   387  We can contrast this to MySQL, where it is common to try an INSERT, catch any
   388  constraint violation, and fall back to an UPDATE. That doesn't work for us
   389  because the transaction is in an undefined state after any error and can only be
   390  retried from the beginning.
   391  
   392  ## Other Design Considerations
   393  
   394  ### EXPLAIN support
   395  
   396  Regardless of the programmatic interface we go with, we will also want to
   397  support introspection into the behavior and effectiveness of statement
   398  parallelization through the `EXPLAIN` statement. By exposing these details to
   399  users, they can learn more about the behavior of their queries and the impact
   400  parallelized statement execution is having on those queries' execution.
   401  
   402  It is not immediately obvious how this should work, and it will likely be
   403  different depending on the parallelization interface. For now, the details of
   404  how `EXPLAIN` will interact with statement parallelization remains an open
   405  question, but the ability to use `EXPLAIN` to get insight into parallelized
   406  statement execution remains a design goal.
   407  
   408  ### Thread-Safe sql.Sessions
   409  
   410  `sql.Session` currently assumes the single-threaded execution of SQL statements.
   411  This could be problematic when attempting to run execute multiple statements
   412  concurrently because certain Session variables can be used and modified during
   413  statement execution, such as `Location`, `Database`, and `virtualSchemas`.
   414  However, none of these variables should ever be mutated by the [limited set of
   415  SQL statements](#parallelizable-statements-types) we allow to be parallelized,
   416  so we do not believe any further synchronization methods will need to be
   417  employed in order to keep these accesses safe. Still, we should be aware of this
   418  during implementation.
   419  
   420  ## Motivating Example Analysis
   421  
   422  Below is one of the motivating examples for this change. Five statements are
   423  executed sequentially in a transaction, and work on the schema created directly
   424  above them.
   425  
   426  ```
   427  CREATE TABLE IF NOT EXISTS account (
   428    id INT,
   429    balance BIGINT NOT NULL,
   430    name STRING,
   431  
   432    PRIMARY KEY (id),
   433    UNIQUE INDEX byName (name)
   434  );
   435  
   436  CREATE TABLE IF NOT EXISTS transaction (
   437    id INT,
   438    booking_date TIMESTAMP DEFAULT NOW(),
   439    txn_date TIMESTAMP DEFAULT NOW(),
   440    txn_ref STRING,
   441  
   442    PRIMARY KEY (id),
   443    UNIQUE INDEX byTxnRef (txn_ref)
   444  );
   445  
   446  CREATE TABLE IF NOT EXISTS transaction_leg (
   447    id BYTES DEFAULT uuid_v4(),
   448    account_id INT,
   449    amount BIGINT NOT NULL,
   450    running_balance BIGINT NOT NULL,
   451    txn_id INT,
   452  
   453    PRIMARY KEY (id)
   454  );
   455  
   456  BEGIN;
   457  SELECT id, balance FROM account WHERE id IN ($1, $2); -- result used by client
   458  INSERT INTO transaction (id, txn_ref) VALUES ($1, $2);
   459  INSERT INTO transaction_leg (account_id, amount, running_balance, txn_id) VALUES ($1, $2, $3, $4);
   460  UPDATE account SET balance = $1 WHERE id = $2;
   461  UPDATE account SET balance = $1 WHERE id = $2;
   462  COMMIT;
   463  ```
   464  
   465  At present time, each of these five statements executes sequentially. The
   466  execution timeline of this transaction looks like:
   467  
   468  ```
   469  BEGIN S1-----\S1 S2-----\S2 S3-----\S3 S4-----\S4 S5-----\S5 COMMIT
   470  ```
   471  
   472  It is interesting to explore how this newly proposed parallelized statement
   473  execution functionality could be used to speed up this transaction. For now, we
   474  will assume that the programmatic interface decided upon was the `RETURN
   475  NOTHING` proposal.
   476  
   477  First, we note that the SELECT statement's results are used by the client, so
   478  even if our proposal supported read queries, it would not be useful here.
   479  However, this is the only statement where the results are used. Because of that,
   480  the client can add the `RETURNING NOTHING` clause to the end of the four
   481  mutating statements to indicate to CockroachDB that the results of the
   482  statements are not needed and that Cockroach should try to parallelize their
   483  execution. At this point, the transaction looks like this:
   484  
   485  ```
   486  BEGIN;
   487  SELECT id, balance FROM account WHERE id IN ($1, $2);
   488  INSERT INTO transaction (id, txn_ref) VALUES ($1, $2) RETURNING NOTHING;
   489  INSERT INTO transaction_leg (account_id, amount, running_balance, txn_id) VALUES ($1, $2, $3, $4) RETURNING NOTHING;
   490  UPDATE account SET balance = $1 WHERE id = $2 RETURNING NOTHING;
   491  UPDATE account SET balance = $1 WHERE id = $2 RETURNING NOTHING;
   492  COMMIT;
   493  ```
   494  
   495  We see that the two INSERT statements go to different tables. Because of this,
   496  and because they share no implicit writes, they fit the definition for
   497  independent statements. Furthermore, the following UPDATE is also to a different
   498  table, and it can be proven to be independent of the union of the two INSERT
   499  statements. However, because the second UPDATE is not to a unique table, it is
   500  not independent of the previous three statements. It is true that the first and
   501  second UPDATE statements could be merged with some clever `CASE` trickery, but
   502  for now we will ignore this optimization because it is not representative of a
   503  query that most ORMs would execute. So, with no other changes, the execution
   504  timeline of this statement will now look like:
   505  
   506  ```
   507  BEGIN S1-----\S1 S2-----\S2   S5-----\S5 COMMIT
   508                    S3-----\S3
   509                     S4-----\S4
   510  ```
   511  
   512  This is already a huge improvement. Assuming all statements take roughly the
   513  same time to execute and that client-server latency is negligible, we've
   514  effectively cut the processing time for the transaction by 40%!
   515  
   516  ### Future Optimization
   517  
   518  While our current proposal already speeds up the motivating example
   519  substantially, there is still room for improvement. Specially, we can reason
   520  that while the last two UPDATE statements mutate the same table, as long as
   521  their `$2` parameters are different, they will not overlap and are actually
   522  independent. The reason for our "false" dependency classification is the
   523  [conservative dependency detection granularity](#dependency-granularity)
   524  proposed in our initial implementation of statement parallelization. While in
   525  theory increasing this granularity to the row-level across the board would solve
   526  this issue, it would also come with a number of complications. Alternatively,
   527  one proposed solution is to selectively increase this granularity if and only if
   528  all statements operating on the same table also fit some predefined pattern. One
   529  such pattern might be the "single-row-WHERE-matches-primary-key" style, which
   530  makes it much easier to analyze inter-statement dependencies than in the general
   531  case. If such an optimization did exist, we could detect that the two UPDATEs
   532  were actually independent, we could cut processing time for the transaction by
   533  60% using the following execution timeline:
   534  
   535  ```
   536  BEGIN S1-----\S1 S2-----\S2    COMMIT
   537                    S3-----\S3
   538                     S4-----\S4
   539                      S5-----\S5
   540  ```
   541  
   542  # Drawbacks
   543  
   544  ### Disrupting Data Locality (and other unintended performance consequences)
   545  
   546  Parallelizing SQL statements will necessarily alter data access patterns and
   547  locality for transactions. This is because statements that previously executed
   548  in series, implicitly isolated from each other, will now be executing
   549  concurrently. In practice, this means that two statements, although proven
   550  independent in terms of data dependencies, may subtly interact with one another
   551  down the stack beneath the SQL layer. These interactions could range from
   552  altering cache utilization in RocksDB to changing network communication
   553  characteristics. In most cases, we expect that these effects will be negligible.
   554  Furthermore, these effects should be no different than those seen between any
   555  other non-contentious statements executing concurrently. Nevertheless, users
   556  should be aware of this and benchmark any benefit they get from parallel
   557  statement execution before expecting a certain level of performance improvement.
   558  
   559  
   560  # Alternatives
   561  
   562  ### Perform no Dependency Detection
   563  
   564  An alternative option to [detecting statement
   565  independence](#dependency-detection) and performing batching based off this
   566  analysis is to push all decision-making down to the user. This approach has the
   567  benefit that CockroachDB will no longer be responsible for deciding if two
   568  statements are safe to execute in parallel. Instead, we shift all of this
   569  responsibility to the user. With this approach, we would instead need to decide
   570  on a new interface for users to specify that they want multiple statements
   571  within the same transaction to execute in parallel. We have two main objections
   572  to this approach. First, in many cases, it may actually be easier for us to
   573  decide if two statements are independent than for a user to reason about it,
   574  especially in the case of obscure data dependencies like foreign keys. Secondly,
   575  the automatic detection of parallelizable SQL statements by our SQL engine would
   576  be a powerful feature for many users, and would fit with our theme of "making
   577  data easy".
   578  
   579  ### Parallelizing Semicolon-Separated Statements
   580  
   581  Our SQL engine currently allows users to provide a semicolon-separated list of
   582  SQL statements in a single string, and will execute all statements before
   583  returning multiple result sets back in a single response to the user. Since the
   584  SQL Executor has access to multiple statements in a transaction all at once when
   585  a user provides a semicolon-separated list of statements, this interface would
   586  be a natural fit to extend for parallelized statement execution. An alternate
   587  approach to parallelizing statements through a standard conversational API is to
   588  parallelize statements sent within a semicolon-separated statement list. A user
   589  would simply activate statement parallelization through some kind of session
   590  variable setting and would then send groups of statements separated by
   591  semicolons in a single request. Our SQL engine would detect statements that
   592  could be run in parallel and would do so whenever possible. It would then return
   593  a single response with multiple result sets, like it already does for grouped
   594  statements like these. A list of pros and cons to this approach are listed
   595  below:
   596  
   597  - [+] Drivers are already expecting multiple result sets all at once when they
   598    send semicolon-separated statements
   599  - [+] Never needs to mock out statement results
   600  - [+] Allows [error handling](#error-handling) to remain relatively
   601    straightforward because the executor has all statements on hand at the time of
   602    batch execution and will not prematurely testify to the success or failure of
   603    any statement before its execution
   604  - [+] Fits a nice mental model/easy to reason about
   605  - [+] Does not need to be opt-in, although we probably still want it to be, at
   606    least at first
   607  - [+] More generally applicable
   608  - [+] Could support parallelized reads
   609  - [-] May not be possible to use with ORMs (or may at least require custom
   610    handling)
   611  - [-] Inflexible with regard to allowing users to decide which statements get
   612    executed in parallel
   613  
   614  ### Coalesced Batches
   615  
   616  An alternate approach to supporting concurrent `client.Batch`es executing within
   617  a `client.Txn` is to gain concurrency support at a layer above KV, in SQL logic.
   618  To do this, we would build up a single `client.Batch` between multiple
   619  statements and then run this batch when all statements are ready.
   620  
   621  Doing so would not be terribly difficult, but would be fairly intrusive into
   622  code surrounding the `sql.tableWriter` interface. The `sql.Executor` would need
   623  to inject some new object (`BatchCoalescer`?) that wraps a single
   624  `client.Batch`, packages multiple client requests from multiple goroutines into
   625  the batch, sends the batch only when all goroutines were ready, and then
   626  delegates the results out to the appropriate goroutines when the batch response
   627  arrives. During this implementation, it would almost certainly make sense to
   628  pull out a new interface in the `internal/client` package that incorporates the
   629  creation/execution of client requests (Get, Put, etc.), since these methods are
   630  already shared across `client.DB`, `client.Txn`, and `client.Batch`. Our new
   631  batch delegation object could then also implement this interface to allow for
   632  conditional indirection in the SQL layer.
   633  
   634  This approach gets more complicated when considering statements that issue
   635  multiple KV batches. These cases are not rare, and arise for all statements that
   636  deal with foreign keys. This is also the case for UPSERT statements. The optimal
   637  solution here is not clear, but may reduce to the serial execution of these
   638  parts of the statement evaluation, or the decision to simply not parallelize any
   639  statement that requires more than one `client.Batch` execution at all.
   640  
   641  
   642  # Unresolved questions
   643  
   644  ### Distributed SQL Interaction
   645  
   646  The dependency detection algorithm proposed here applies just as well to
   647  distributed SQL as it does to standard SQL. Likewise, the question of
   648  programmatically interfacing with this new feature applies to both SQL engines
   649  as well. However, it remains to be explored if distributed SQL will need any
   650  special handling for parallel statement execution.
   651  
   652  ### Parallel Foreign Key Validation
   653  
   654  There is a
   655  [TODO](https://github.com/cockroachdb/cockroach/blob/b3e11b238327c2625c519503691361850c2bb261/pkg/sql/fk.go#L294)
   656  in foreign key validation code to batch the checks for multiple rows. While not
   657  directly related to this RFC, infrastructure developed here could be directly
   658  applicable to solving that issue.