Skip to content

Forward libpq TCP keepalive / tcp_user_timeout parameters in the PostgreSQL drivers #7408

Description

@benconda

Feature Request

Q A
New feature yes
Affected drivers pdo_pgsql, pgsql (native)
DBAL version 3.10.x and current 4.4.x

Summary

The PostgreSQL drivers build the connection string from a fixed whitelist of
parameters and silently drop everything else. As a result, the libpq TCP-resilience
keywords — keepalives, keepalives_idle, keepalives_interval, keepalives_count,
tcp_user_timeout (and connect_timeout) — cannot be configured through DBAL, even
though libpq and ext-pgsql/pdo_pgsql fully support them.

I'd like to propose forwarding these keywords to the connection string.

Motivation

When a TCP connection to PostgreSQL is silently cut mid-query (e.g. a flaky link
between two datacenters, a stateful firewall/NAT dropping an idle entry, a load
balancer reset without RST), the PHP process blocks in libpq's recv() until the
kernel gives up. With the Linux default net.ipv4.tcp_retries2 = 15, that is
~15 minutes per stuck request, which can exhaust the worker/FPM pool and cascade
into an outage.

We hit exactly this in production (queries hanging for a consistent ~944 s ≈
tcp_retries2=15, failing with SQLSTATE[HY000] [7] could not receive data from server: Connection timed out), while healthy query durations are sub-second
(p99 ≈ 30 ms). The standard remedy is libpq's keepalives + tcp_user_timeout, which
bound dead-peer detection to a few seconds — but there is currently no way to set them
via DBAL.

Current behavior

Doctrine\DBAL\Driver\PDO\PgSQL\Driver::constructPdoDsn() only appends:
host, port, dbname, sslmode, sslrootcert, sslcert, sslkey, sslcrl,
application_name, gssencmode.

Doctrine\DBAL\Driver\PgSQL\Driver::constructConnectionString() (native) only keeps:
host, hostaddr, port, dbname, user, password, sslmode, gssencmode.

Any other connection parameter passed via DriverManager::getConnection([...]) is
ignored, so these keywords never reach libpq.

Proposed change

Forward the libpq TCP keywords in both PgSQL drivers' connection-string builders,
in the same explicit style already used for the ssl* keywords:

  • keepalives
  • keepalives_idle
  • keepalives_interval
  • keepalives_count
  • tcp_user_timeout
  • connect_timeout
// example, e.g. DriverManager::getConnection()
$conn = DriverManager::getConnection([
    'driver'           => 'pdo_pgsql',
    'host'             => 'db.internal',
    'dbname'           => 'app',
    'keepalives'       => 1,
    'keepalives_idle'  => 10,
    'tcp_user_timeout' => 15000, // ms
]);

All of these are standard libpq connection keywords
(https://www.postgresql.org/docs/current/libpq-connect.html), already accepted by
pdo_pgsql / ext-pgsql DSNs today.

Workaround

Today this requires a custom driver_class that subclasses AbstractPostgreSQLDriver
and re-implements the DSN builder (the stock PDO driver is final), just to append a
handful of keywords — which means duplicating connect/DSN logic that has to be kept in
sync with DBAL.

Open question for maintainers

Would you prefer:

  1. Enumerating these specific keywords (matches the current ssl* / gssencmode
    style), or
  2. A generic passthrough of a documented set of libpq keywords?

Happy to open a PR for whichever direction you'd like (covering both the PDO and native
drivers, plus docs/tests).

Environment

  • DBAL: 3.10.x (also verified the gap on 4.4.x)
  • PHP: 8.3, ext-pdo_pgsql (libpq 18)
  • PostgreSQL: 16

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions