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) }, }, {