diff options
| author | bndw <ben@bdw.to> | 2026-02-14 09:44:55 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-02-14 09:44:55 -0800 |
| commit | fe3708eaf495613cc6e2340b821795f25811d6ed (patch) | |
| tree | 061f4b1c2938919ab38e0bc840c34d7c35bad42f /internal/config | |
| parent | 688548d4ac3293449a88913275f886fd2e103cdf (diff) | |
fix: optimize SQLite connection pooling for single-writer architecture
Remove misleading max_connections config option and properly configure
SQLite connection pooling in the storage layer.
Changes:
- Set MaxOpenConns(1) for optimal SQLite performance
- Set MaxIdleConns(1) to keep connection alive
- Set ConnMaxLifetime(0) to never close connection
- Remove max_connections and max_lifetime from DatabaseConfig
- Update docs to clarify SQLite's single-writer architecture
Rationale:
SQLite is an embedded database with a single-writer lock. Multiple
connections cause lock contention and reduce performance. WAL mode
allows concurrent reads from the same connection, making connection
pooling unnecessary and counterproductive.
This change makes the configuration clearer and ensures optimal
SQLite performance by using a single long-lived connection.
Diffstat (limited to 'internal/config')
| -rw-r--r-- | internal/config/README.md | 9 | ||||
| -rw-r--r-- | internal/config/config.go | 16 | ||||
| -rw-r--r-- | internal/config/config_test.go | 5 |
3 files changed, 8 insertions, 22 deletions
diff --git a/internal/config/README.md b/internal/config/README.md index 87d6fa1..79e1b89 100644 --- a/internal/config/README.md +++ b/internal/config/README.md | |||
| @@ -75,11 +75,8 @@ database: | |||
| 75 | # Path to SQLite database file | 75 | # Path to SQLite database file |
| 76 | path: "relay.db" | 76 | path: "relay.db" |
| 77 | 77 | ||
| 78 | # Maximum number of open connections | 78 | # Note: Connection pooling is automatically configured for SQLite. |
| 79 | max_connections: 10 | 79 | # SQLite uses a single connection for optimal performance. |
| 80 | |||
| 81 | # Connection max lifetime | ||
| 82 | max_lifetime: "1h" | ||
| 83 | 80 | ||
| 84 | # Authentication configuration | 81 | # Authentication configuration |
| 85 | auth: | 82 | auth: |
| @@ -264,8 +261,6 @@ Server: | |||
| 264 | 261 | ||
| 265 | Database: | 262 | Database: |
| 266 | Path: "relay.db" | 263 | Path: "relay.db" |
| 267 | MaxConnections: 10 | ||
| 268 | MaxLifetime: 1h | ||
| 269 | 264 | ||
| 270 | Auth: | 265 | Auth: |
| 271 | Enabled: false | 266 | Enabled: false |
diff --git a/internal/config/config.go b/internal/config/config.go index 87ca4eb..91e79f7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go | |||
| @@ -31,9 +31,9 @@ type ServerConfig struct { | |||
| 31 | 31 | ||
| 32 | // DatabaseConfig holds database configuration. | 32 | // DatabaseConfig holds database configuration. |
| 33 | type DatabaseConfig struct { | 33 | type DatabaseConfig struct { |
| 34 | Path string `yaml:"path"` | 34 | Path string `yaml:"path"` |
| 35 | MaxConnections int `yaml:"max_connections"` | 35 | // Note: SQLite connection pooling is handled internally in the storage layer. |
| 36 | MaxLifetime time.Duration `yaml:"max_lifetime"` | 36 | // SQLite works best with a single connection due to its single-writer architecture. |
| 37 | } | 37 | } |
| 38 | 38 | ||
| 39 | // AuthConfig holds authentication configuration. | 39 | // AuthConfig holds authentication configuration. |
| @@ -106,9 +106,7 @@ func Default() *Config { | |||
| 106 | WriteTimeout: 30 * time.Second, | 106 | WriteTimeout: 30 * time.Second, |
| 107 | }, | 107 | }, |
| 108 | Database: DatabaseConfig{ | 108 | Database: DatabaseConfig{ |
| 109 | Path: "relay.db", | 109 | Path: "relay.db", |
| 110 | MaxConnections: 10, | ||
| 111 | MaxLifetime: 1 * time.Hour, | ||
| 112 | }, | 110 | }, |
| 113 | Auth: AuthConfig{ | 111 | Auth: AuthConfig{ |
| 114 | Enabled: false, | 112 | Enabled: false, |
| @@ -239,12 +237,6 @@ func applyEnvOverrides(cfg *Config) { | |||
| 239 | if val := os.Getenv("MUXSTR_DATABASE_PATH"); val != "" { | 237 | if val := os.Getenv("MUXSTR_DATABASE_PATH"); val != "" { |
| 240 | cfg.Database.Path = val | 238 | cfg.Database.Path = val |
| 241 | } | 239 | } |
| 242 | if val := os.Getenv("MUXSTR_DATABASE_MAX_CONNECTIONS"); val != "" { | ||
| 243 | var n int | ||
| 244 | if _, err := fmt.Sscanf(val, "%d", &n); err == nil { | ||
| 245 | cfg.Database.MaxConnections = n | ||
| 246 | } | ||
| 247 | } | ||
| 248 | 240 | ||
| 249 | // Auth | 241 | // Auth |
| 250 | if val := os.Getenv("MUXSTR_AUTH_ENABLED"); val != "" { | 242 | if val := os.Getenv("MUXSTR_AUTH_ENABLED"); val != "" { |
diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 50d9b67..e1df1aa 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go | |||
| @@ -257,7 +257,6 @@ server: | |||
| 257 | 257 | ||
| 258 | database: | 258 | database: |
| 259 | path: "test.db" | 259 | path: "test.db" |
| 260 | max_lifetime: "30m" | ||
| 261 | 260 | ||
| 262 | rate_limit: | 261 | rate_limit: |
| 263 | cleanup_interval: "10m" | 262 | cleanup_interval: "10m" |
| @@ -282,7 +281,7 @@ rate_limit: | |||
| 282 | t.Errorf("expected write timeout 2m, got %v", cfg.Server.WriteTimeout) | 281 | t.Errorf("expected write timeout 2m, got %v", cfg.Server.WriteTimeout) |
| 283 | } | 282 | } |
| 284 | 283 | ||
| 285 | if cfg.Database.MaxLifetime != 30*time.Minute { | 284 | if cfg.RateLimit.CleanupInterval != 10*time.Minute { |
| 286 | t.Errorf("expected max lifetime 30m, got %v", cfg.Database.MaxLifetime) | 285 | t.Errorf("expected cleanup interval 10m, got %v", cfg.RateLimit.CleanupInterval) |
| 287 | } | 286 | } |
| 288 | } | 287 | } |
