Subject: [pgjdbc/pgjdbc] issue #2325: Support pipelining queries

From: "kustodian (@kustodian)" <[email-masked]>
Date: Wed, 27 Oct 2021 09:33:50 +0000

**I'm submitting a ...**
- feature request

**Describe the issue**
Postgres 14 libpq added the [pipeline mode](https://www.postgresql.org/docs/14/libpq-pipeline-mode.html) so it would be great if JDBC also supported this feature which can significantly improve performance over high latency connections or for workloads with many small write (INSERT/UPDATE/DELETE) operations. 

**PostgreSQL Version?**
14

---

From: "davecramer (@davecramer)" <[email-masked]>
Date: Wed, 27 Oct 2021 09:36:36 +0000

Unfortunately the driver is synchronous. Making the driver asynchronous is a pretty big blocker here.
Curious how would you even use this in the JDBC model ? You would need to use futures, etc.

---

From: "davecramer (@davecramer)" <[email-masked]>
Date: Fri, 17 Dec 2021 14:00:29 +0000

I'm not even sure how you would use this in JDBC? It's not asynchronous at all

---

From: "vlsi (@vlsi)" <[email-masked]>
Date: Fri, 17 Dec 2021 16:39:06 +0000

We can try inventing a better "prepared statement batching".

1. JDBC spec requires that batch is executed only with `executeBatch` call. In other words, the spec does not allow sending queries earlier. We could do better if we were allowed to send queries as soon as the user calls `addBatch`. Unfortunately, the spec allows for `clearBatch` method, so eager sending should be opt-in (e.g. via special `setBatchMode(forward_only)`).
On the other hand, we already have `rewrite batch inserts to insert values`, so we might add an option that would automatically pipeline after each `addBatch`, and it would throw an exception if the user calls `clearBatch` when some of the data has been pipelined. @davecramer , WDYT?

1. JDBC spec requires that the shape of all the batched statements is the same. This effectively blocks batching statements with different SQL. Of course, we might try adding our own API (e.g. something like `Copy` where you add `PreparedStatements`), however, it would be a non-standard API.

----

If we consider pipelining arbitrary SQLs, then it could be like:

```
nameResultSet = con.executeStatement("select name from object where id=1"); // pipelines query
descriptionResultSet = con.executeStatement("select description from object where id=1"); // pipelines query

nameResultSet.next(); // <-- blocks and waits for the result
descriptionResultSet.next(); // <-- the result is likely ready by this time
```

Unfortunately, we can't move exception from `executeStatement` till `nameResultSet.next()` as it would change the semantics. The application might expect that "invalid SQL" would result in SQL exception when you call `executeStatement`, and they would be surprised in case the exception is delayed till the first `resultSet` operation or even till the statement is closed (e.g. in case resultset was not really used).

On the other hand, I do not know if moving "exceptions" from `executeStatement` till "the first access of resultset" is really that breaking for the applications.
It might be fun to try `autoPipelineAllQueries=true` approach so `statement.executeQuery()` does not really wait for the response from the backend.

---

From: "davecramer (@davecramer)" <[email-masked]>
Date: Fri, 17 Dec 2021 16:55:12 +0000

Good catch, I hadn't thought about the batching stuff. It would certainly be non-standard. The question becomes who has the bandwidth to implement this ?

---

From: "ebrandsberg (@ebrandsberg)" <[email-masked]>
Date: Wed, 20 Jul 2022 14:31:32 +0000

This would impact proxies as well potentially...

---

From: "beikov (@beikov)" <[email-masked]>
Date: Wed, 11 Feb 2026 22:58:30 +0000

I'd be interested in implementing support for pipelining when using virtual threads.

The idea is that, when a virtual thread executes a query, to block the thread waiting for the result, but then allow another virtual thread to execute another query in pipeline mode on the same connection.

Would you also be interested in such an implementation?

---

From: "davecramer (@davecramer)" <[email-masked]>
Date: Thu, 12 Feb 2026 10:43:14 +0000

Yup, have at it. Look forward to seeing this.

---

From: "beikov (@beikov)" <[email-masked]>
Date: Fri, 27 Mar 2026 10:37:20 +0000

I would appreciate if I could get some feedback on e.g. https://github.com/pgjdbc/pgjdbc/pull/4010. I also have a [different prototype](https://github.com/pgjdbc/pgjdbc/pull/4009), but there are some locking issues which are avoided by https://github.com/pgjdbc/pgjdbc/pull/4010

---

From: "vlsi (@vlsi)" <[email-masked]>
Date: Fri, 27 Mar 2026 11:19:35 +0000

@beikov , thank you for working on this, however, could you please clarify why you suggest pipeline mode for virtual threads only?
I guess the same approach should work with regular threads as well.

---

From: "beikov (@beikov)" <[email-masked]>
Date: Fri, 27 Mar 2026 11:25:14 +0000

I could try to remodel it to also work with regular threads, but sharing a connection between different non-virtual threads didn't seem like a good idea to me, whereas with virtual threads it feels more natural.

Are there any good use cases for doing pipelining this way with platform threads? I would think that for non-virtual thread users it would be much more convenient to use the JDBC batch API for pipelining instead. Wdyt?

---

From: "vlsi (@vlsi)" <[email-masked]>
Date: Fri, 27 Mar 2026 15:00:37 +0000

> connection between different non-virtual threads didn't seem like a good idea to me

> whereas with virtual threads it feels more natural

Why so?

I do not understand why sharing a connection between virtual threads should look any better than sharing connections between regular threads.

PS PostgreSQL backend semantics is "any failure kills the transaction". So, if a single SQL fails, all subsequent SQLs would fail as well until commit/rollback. That means, if virtual threads are unrelated, a single problematic would kill the results for all the others.

---

From: "vlsi (@vlsi)" <[email-masked]>
Date: Fri, 27 Mar 2026 15:01:29 +0000

> JDBC batch API

It does not support select pipelining, it does not support pipelining queries of different shapes (e.g. insert / update pipelining).

---

From: "beikov (@beikov)" <[email-masked]>
Date: Fri, 27 Mar 2026 15:25:17 +0000

> I do not understand why sharing a connection between virtual threads should look any better than sharing connections between regular threads.

In my mind, a connection is always used in a single threaded manner. Since virtual threads would usually (always?) run on the same carrier thread, the whole thing is more or less single-threaded, which is why it seems more natural to me. Anyway, if you think that people will spawn or share the connection with platform threads to achieve pipelining, then I'll test to see if that works as well.

> PS PostgreSQL backend semantics is "any failure kills the transaction". So, if a single SQL fails, all subsequent SQLs would fail as well until commit/rollback. That means, if virtual threads are u…

I don't understand why virtual threads would be the problem here. A connection is shared, so any thread that uses that connection has to understand the implications. Either way, a connection can fail due to communication errors any time, so not sure what you're trying to get at here.

> It does not support select pipelining, it does not support pipelining queries of different shapes (e.g. insert / update pipelining).

I was thinking about `java.sql.Statement#addBatch(String)`, but that doesn't support parameters, which is unfortunate.

I heard that the Oracle JDBC driver folks toyed around with splitting the SQL string passed to `prepareStatement` on semi-colon and then pipeline these statements, which supports parameters, but requires numbering them for the whole string.

Anyway, my focus is on implementing support for pipelining such that it works with virtual threads, because that is the use case I want to work. I'll see if I can easily support platform threads and add that.

---

From: "vlsi (@vlsi)" <[email-masked]>
Date: Fri, 27 Mar 2026 16:55:57 +0000

> then I'll test to see if that works as well.

I haven't noticed if you use any virtual thread specific APIs, so it is unclear why you cover the feature with `if (virtual)` check.

> Since virtual threads would usually (always?) run on the same carrier thread, the whole thing is more or less single-threaded

Imagine someone switching their Spring Boot app to virtual threads and saying something like "we don't need connection pool, just pipeline everything through a single connection".

---

Do you know practical ways to test the change?
I wonder what are the limitations and what could theoretically be put in a changelog.

Would it support just `preparedStatement.execute` or all the APIs?

---

Historically, the implementation was not designed for full thread-safety, so mutable fields in `PgConnection` might play against concurrent use of the connection.