Files
easyai-ai-gateway/apps/api/internal/store/postgres_config_test.go
T
wangbo 36546bd5c4 perf(postgres): 回收突发连接并等待验收副作用
失败 Run 的未提交任务已取消,但退款 outbox 尚未完成时下一轮会继承 API 连接高水位和 canceled acquire 增量,污染容量验收。

新增 AI_GATEWAY_DATABASE_MAX_CONN_IDLE_SECONDS,生产配置 30 秒回收突发空闲连接;验收中止后等待旧任务、退款、回调和数据库连接全部收敛后才允许新负载启动。

验证:Go 全量测试、gofmt、bash -n、ShellCheck、kubectl kustomize 和 git diff --check 通过。
2026-07-31 03:46:41 +08:00

115 lines
3.7 KiB
Go

package store
import (
"context"
"testing"
"time"
"github.com/jackc/pgx/v5/pgconn"
)
func TestPostgresPoolConfigSetsDiagnosticAndConnectTimeout(t *testing.T) {
config, err := postgresPoolConfig("postgresql://gateway:password@localhost:5432/gateway?sslmode=disable")
if err != nil {
t.Fatalf("parse PostgreSQL pool config: %v", err)
}
if config.ConnConfig.ConnectTimeout != 5*time.Second {
t.Fatalf("connect timeout = %s, want 5s", config.ConnConfig.ConnectTimeout)
}
if applicationName := config.ConnConfig.RuntimeParams["application_name"]; applicationName != "easyai-ai-gateway" {
t.Fatalf("application_name = %q, want easyai-ai-gateway", applicationName)
}
}
func TestPostgresPoolConfigRejectsMalformedURL(t *testing.T) {
if _, err := postgresPoolConfig("://malformed"); err == nil {
t.Fatal("expected malformed PostgreSQL URL to fail")
}
}
func TestPostgresPoolConfigUsesExplicitMinimumIdleConnections(t *testing.T) {
config, err := postgresPoolConfig(
"postgresql://gateway:password@localhost:5432/gateway?sslmode=disable",
24,
)
if err != nil {
t.Fatalf("parse PostgreSQL pool config: %v", err)
}
if config.MaxConns != 24 || config.MinIdleConns != 0 {
t.Fatalf(
"pool bounds max=%d minIdle=%d, want 24/0",
config.MaxConns,
config.MinIdleConns,
)
}
warmed, err := postgresPoolConfigWithOptions(
"postgresql://gateway:password@localhost:5432/gateway?sslmode=disable",
PostgresPoolOptions{MaxConns: 24, MinIdleConns: 4},
)
if err != nil {
t.Fatalf("parse warmed PostgreSQL pool config: %v", err)
}
if warmed.MinIdleConns != 4 {
t.Fatalf("warmed pool minIdle=%d, want 4", warmed.MinIdleConns)
}
}
func TestPostgresPoolConfigSetsBoundedTransactionTimeouts(t *testing.T) {
config, err := postgresPoolConfigWithOptions(
"postgresql://gateway:password@localhost:5432/gateway?sslmode=disable",
PostgresPoolOptions{
MaxConns: 32,
MinIdleConns: 4,
MaxConnIdleTime: 30 * time.Second,
IdleInTransactionTimeout: 60 * time.Second,
LockTimeout: 30 * time.Second,
},
)
if err != nil {
t.Fatalf("parse PostgreSQL pool config: %v", err)
}
if got := config.ConnConfig.RuntimeParams["idle_in_transaction_session_timeout"]; got != "60000ms" {
t.Fatalf("idle transaction timeout = %q, want 60000ms", got)
}
if got := config.ConnConfig.RuntimeParams["lock_timeout"]; got != "30000ms" {
t.Fatalf("lock timeout = %q, want 30000ms", got)
}
if config.MaxConns != 32 || config.MinIdleConns != 4 {
t.Fatalf("pool bounds max=%d minIdle=%d, want 32/4", config.MaxConns, config.MinIdleConns)
}
if config.MaxConnIdleTime != 30*time.Second {
t.Fatalf("pool max idle time=%s, want 30s", config.MaxConnIdleTime)
}
}
func TestIsPostgresUnavailableClassifiesConnectivityFailures(t *testing.T) {
for _, testCase := range []struct {
name string
err error
}{
{name: "deadline", err: context.DeadlineExceeded},
{name: "connection exception", err: &pgconn.PgError{Code: "08006"}},
{name: "too many connections", err: &pgconn.PgError{Code: "53300"}},
{name: "cannot connect now", err: &pgconn.PgError{Code: "57P03"}},
} {
t.Run(testCase.name, func(t *testing.T) {
if !IsPostgresUnavailable(testCase.err) {
t.Fatalf("error %v was not classified as PostgreSQL unavailable", testCase.err)
}
})
}
if IsPostgresUnavailable(&pgconn.PgError{Code: "42601"}) {
t.Fatal("SQL syntax error was incorrectly classified as PostgreSQL unavailable")
}
}
func TestIsPostgresLockTimeout(t *testing.T) {
if !IsPostgresLockTimeout(&pgconn.PgError{Code: "55P03"}) {
t.Fatal("lock timeout was not classified")
}
if IsPostgresLockTimeout(&pgconn.PgError{Code: "57014"}) {
t.Fatal("query cancellation was classified as lock timeout")
}
}