pgjdbc/pgjdbc GitHub issues and pull requests (mirror)
help / color / mirror / Atom feed[pgjdbc/pgjdbc] PR #4036: Add DNS SRV discovery via jdbc:postgresql+srv:// URL scheme
3+ messages / 2 participants
[nested] [flat]
* [pgjdbc/pgjdbc] PR #4036: Add DNS SRV discovery via jdbc:postgresql+srv:// URL scheme
@ 2026-04-21 18:38 "x4m (@x4m)" <[email protected]>
0 siblings, 0 replies; 3+ messages in thread
From: x4m (@x4m) @ 2026-04-21 18:38 UTC (permalink / raw)
To: pgjdbc/pgjdbc <[email protected]>
## Add DNS SRV discovery via `jdbc:postgresql+srv://` URL scheme
### Problem
When running a replicated PostgreSQL cluster, clients need to know the address of every node. Today the only way to express this in a JDBC connection string is a hard-coded comma-separated host list:
```
jdbc:postgresql://pg1.example.com,pg2.example.com,pg3.example.com/mydb?targetServerType=primary
```
This creates operational coupling: every time a node is added, removed, or replaced, the connection string in every application must be updated and redeployed. Managed database providers and HA tooling (Patroni, pg_auto_failover, etc.) cannot change the cluster topology without coordinating with application teams.
### Solution
DNS SRV records ([RFC 2782](https://datatracker.ietf.org/doc/html/rfc2782)) were designed exactly for this: a single name that maps to an ordered, weighted list of `(host, port)` endpoints. MongoDB adopted `mongodb+srv://` for the same reason.
This PR adds a `jdbc:postgresql+srv://` URL scheme (and a `srvhost=` connection property) that tells the driver to resolve `_postgresql._tcp.<cluster>` at connect time and feed the returned targets — sorted by priority then weight per RFC 2782 — into the existing multi-host `HostChooser` / `MultiHostChooser` pipeline:
```java
// Instead of hard-coding every node:
Connection conn = DriverManager.getConnection(
"jdbc:postgresql+srv://cluster.example.com/mydb?targetServerType=primary",
"user", "password");
```
DNS at the provider's side:
```
_postgresql._tcp.cluster.example.com. SRV 10 1 5432 pg1.example.com.
_postgresql._tcp.cluster.example.com. SRV 10 1 5432 pg2.example.com.
_postgresql._tcp.cluster.example.com. SRV 20 1 5433 pg-replica.example.com.
```
Adding or removing a node now requires only a DNS record change — no application config or restart.
### How it integrates with the existing multi-host machinery
The SRV-resolved `HostSpec[]` is passed directly into `ConnectionFactoryImpl.openConnectionImpl()` unchanged. Every existing feature continues to work without modification:
| Feature | Works with SRV |
|---|---|
| `targetServerType=primary/secondary/...` | ✓ |
| `loadBalanceHosts=true` | ✓ |
| `hostRecheckSeconds` | ✓ |
| `GlobalHostStatusTracker` | ✓ |
| `connectTimeout` | ✓ |
| SSL / `sslmode` | ✓ |
### API
**New `PGProperty`:**
```java
PGProperty.SRV_HOST // key: "srvhost"
```
**Supported URL forms:**
```
# +srv scheme (recommended)
jdbc:postgresql+srv://cluster.example.com/mydb?targetServerType=primary&sslmode=require
# srvhost= connection property
jdbc:postgresql:mydb?srvhost=cluster.example.com&targetServerType=primary
```
`srvhost` is mutually exclusive with an explicit host in the URL authority. Specifying both is rejected with a warning during `parseURL()`.
### Implementation
**`SRVLookup` (new class)** — resolves `_postgresql._tcp.<srvHost>` using JNDI DNS (`javax.naming.directory.DirContext`), which ships with every JDK since 1.3. No new dependencies are added.
```java
// DNS query: _postgresql._tcp.cluster.example.com IN SRV
// JNDI returns each record as the string "priority weight port target"
// Records are sorted: priority ASC, weight DESC (RFC 2782)
HostSpec[] specs = SRVLookup.resolve("cluster.example.com");
```
**`Driver.parseURL()`** — detects the `+srv` scheme prefix before normal parsing, strips it, and records the SRV domain in `PGProperty.SRV_HOST`. Also accepts `srvhost=` as a query parameter.
**`Driver.hostSpecs()`** — if `SRV_HOST` is set, delegates to `SRVLookup.resolve()` instead of the normal comma-split logic.
### Testing
**Unit tests (no database, no DNS server):**
| Test | What it covers |
|---|---|
| `parseURLSrvScheme` | `jdbc:postgresql+srv://` sets `SRV_HOST` |
| `parseURLSrvSchemeWithUser` | `+srv` plus query params parses correctly |
| `parseURLSrvKeyword` | `srvhost=` property sets `SRV_HOST` |
| `parseURLSrvAndHostMutuallyExclusive` | returns null when both host and srvhost are given |
| `parseURLNormalUnchanged` | plain JDBC URLs are unaffected |
| `parseAndSortByPriorityAscending` | lower priority number wins |
| `parseAndSortByWeightDescendingWithinPriority` | higher weight wins within same priority |
| `parseAndSortStripsTrailingDot` | FQDN trailing dot is normalised |
| `parseAndSortMixedPriorityAndWeight` | 4-record ordering matches mmatvei.ru real data |
| `parseAndSortEmptyListThrows` | error on empty record list |
**Live DNS test (no database required):**
`testResolveSRVLive` queries four real public SRV records at `_postgresql._tcp.mmatvei.ru` and verifies RFC 2782 priority ordering end-to-end:
```
[0] pg4.mmatvei.ru:5432 (priority 96)
[1] pg3.mmatvei.ru:5432 (priority 97)
[2] pg2.mmatvei.ru:5432 (priority 99)
[3] pg.mmatvei.ru:5432 (priority 100)
```
If the system resolver has a stale negative cache, set `PGJDBC_TEST_SRV_DNS_SERVER=<nameserver-ip>` to query a specific authoritative server.
The test skips automatically when the domain is unreachable, so it never breaks an offline build.
### Prior art and related discussion
- pgjdbc issue discussing multi-host improvements: https://github.com/pgjdbc/pgjdbc/issues/1870
- pgx SRV PR (same feature for the Go driver): https://github.com/jackc/pgx/pull/2538
- Original libpq proposal (2019): https://www.postgresql.org/message-id/[email protected]...
- MongoDB `+srv` scheme for comparison: https://www.mongodb.com/docs/manual/reference/connection-string/#dns-seed-list-connection-format
^ permalink raw reply [nested|flat] 3+ messages in thread
* Re: [pgjdbc/pgjdbc] PR #4036: Add DNS SRV discovery via jdbc:postgresql+srv:// URL scheme
@ 2026-04-22 05:54 "vlsi (@vlsi)" <[email protected]>
1 sibling, 0 replies; 3+ messages in thread
From: vlsi (@vlsi) @ 2026-04-22 05:54 UTC (permalink / raw)
To: pgjdbc/pgjdbc <[email protected]>
Thanks for the suggestion.
I am leaning towards a pluggable approach for the discovery: https://github.com/pgjdbc/pgjdbc/issues/3367
Have you explored the plugin approach so SRV becomes one of the possibilities?
^ permalink raw reply [nested|flat] 3+ messages in thread
* Re: [pgjdbc/pgjdbc] PR #4036: Add DNS SRV discovery via jdbc:postgresql+srv:// URL scheme
@ 2026-04-22 06:26 "x4m (@x4m)" <[email protected]>
1 sibling, 0 replies; 3+ messages in thread
From: x4m (@x4m) @ 2026-04-22 06:26 UTC (permalink / raw)
To: pgjdbc/pgjdbc <[email protected]>
The plugin API from #3367 doesn't exist yet, so adopting it would block this feature. More importantly, SRV is orthogonal to what #3367 is designed for: dynamic load balancing, connection draining, per-host timeouts, and lifecycle callbacks (registerSuccess, registerFailure, registerDisconnect). SRV needs none of that - it's a one-shot host expansion at connect time, backed by an IETF standard (RFC 2782). I'm proposing this across the PostgreSQL driver ecosystem (pgx, npgsql, libpq) for the same reason: it's a standard building block that belongs to a driver.
^ permalink raw reply [nested|flat] 3+ messages in thread
end of thread, other threads:[~2026-04-22 06:26 UTC | newest]
Thread overview: 3+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-04-21 18:38 [pgjdbc/pgjdbc] PR #4036: Add DNS SRV discovery via jdbc:postgresql+srv:// URL scheme "x4m (@x4m)" <[email protected]>
2026-04-22 05:54 ` "vlsi (@vlsi)" <[email protected]>
2026-04-22 06:26 ` "x4m (@x4m)" <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox