From 8e5bc934ceaf3fe4592cd61b1a21502603b51957 Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Sat, 7 Mar 2026 10:55:36 -0300 Subject: [PATCH] feat(database): Expose DB connection max idle time configuration Currently, the database connection pool settings allow for configuring max open connections, max idle connections, and max lifetime, but not the max idle time. Expose the `DB_CONN_MAX_IDLE_TIME_MINUTES` environment variable to provide better control over how long idle connections are kept in the pool before being closed. - Add `DBConnMaxIdleTime` to configuration with a default of 5 minutes - Update `internal/app/di.go` to apply the setting to the database pool - Document the new setting in `docs/configuration.md` - Update `.env.example` with the new variable --- .env.example | 1 + .../index.md | 5 ++++ .../metadata.json | 8 ++++++ .../plan.md | 17 +++++++++++ .../spec.md | 28 +++++++++++++++++++ conductor/tracks.md | 2 ++ docs/configuration.md | 5 ++++ internal/app/di.go | 1 + internal/config/config.go | 12 ++++++++ internal/config/config_test.go | 13 +++++---- 10 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 conductor/archive/expose_db_conn_max_idle_time_20260307/index.md create mode 100644 conductor/archive/expose_db_conn_max_idle_time_20260307/metadata.json create mode 100644 conductor/archive/expose_db_conn_max_idle_time_20260307/plan.md create mode 100644 conductor/archive/expose_db_conn_max_idle_time_20260307/spec.md diff --git a/.env.example b/.env.example index e48910f..8b8055b 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,7 @@ DB_CONNECTION_STRING=postgres://user:password@localhost:5432/mydb?sslmode=disabl DB_MAX_OPEN_CONNECTIONS=25 DB_MAX_IDLE_CONNECTIONS=5 DB_CONN_MAX_LIFETIME_MINUTES=5 +DB_CONN_MAX_IDLE_TIME_MINUTES=5 # Server configuration SERVER_HOST=0.0.0.0 diff --git a/conductor/archive/expose_db_conn_max_idle_time_20260307/index.md b/conductor/archive/expose_db_conn_max_idle_time_20260307/index.md new file mode 100644 index 0000000..cb1660a --- /dev/null +++ b/conductor/archive/expose_db_conn_max_idle_time_20260307/index.md @@ -0,0 +1,5 @@ +# Track expose_db_conn_max_idle_time_20260307 Context + +- [Specification](./spec.md) +- [Implementation Plan](./plan.md) +- [Metadata](./metadata.json) diff --git a/conductor/archive/expose_db_conn_max_idle_time_20260307/metadata.json b/conductor/archive/expose_db_conn_max_idle_time_20260307/metadata.json new file mode 100644 index 0000000..ee54f2e --- /dev/null +++ b/conductor/archive/expose_db_conn_max_idle_time_20260307/metadata.json @@ -0,0 +1,8 @@ +{ + "track_id": "expose_db_conn_max_idle_time_20260307", + "type": "feature", + "status": "new", + "created_at": "2026-03-07T00:00:00Z", + "updated_at": "2026-03-07T00:00:00Z", + "description": "Expose DB ConnMaxIdleTime Configuration" +} diff --git a/conductor/archive/expose_db_conn_max_idle_time_20260307/plan.md b/conductor/archive/expose_db_conn_max_idle_time_20260307/plan.md new file mode 100644 index 0000000..8c433c1 --- /dev/null +++ b/conductor/archive/expose_db_conn_max_idle_time_20260307/plan.md @@ -0,0 +1,17 @@ +# Implementation Plan: Expose DB ConnMaxIdleTime Configuration + +## Phase 1: Configuration & Environment [checkpoint: fd1cdc3] +- [x] Task: Update `.env.example` with `DB_CONN_MAX_IDLE_TIME_MINUTES=5` (8385017) +- [x] Task: Update `internal/config/config.go` to include `DefaultDBConnMaxIdleTime` and `DBConnMaxIdleTime` field. (de33982) +- [x] Task: Write failing unit tests in `internal/config/config_test.go` for the new configuration field (Red Phase). (36b3f36) +- [x] Task: Implement `Load()` and `Validate()` in `internal/config/config.go` to support `DB_CONN_MAX_IDLE_TIME_MINUTES` (Green Phase). (c985d84) +- [x] Task: Conductor - User Manual Verification 'Phase 1: Configuration & Environment' (Protocol in workflow.md) (fd1cdc3) + +## Phase 2: Dependency Injection & Integration [checkpoint: 67cebf7] +- [x] Task: Update `internal/app/di.go` to pass `DBConnMaxIdleTime` from the configuration to `database.Connect`. (396da05) +- [x] Task: Add integration or manual verification test to ensure `ConnMaxIdleTime` is correctly applied to the database pool. (0089ec3) +- [x] Task: Conductor - User Manual Verification 'Phase 2: Dependency Injection & Integration' (Protocol in workflow.md) (67cebf7) + +## Phase 3: Documentation [checkpoint: 95eeb98] +- [x] Task: Update `docs/configuration.md` to document the `DB_CONN_MAX_IDLE_TIME_MINUTES` setting. (95ebccc) +- [x] Task: Conductor - User Manual Verification 'Phase 3: Documentation' (Protocol in workflow.md) (95eeb98) diff --git a/conductor/archive/expose_db_conn_max_idle_time_20260307/spec.md b/conductor/archive/expose_db_conn_max_idle_time_20260307/spec.md new file mode 100644 index 0000000..1c842a8 --- /dev/null +++ b/conductor/archive/expose_db_conn_max_idle_time_20260307/spec.md @@ -0,0 +1,28 @@ +# Specification: Expose DB ConnMaxIdleTime Configuration + +## Overview +Currently, the `Secrets` application allows configuring several database connection pool parameters (`DB_MAX_OPEN_CONNECTIONS`, `DB_MAX_IDLE_CONNECTIONS`, `DB_CONN_MAX_LIFETIME_MINUTES`), but it does not expose the `ConnMaxIdleTime` setting, which controls how long a connection can remain idle before being closed. This track will expose this configuration to provide better control over connection management. + +## Functional Requirements +1. **Configuration Structure:** Add `DBConnMaxIdleTime` (of type `time.Duration`) to the `Config` struct in `internal/config/config.go`. +2. **Environment Variable:** Support loading this value from the environment variable `DB_CONN_MAX_IDLE_TIME_MINUTES`. +3. **Default Value:** Set a default value of 5 minutes (`DefaultDBConnMaxIdleTime = 5`). +4. **Dependency Injection:** Update `internal/app/di.go` to pass the `DBConnMaxIdleTime` from the configuration to the `database.Connect` function. +5. **Validation:** Ensure the value is validated (e.g., non-negative). +6. **Documentation:** Update `docs/configuration.md` to include information about the new `DB_CONN_MAX_IDLE_TIME_MINUTES` setting. +7. **Example Environment File:** Update `.env.example` to include the new environment variable with its default value. + +## Non-Functional Requirements +- **Consistency:** Follow the existing naming conventions for database configuration. + +## Acceptance Criteria +- The application correctly loads `DB_CONN_MAX_IDLE_TIME_MINUTES` from the environment. +- The default value of 5 minutes is used if the environment variable is not set. +- The `database.Connect` function receives and applies the configured `ConnMaxIdleTime`. +- Unit tests in `internal/config/config_test.go` verify the new configuration field. +- `docs/configuration.md` correctly reflects the new configuration option. +- `.env.example` includes the new variable. + +## Out of Scope +- Modifying other database pool settings. +- Changing the default values for existing settings. diff --git a/conductor/tracks.md b/conductor/tracks.md index 22d3d64..0b5c54e 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -1,3 +1,5 @@ # Project Tracks This file tracks all major tracks for the project. Each track has its own detailed plan in its respective folder. + +--- diff --git a/docs/configuration.md b/docs/configuration.md index 451b3d6..172226a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -11,6 +11,7 @@ DB_CONNECTION_STRING=postgres://user:password@localhost:5432/mydb?sslmode=disabl DB_MAX_OPEN_CONNECTIONS=25 DB_MAX_IDLE_CONNECTIONS=5 DB_CONN_MAX_LIFETIME_MINUTES=5 +DB_CONN_MAX_IDLE_TIME_MINUTES=5 # Server configuration SERVER_HOST=0.0.0.0 @@ -104,6 +105,10 @@ Maximum number of idle database connections (default: `5`). Maximum lifetime of a connection in minutes (default: `5`). +### DB_CONN_MAX_IDLE_TIME_MINUTES + +Maximum amount of time a connection may be idle in minutes (default: `5`). + ## Server configuration ### SERVER_HOST diff --git a/internal/app/di.go b/internal/app/di.go index ae89ae9..4b882b6 100644 --- a/internal/app/di.go +++ b/internal/app/di.go @@ -340,6 +340,7 @@ func (c *Container) initDB(ctx context.Context) (*sql.DB, error) { MaxOpenConnections: c.config.DBMaxOpenConnections, MaxIdleConnections: c.config.DBMaxIdleConnections, ConnMaxLifetime: c.config.DBConnMaxLifetime, + ConnMaxIdleTime: c.config.DBConnMaxIdleTime, }) if err != nil { return nil, fmt.Errorf("failed to connect to database: %w", err) diff --git a/internal/config/config.go b/internal/config/config.go index e77918e..60dcff1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -26,6 +26,7 @@ const ( DefaultDBMaxIdleConnections = 5 DefaultDBConnMaxLifetime = 5 // minutes + DefaultDBConnMaxIdleTime = 5 // minutes DefaultLogLevel = "info" DefaultAuthTokenExpiration = 14400 // seconds DefaultRateLimitEnabled = true @@ -70,6 +71,8 @@ type Config struct { DBMaxIdleConnections int // DBConnMaxLifetime is the maximum amount of time a connection may be reused. DBConnMaxLifetime time.Duration + // DBConnMaxIdleTime is the maximum amount of time a connection may be idle. + DBConnMaxIdleTime time.Duration // LogLevel is the logging level (e.g., "debug", "info", "warn", "error", "fatal", "panic"). LogLevel string @@ -124,6 +127,10 @@ func (c *Config) Validate() error { c, validation.Field(&c.DBDriver, validation.Required, validation.In("postgres", "mysql")), validation.Field(&c.DBConnectionString, validation.Required), + validation.Field(&c.DBMaxOpenConnections, validation.Min(0)), + validation.Field(&c.DBMaxIdleConnections, validation.Min(0)), + validation.Field(&c.DBConnMaxLifetime, validation.Min(0*time.Second)), + validation.Field(&c.DBConnMaxIdleTime, validation.Min(0*time.Second)), validation.Field(&c.ServerPort, validation.Required, validation.Min(1), validation.Max(65535)), validation.Field( &c.ServerReadTimeout, @@ -220,6 +227,11 @@ func Load() (*Config, error) { DefaultDBConnMaxLifetime, time.Minute, ), + DBConnMaxIdleTime: env.GetDuration( + "DB_CONN_MAX_IDLE_TIME_MINUTES", + DefaultDBConnMaxIdleTime, + time.Minute, + ), // Logging LogLevel: env.GetString("LOG_LEVEL", DefaultLogLevel), diff --git a/internal/config/config_test.go b/internal/config/config_test.go index b47fd62..67f22ba 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -410,6 +410,7 @@ func TestLoad(t *testing.T) { assert.Equal(t, 25, cfg.DBMaxOpenConnections) assert.Equal(t, 5, cfg.DBMaxIdleConnections) assert.Equal(t, 5*time.Minute, cfg.DBConnMaxLifetime) + assert.Equal(t, 5*time.Minute, cfg.DBConnMaxIdleTime) assert.Equal(t, "info", cfg.LogLevel) assert.Equal(t, 14400*time.Second, cfg.AuthTokenExpiration) assert.Equal(t, true, cfg.RateLimitEnabled) @@ -473,11 +474,12 @@ func TestLoad(t *testing.T) { { name: "load custom database configuration", envVars: map[string]string{ - "DB_DRIVER": "mysql", - "DB_CONNECTION_STRING": "user:password@tcp(localhost:3306)/testdb", - "DB_MAX_OPEN_CONNECTIONS": "50", - "DB_MAX_IDLE_CONNECTIONS": "10", - "DB_CONN_MAX_LIFETIME_MINUTES": "10", + "DB_DRIVER": "mysql", + "DB_CONNECTION_STRING": "user:password@tcp(localhost:3306)/testdb", + "DB_MAX_OPEN_CONNECTIONS": "50", + "DB_MAX_IDLE_CONNECTIONS": "10", + "DB_CONN_MAX_LIFETIME_MINUTES": "10", + "DB_CONN_MAX_IDLE_TIME_MINUTES": "10", }, validate: func(t *testing.T, cfg *Config) { assert.Equal(t, "mysql", cfg.DBDriver) @@ -485,6 +487,7 @@ func TestLoad(t *testing.T) { assert.Equal(t, 50, cfg.DBMaxOpenConnections) assert.Equal(t, 10, cfg.DBMaxIdleConnections) assert.Equal(t, 10*time.Minute, cfg.DBConnMaxLifetime) + assert.Equal(t, 10*time.Minute, cfg.DBConnMaxIdleTime) }, }, {