Skip to content

Commit 8933f30

Browse files
Added support for pipelining
1 parent f290557 commit 8933f30

16 files changed

+728
-435
lines changed

README.md

Lines changed: 117 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -460,33 +460,137 @@ Oracle R2DBC.
460460
- [oracle.net.ldap.ssl.trustManagerFactory.algorithm](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_LDAP_SSL_TRUSTMANAGER_FACTORY_ALGORITHM)
461461
- [oracle.net.ldap.ssl.ssl_context_protocol](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_LDAP_SSL_CONTEXT_PROTOCOL)
462462

463-
### Thread Safety and Parallel Execution
463+
### Thread Safety
464464
Oracle R2DBC's `ConnectionFactory` and `ConnectionFactoryProvider` are the only
465465
classes that have a thread safe implementation. All other classes implemented
466466
by Oracle R2DBC are not thread safe. For instance, it is not safe for multiple
467467
threads to concurrently access a single instance of `Result`.
468468
> It is recommended to use a Reactive Streams library such as Project Reactor
469469
> or RxJava to manage the consumption of non-thread safe objects
470470
471-
Oracle Database does not allow multiple database calls to execute in parallel
472-
over a single `Connection`. If an attempt is made to execute a database call
473-
before a previous call has completed, then Oracle R2DBC will enqueue that call
474-
and only execute it after the previous call has completed.
475-
476-
To illustrate, the following code attempts to execute two statements in
477-
parallel:
471+
While it is not safe for multiple threads to concurrently access the _same_
472+
object, it is safe from them to do so with _different_ objects from the _same_
473+
`Connection`. For example, two threads can concurrently subscribe to two
474+
`Statement` objects from the same `Connection`. When this happens, the two
475+
statements are executed in a "pipeline". Pipelining will be covered in the next
476+
section.
477+
478+
### Pipelining
479+
Pipelining allows Oracle R2DBC to send a call without having to wait for a previous call
480+
to complete. [If all requirements are met](#requirements-for-pipelining), then
481+
pipelining will be activated by concurrently subscribing to publishers
482+
from the same connection. For example, the following code concurrently
483+
subscribes to two statements:
478484
```java
479485
Flux.merge(
480486
connection.createStatement(
481-
"INSERT INTO example (id, value) VALUES (0, 'x')")
487+
"INSERT INTO example (id, value) VALUES (0, 'X')")
482488
.execute(),
483489
connection.createStatement(
484-
"INSERT INTO example (id, value) VALUES (1, 'y')")
490+
"INSERT INTO example (id, value) VALUES (1, 'Y')")
485491
.execute())
486492
```
487-
When the publisher of the second statement is subscribed to, Oracle R2DBC will
488-
enqueue a task for sending that statement to the database. The enqueued task
489-
will only be executed after the publisher of the first statement has completed.
493+
When the `Publisher` returned by `merge` is subscribed to, both INSERTs are
494+
immediately sent to the database. The network traffic can be visualized as:
495+
```
496+
TIME | ORACLE R2DBC | NETWORK | ORACLE DATABASE
497+
-----+------------------+---------+-----------------
498+
0 | Send INSERT-X | ------> | WAITING
499+
0 | Send INSERT-Y | ------> | WAITING
500+
1 | WAITING | <------ | Send Result-X
501+
1 | WAITING | <------ | Send Result-Y
502+
2 | Receive Result-X | | WAITING
503+
2 | Receive Result-Y | | WAITING
504+
505+
```
506+
In this visual, 1 unit of TIME is required to transfer data over the
507+
network. The TIME column is only measuring network latency. It does not include
508+
computational time spent on executing the INSERTs.
509+
510+
The key takeaway from this visual is that the INSERTs are sent and
511+
received _concurrently_, rather than _sequentially_. Both INSERTs are sent at
512+
TIME=0, and both are received at TIME=1. And, the results are both sent at TIME=1,
513+
and are received at TIME=2.
514+
515+
> Recall that TIME is not measuring computational time. If each action by Oracle
516+
> R2DBC and Oracle Database requires 0.1 units of computational TIME, then we
517+
> can say:
518+
>
519+
> INSERTs are sent at TIME=0.1 and TIME=0.2, and are received at TIME=1.1 and
520+
> TIME=1.2. And, the results are sent at TIME=1.2 and
521+
> TIME=1.3, and are received at TIME=2.2 and TIME=2.3.
522+
>
523+
> This is a bit more complicated to think about, but it is important to keep in
524+
> mind. All database calls will require at least some computational time.
525+
526+
Below is another visual of the network traffic, but in this case the INSERTs are
527+
sent and received _without pipelining_:
528+
```
529+
TIME | ORACLE R2DBC | NETWORK | ORACLE DATABASE
530+
-----+------------------+---------+-----------------
531+
0 | Send INSERT-X | ------> | WAITING
532+
1 | WAITING | <------ | Send Result-X
533+
2 | Receive Result-X | | WAITING
534+
2 | Send INSERT-Y | ------> | WAITING
535+
3 | WAITING | <------ | Send Result-Y
536+
4 | Receive Result-Y | | WAITING
537+
538+
```
539+
This visual shows a _sequential_ process of sending and receiving. It can be
540+
compared to the _concurrent_ process seen in the previous visual. In both cases,
541+
Oracle R2DBC and Oracle Database have the same number of WAITING actions. These
542+
actions are waiting for network transfers. And in both cases, each network
543+
transfer requires 1 unit of TIME.
544+
545+
So if network latency is the same, and the number of
546+
WAITING actions are the same (,and the
547+
computational times are the same), then how are these INSERTs completing in less
548+
TIME with pipelining? The answer is that _pipelining allowed the
549+
network transfer times to be waited for __concurrently___.
550+
551+
In the first visual, with pipelining, the database waits for _both_ INSERT-X and
552+
INSERT-Y at TIME=0. Compare that to the second visual, without pipelining, where
553+
the database waits for INSERT-X at TIME=0, and then _waits again_ for INSERT-Y
554+
at TIME=2. That's 1 additional unit of TIME when compared to pipelining. The
555+
other additional unit of TIME happens on the Oracle R2DBC side. Without
556+
pipelining, it waits for Result-X at TIME=1, and then _waits again_ for Result-Y
557+
at TIME=3. With pipelining, it _waits for both results concurrently_ at TIME=1.
558+
559+
### Requirements for Pipelining
560+
561+
There are some requirements which must be met in order to use pipelining. As
562+
explained in the previous section, the availability of pipelining can have a
563+
significant impact on performance. Users should review the requirements listed
564+
in this section when developing applications that rely on this performance gain.
565+
566+
In terms of functional behavior, the availability of pipelining will have no
567+
impact: With or without it, the same database calls are going be executed. Users
568+
who are not relying on pipelining performance do not necessarily need to review
569+
the requirements listed in this section. Oracle JDBC is designed to
570+
automatically check for these requirements, and it will fallback to using
571+
sequential network transfers if any requirement is not met.
572+
573+
#### Product Versions
574+
Pipelining is only available with Oracle Database version 23.4 or newer. It also
575+
requires an Oracle JDBC version of 23.4 or newer, but this is already a
576+
transitive dependency of Oracle R2DBC.
577+
578+
#### Out Of Band Breaks
579+
Pipelining requires out-of-band (OOB) breaks (ie: TCP urgent data) for cancelling
580+
statement execution. The Oracle JDBC Driver automatically checks if OOB is
581+
available, and will disable pipelining if it is not. The availability of OOB may
582+
depend on the operating system where Oracle R2DBC is running. Notably, _OOB is
583+
not available on Mac OS_ (or at least not available in the way which Oracle JDBC
584+
needs it to be for sending TCP urgent data to Oracle Database).
585+
586+
__For experimentation only__, Mac OS users can choose to by-pass the OOB
587+
requirement by setting a JVM system property:
588+
```
589+
-Doracle.jdbc.disablePipeline=false
590+
```
591+
Bypassing the OOB requirement on Mac OS will result in non-functional
592+
implementations of `Connection.setStatementTimeout(Duration)`, and
593+
`Subscription.cancel()` for a `Subscription` from `Statement.execute()`.
490594

491595
### Reactive Streams
492596
Every method implemented by Oracle R2DBC that returns a Publisher has a JavaDoc

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565

6666
<properties>
6767
<java.version>11</java.version>
68-
<ojdbc.version>21.11.0.0</ojdbc.version>
68+
<ojdbc.version>23.4.0.24.05</ojdbc.version>
6969
<r2dbc.version>1.0.0.RELEASE</r2dbc.version>
7070
<reactor.version>3.5.11</reactor.version>
7171
<reactive-streams.version>1.0.3</reactive-streams.version>

src/main/java/oracle/r2dbc/OracleR2dbcOptions.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,6 @@ private OracleR2dbcOptions() {}
396396
*/
397397
public static final Option<CharSequence> KERBEROS_JAAS_LOGIN_MODULE;
398398

399-
400399
/** The unmodifiable set of all extended options */
401400
private static final Set<Option<?>> OPTIONS = Set.of(
402401
DESCRIPTOR = Option.valueOf("oracle.r2dbc.descriptor"),

0 commit comments

Comments
 (0)