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 logical 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.