feat: make database connection pool size configurable (#21403)

Closes https://github.com/coder/coder/issues/21360

A few considerations/notes:
- I've kept the number of conns to 10 in all other places, except coderd
- which uses the config value
- I opted to also make idle conns configurable; the greater the delta
between max open and max idle, the more connection churn
- Postgres maintains a [_process_ per
connection](https://www.postgresql.org/docs/current/connect-estab.html),
contrary to what the comment said previously
- Operators should be able to tune this, since process churn can
negatively affect OS scheduling
- I've set the value to `"auto"` by default so it's not another knob one
_has to_ twiddle, and sets max idle = max conns / 3

---------

Signed-off-by: Danny Kopping <danny@coder.com>
This commit is contained in:
Danny Kopping
2026-01-13 10:50:57 +02:00
committed by GitHub
parent 61ae5b81ab
commit 49a42eff5c
13 changed files with 334 additions and 17 deletions
+55
View File
@@ -442,6 +442,10 @@ var PostgresAuthDrivers = []string{
string(PostgresAuthAWSIAMRDS),
}
// PostgresConnMaxIdleAuto is the value for auto-computing max idle connections
// based on max open connections.
const PostgresConnMaxIdleAuto = "auto"
// DeploymentValues is the central configuration values the coder server.
type DeploymentValues struct {
Verbose serpent.Bool `json:"verbose,omitempty"`
@@ -462,6 +466,8 @@ type DeploymentValues struct {
EphemeralDeployment serpent.Bool `json:"ephemeral_deployment,omitempty" typescript:",notnull"`
PostgresURL serpent.String `json:"pg_connection_url,omitempty" typescript:",notnull"`
PostgresAuth string `json:"pg_auth,omitempty" typescript:",notnull"`
PostgresConnMaxOpen serpent.Int64 `json:"pg_conn_max_open,omitempty" typescript:",notnull"`
PostgresConnMaxIdle serpent.String `json:"pg_conn_max_idle,omitempty" typescript:",notnull"`
OAuth2 OAuth2Config `json:"oauth2,omitempty" typescript:",notnull"`
OIDC OIDCConfig `json:"oidc,omitempty" typescript:",notnull"`
Telemetry TelemetryConfig `json:"telemetry,omitempty" typescript:",notnull"`
@@ -2623,6 +2629,30 @@ func (c *DeploymentValues) Options() serpent.OptionSet {
Value: serpent.EnumOf(&c.PostgresAuth, PostgresAuthDrivers...),
YAML: "pgAuth",
},
{
Name: "Postgres Connection Max Open",
Description: "Maximum number of open connections to the database. Defaults to 10.",
Flag: "postgres-conn-max-open",
Env: "CODER_PG_CONN_MAX_OPEN",
Default: "10",
Value: serpent.Validate(&c.PostgresConnMaxOpen, func(value *serpent.Int64) error {
if value.Value() <= 0 {
return xerrors.New("must be greater than zero")
}
return nil
}),
YAML: "pgConnMaxOpen",
},
{
Name: "Postgres Connection Max Idle",
Description: "Maximum number of idle connections to the database. Set to \"auto\" (the default) to use max open / 3. " +
"Value must be greater or equal to 0; 0 means explicitly no idle connections.",
Flag: "postgres-conn-max-idle",
Env: "CODER_PG_CONN_MAX_IDLE",
Default: PostgresConnMaxIdleAuto,
Value: &c.PostgresConnMaxIdle,
YAML: "pgConnMaxIdle",
},
{
Name: "Secure Auth Cookie",
Description: "Controls if the 'Secure' property is set on browser session cookies.",
@@ -4128,3 +4158,28 @@ func (c CryptoKey) CanVerify(now time.Time) bool {
beforeDelete := c.DeletesAt.IsZero() || now.Before(c.DeletesAt)
return hasSecret && beforeDelete
}
// ComputeMaxIdleConns calculates the effective maxIdleConns value. If
// configuredIdle is "auto", it returns maxOpen/3 with a minimum of 1. If
// configuredIdle exceeds maxOpen, it returns an error.
func ComputeMaxIdleConns(maxOpen int, configuredIdle string) (int, error) {
configuredIdle = strings.TrimSpace(configuredIdle)
if configuredIdle == PostgresConnMaxIdleAuto {
computed := maxOpen / 3
if computed < 1 {
return 1, nil
}
return computed, nil
}
idle, err := strconv.Atoi(configuredIdle)
if err != nil {
return 0, xerrors.Errorf("invalid max idle connections %q: must be %q or >= 0", configuredIdle, PostgresConnMaxIdleAuto)
}
if idle < 0 {
return 0, xerrors.Errorf("max idle connections must be %q or >= 0", PostgresConnMaxIdleAuto)
}
if idle > maxOpen {
return 0, xerrors.Errorf("max idle connections (%d) cannot exceed max open connections (%d)", idle, maxOpen)
}
return idle, nil
}