From 990dfa8438a249ad35a1fc52f3c6f932f114b872 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Wed, 22 Apr 2026 22:29:53 +0200 Subject: [PATCH] Update kit with new pg config Signed-off-by: Bryan Frimin --- .env.example | 3 ++ .../charts/probo/templates/deployment.yaml | 6 +++ .../probo/values-production.yaml.example | 5 +++ contrib/helm/charts/probo/values.yaml | 10 +++++ e2e/internal/testutil/testutil.go | 5 ++- go.mod | 2 +- go.sum | 4 +- pkg/bootstrap/builder.go | 17 ++++---- pkg/bootstrap/builder_test.go | 9 +++++ pkg/bootstrap/write_test.go | 16 +++++--- pkg/probod/pg_config.go | 40 +++++++++++++++---- pkg/probod/probod.go | 13 +++--- 12 files changed, 101 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index 1d29a41c5..c096c8a7b 100644 --- a/.env.example +++ b/.env.example @@ -42,6 +42,9 @@ # PG_PASSWORD=postgres # PG_DATABASE=probod # PG_POOL_SIZE=100 +# PG_MIN_POOL_SIZE=10 +# PG_MAX_CONN_IDLE_TIME_SECONDS=1800 +# PG_MAX_CONN_LIFETIME_SECONDS=3600 # PG_DEBUG=false # ── Object storage (SeaweedFS via compose) ──────────────────────────── diff --git a/contrib/helm/charts/probo/templates/deployment.yaml b/contrib/helm/charts/probo/templates/deployment.yaml index 5e664987e..e01b0d820 100644 --- a/contrib/helm/charts/probo/templates/deployment.yaml +++ b/contrib/helm/charts/probo/templates/deployment.yaml @@ -87,6 +87,12 @@ spec: value: {{ include "probo.postgresql.database" . | quote }} - name: PG_POOL_SIZE value: {{ .Values.postgresql.poolSize | default "100" | quote }} + - name: PG_MIN_POOL_SIZE + value: {{ .Values.postgresql.minPoolSize | default "10" | quote }} + - name: PG_MAX_CONN_IDLE_TIME_SECONDS + value: {{ .Values.postgresql.maxConnIdleTimeSeconds | default "1800" | quote }} + - name: PG_MAX_CONN_LIFETIME_SECONDS + value: {{ .Values.postgresql.maxConnLifetimeSeconds | default "3600" | quote }} {{- if .Values.postgresql.caBundle }} - name: PG_CA_BUNDLE valueFrom: diff --git a/contrib/helm/charts/probo/values-production.yaml.example b/contrib/helm/charts/probo/values-production.yaml.example index 2b81caa0f..6e1b60660 100644 --- a/contrib/helm/charts/probo/values-production.yaml.example +++ b/contrib/helm/charts/probo/values-production.yaml.example @@ -210,6 +210,11 @@ postgresql: username: "probod" password: "CHANGE_ME_DB_PASSWORD" poolSize: 200 + # Keep 20 connections warm to absorb bursts without paying the + # TLS + PostgreSQL startup handshake on each acquire. + minPoolSize: 20 + maxConnIdleTimeSeconds: 1800 + maxConnLifetimeSeconds: 3600 # Uncomment if using TLS with custom CA # caCertBundle: | # -----BEGIN CERTIFICATE----- diff --git a/contrib/helm/charts/probo/values.yaml b/contrib/helm/charts/probo/values.yaml index 2f9cf3d0b..c76f65a26 100644 --- a/contrib/helm/charts/probo/values.yaml +++ b/contrib/helm/charts/probo/values.yaml @@ -301,6 +301,16 @@ postgresql: password: "" # REQUIRED when enabled=false: PostgreSQL password database: probod poolSize: 100 + # Minimum number of warm connections kept in the pool. Raising this + # value avoids TLS + PostgreSQL startup handshake latency on bursty + # workloads (visible as spikes on pgxpool_acquire_duration_seconds). + minPoolSize: 10 + # Maximum time a connection can be idle before being destroyed, in + # seconds. Default 1800 (30 minutes) matches pgx defaults. + maxConnIdleTimeSeconds: 1800 + # Maximum lifetime of a connection before being recycled, in + # seconds. Default 3600 (1 hour) matches pgx defaults. + maxConnLifetimeSeconds: 3600 # PostgreSQL TLS/SSL configuration # caBundle: | # -----BEGIN CERTIFICATE----- diff --git a/e2e/internal/testutil/testutil.go b/e2e/internal/testutil/testutil.go index 8dc197e59..c78bc22d2 100644 --- a/e2e/internal/testutil/testutil.go +++ b/e2e/internal/testutil/testutil.go @@ -263,8 +263,9 @@ func generateConfig() (string, error) { "API_CORS_ALLOWED_ORIGINS": "http://localhost:18080", // PG. - "PG_DATABASE": "probod_test", - "PG_POOL_SIZE": "10", + "PG_DATABASE": "probod_test", + "PG_POOL_SIZE": "10", + "PG_MIN_POOL_SIZE": "1", // Auth. "AUTH_COOKIE_SECURE": "false", diff --git a/go.mod b/go.mod index 6da4b1415..6fb2f8e17 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/vikstrous/dataloadgen v0.0.10 github.com/yuin/goldmark v1.4.13 go.gearno.de/crypto/uuid v0.1.1-0.20251208105319-3f587312a712 - go.gearno.de/kit v0.6.0 + go.gearno.de/kit v0.7.0 go.gearno.de/x/ref v0.0.0-20260216110753-a700c951377c go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/trace v1.43.0 diff --git a/go.sum b/go.sum index 96c99da6b..67b5f8c4d 100644 --- a/go.sum +++ b/go.sum @@ -334,8 +334,8 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.gearno.de/crypto/uuid v0.1.1-0.20251208105319-3f587312a712 h1:J5ccbcxFuwxe6Oa9fVi9FqQOo+n17ni4wbl9t4NuEzc= go.gearno.de/crypto/uuid v0.1.1-0.20251208105319-3f587312a712/go.mod h1:fnIIvKO9QnsyLO3ZJLJT3r8KZv/p0FOeT5eZKilYWXg= -go.gearno.de/kit v0.6.0 h1:2O7Gdi8DCt6sWUKXDq+SJPHjNnV7N+pYHpsBMhfFqmU= -go.gearno.de/kit v0.6.0/go.mod h1:jWrI/mxd0F4GZApL0HgMextcEQoiy2YA1JVamSA/G0E= +go.gearno.de/kit v0.7.0 h1:0UfrkWmiqpbI4FImWEc/eVBkYD8dMMVshmWtSSiRqJY= +go.gearno.de/kit v0.7.0/go.mod h1:jWrI/mxd0F4GZApL0HgMextcEQoiy2YA1JVamSA/G0E= go.gearno.de/x/panicf v0.1.1 h1:E3Cr9NB8Ry2EsvEG/1eHr7kplP3tEjTf5d56dTX64VQ= go.gearno.de/x/panicf v0.1.1/go.mod h1:VnB8oF0UefMZcYeD4v+Wk4U5Z1uza7PHLlhT2CbNEbU= go.gearno.de/x/ref v0.0.0-20260216110753-a700c951377c h1:rIVWwnNxHYu9aZhHkptXlNYTBJbY4ccaIAYjztVeaDc= diff --git a/pkg/bootstrap/builder.go b/pkg/bootstrap/builder.go index 097e3f21d..256c4b0f6 100644 --- a/pkg/bootstrap/builder.go +++ b/pkg/bootstrap/builder.go @@ -81,13 +81,16 @@ func (b *Builder) Build() (*probod.FullConfig, error) { ExtraHeaderFields: make(map[string]string), }, Pg: probod.PgConfig{ - Addr: b.getEnvOrDefault("PG_ADDR", "localhost:5432"), - Username: b.getEnvOrDefault("PG_USERNAME", "postgres"), - Password: b.getEnvOrDefault("PG_PASSWORD", "postgres"), - Database: b.getEnvOrDefault("PG_DATABASE", "probod"), - PoolSize: int32(b.getEnvIntOrDefault("PG_POOL_SIZE", 100)), - CACertBundle: pgCACertBundle, - Debug: b.getEnvBoolOrDefault("PG_DEBUG", false), + Addr: b.getEnvOrDefault("PG_ADDR", "localhost:5432"), + Username: b.getEnvOrDefault("PG_USERNAME", "postgres"), + Password: b.getEnvOrDefault("PG_PASSWORD", "postgres"), + Database: b.getEnvOrDefault("PG_DATABASE", "probod"), + PoolSize: int32(b.getEnvIntOrDefault("PG_POOL_SIZE", 100)), + MinPoolSize: int32(b.getEnvIntOrDefault("PG_MIN_POOL_SIZE", 10)), + MaxConnIdleTimeSeconds: b.getEnvIntOrDefault("PG_MAX_CONN_IDLE_TIME_SECONDS", 1800), + MaxConnLifetimeSeconds: b.getEnvIntOrDefault("PG_MAX_CONN_LIFETIME_SECONDS", 3600), + CACertBundle: pgCACertBundle, + Debug: b.getEnvBoolOrDefault("PG_DEBUG", false), }, Auth: probod.AuthConfig{ DisableSignup: b.getEnvBoolOrDefault("AUTH_DISABLE_SIGNUP", false), diff --git a/pkg/bootstrap/builder_test.go b/pkg/bootstrap/builder_test.go index e72976063..3bf2f2e9b 100644 --- a/pkg/bootstrap/builder_test.go +++ b/pkg/bootstrap/builder_test.go @@ -131,6 +131,9 @@ func TestBuilder_Build_Defaults(t *testing.T) { assert.Equal(t, "postgres", cfg.Probod.Pg.Password) assert.Equal(t, "probod", cfg.Probod.Pg.Database) assert.Equal(t, int32(100), cfg.Probod.Pg.PoolSize) + assert.Equal(t, int32(10), cfg.Probod.Pg.MinPoolSize) + assert.Equal(t, 1800, cfg.Probod.Pg.MaxConnIdleTimeSeconds) + assert.Equal(t, 3600, cfg.Probod.Pg.MaxConnLifetimeSeconds) assert.False(t, cfg.Probod.Pg.Debug) // Auth config @@ -233,6 +236,9 @@ func TestBuilder_Build_CustomValues(t *testing.T) { env["PG_PASSWORD"] = "secret123" env["PG_DATABASE"] = "probo_prod" env["PG_POOL_SIZE"] = "200" + env["PG_MIN_POOL_SIZE"] = "25" + env["PG_MAX_CONN_IDLE_TIME_SECONDS"] = "900" + env["PG_MAX_CONN_LIFETIME_SECONDS"] = "7200" env["PG_DEBUG"] = "true" // Auth env["AUTH_DISABLE_SIGNUP"] = "true" @@ -310,6 +316,9 @@ func TestBuilder_Build_CustomValues(t *testing.T) { assert.Equal(t, "secret123", cfg.Probod.Pg.Password) assert.Equal(t, "probo_prod", cfg.Probod.Pg.Database) assert.Equal(t, int32(200), cfg.Probod.Pg.PoolSize) + assert.Equal(t, int32(25), cfg.Probod.Pg.MinPoolSize) + assert.Equal(t, 900, cfg.Probod.Pg.MaxConnIdleTimeSeconds) + assert.Equal(t, 7200, cfg.Probod.Pg.MaxConnLifetimeSeconds) assert.True(t, cfg.Probod.Pg.Debug) // Auth assert.True(t, cfg.Probod.Auth.DisableSignup) diff --git a/pkg/bootstrap/write_test.go b/pkg/bootstrap/write_test.go index 8ac5b990f..b36da763e 100644 --- a/pkg/bootstrap/write_test.go +++ b/pkg/bootstrap/write_test.go @@ -111,11 +111,14 @@ func TestWriteConfig_CompleteConfig(t *testing.T) { ExtraHeaderFields: map[string]string{}, }, Pg: probod.PgConfig{ - Addr: "localhost:5432", - Username: "postgres", - Password: "postgres", - Database: "probod", - PoolSize: 100, + Addr: "localhost:5432", + Username: "postgres", + Password: "postgres", + Database: "probod", + PoolSize: 100, + MinPoolSize: 10, + MaxConnIdleTimeSeconds: 1800, + MaxConnLifetimeSeconds: 3600, }, Connectors: []probod.ConnectorConfig{ { @@ -147,6 +150,9 @@ func TestWriteConfig_CompleteConfig(t *testing.T) { assert.Equal(t, cfg.Unit.Tracing.MaxBatchSize, loaded.Unit.Tracing.MaxBatchSize) assert.Equal(t, cfg.Probod.Api.Cors.AllowedOrigins, loaded.Probod.Api.Cors.AllowedOrigins) assert.Equal(t, cfg.Probod.Pg.PoolSize, loaded.Probod.Pg.PoolSize) + assert.Equal(t, cfg.Probod.Pg.MinPoolSize, loaded.Probod.Pg.MinPoolSize) + assert.Equal(t, cfg.Probod.Pg.MaxConnIdleTimeSeconds, loaded.Probod.Pg.MaxConnIdleTimeSeconds) + assert.Equal(t, cfg.Probod.Pg.MaxConnLifetimeSeconds, loaded.Probod.Pg.MaxConnLifetimeSeconds) require.Len(t, loaded.Probod.Connectors, 1) assert.Equal(t, "SLACK", loaded.Probod.Connectors[0].Provider) } diff --git a/pkg/probod/pg_config.go b/pkg/probod/pg_config.go index cbaa9bf1b..54832cb6f 100644 --- a/pkg/probod/pg_config.go +++ b/pkg/probod/pg_config.go @@ -17,18 +17,22 @@ package probod import ( "crypto/x509" "encoding/pem" + "time" "go.gearno.de/kit/pg" ) type PgConfig struct { - Addr string `json:"addr"` - Username string `json:"username"` - Password string `json:"password"` - Database string `json:"database"` - PoolSize int32 `json:"pool-size"` - CACertBundle string `json:"ca-cert-bundle"` - Debug bool `json:"debug"` + Addr string `json:"addr"` + Username string `json:"username"` + Password string `json:"password"` + Database string `json:"database"` + PoolSize int32 `json:"pool-size"` + MinPoolSize int32 `json:"min-pool-size"` + MaxConnIdleTimeSeconds int `json:"max-conn-idle-time-seconds"` + MaxConnLifetimeSeconds int `json:"max-conn-lifetime-seconds"` + CACertBundle string `json:"ca-cert-bundle"` + Debug bool `json:"debug"` } func (cfg PgConfig) Options(options ...pg.Option) []pg.Option { @@ -40,6 +44,28 @@ func (cfg PgConfig) Options(options ...pg.Option) []pg.Option { pg.WithPoolSize(cfg.PoolSize), } + if cfg.MinPoolSize > 0 { + opts = append(opts, pg.WithMinPoolSize(cfg.MinPoolSize)) + } + + if cfg.MaxConnIdleTimeSeconds > 0 { + opts = append( + opts, + pg.WithMaxConnIdleTime( + time.Duration(cfg.MaxConnIdleTimeSeconds)*time.Second, + ), + ) + } + + if cfg.MaxConnLifetimeSeconds > 0 { + opts = append( + opts, + pg.WithMaxConnLifetime( + time.Duration(cfg.MaxConnLifetimeSeconds)*time.Second, + ), + ) + } + if cfg.Debug { opts = append(opts, pg.WithDebug()) } diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go index 28e4e9437..b3b66d56f 100644 --- a/pkg/probod/probod.go +++ b/pkg/probod/probod.go @@ -152,11 +152,14 @@ func New() *Implm { Addr: "localhost:8080", }, Pg: PgConfig{ - Addr: "localhost:5432", - Username: "postgres", - Password: "postgres", - Database: "probod", - PoolSize: 100, + Addr: "localhost:5432", + Username: "postgres", + Password: "postgres", + Database: "probod", + PoolSize: 100, + MinPoolSize: 10, + MaxConnIdleTimeSeconds: 1800, + MaxConnLifetimeSeconds: 3600, }, ChromeDPAddr: "localhost:9222", Auth: AuthConfig{