Skip to content

feat(reliability): configurable DB pool tuning + pre_ping/recycle#929

Open
seonghobae wants to merge 1 commit into
developfrom
feat/db-pool-tuning
Open

feat(reliability): configurable DB pool tuning + pre_ping/recycle#929
seonghobae wants to merge 1 commit into
developfrom
feat/db-pool-tuning

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Gap

Both async engines (db/session.py) were created with no pool configuration (reliability audit #9): default sizing is a silent bottleneck under enterprise concurrency, a restarted/failed-over Postgres surfaces stale pooled connections as user-facing errors, and operators had no knob.

What

  • _engine_kwargs() shared by primary + read-only engines:
    • pool_pre_ping (default on) — validates connections at checkout; a DB restart/failover no longer causes stale-connection 500s.
    • pool_recycle (default 1800s) — replaces aged connections before server-side idle timeouts kill them.
    • DB_POOL_SIZE / DB_MAX_OVERFLOW / DB_POOL_TIMEOUT_SECONDSdefault None → not passed → exact SQLAlchemy defaults (zero sizing change until an operator sets them; also keeps SQLite test pools compatible).

Verification (local — CI frozen org-wide)

  • tests/test_db_session.py: default behavior (pre_ping on, recycle 1800, no sizing kwargs passed) + configured sizing (20/10/5/900/pre_ping off) + existing replica-URL tests updated for the new kwargs → 4 passed.
  • Regressions: test_search + test_emails_api + test_bootstrap_db87 passed.

Notes

This pairs with the HA drill docs (postgresql-physical-replication.md): pre_ping is what makes a replica promotion recoverable without a backend restart.

🤖 Generated with Claude Code

Both async engines were created with no pool configuration: default sizing
under enterprise concurrency is a silent bottleneck, stale pooled connections
surface as user-facing errors after a DB restart/failover, and there was no
operator knob to tune any of it.

- db/session.py: shared _engine_kwargs() for the primary and read-only
  engines. pool_pre_ping (default on) validates connections at checkout;
  pool_recycle (default 1800s) replaces aged connections before server-side
  idle timeouts kill them.
- core/config.py: DB_POOL_SIZE / DB_MAX_OVERFLOW / DB_POOL_TIMEOUT_SECONDS
  default to None and are only passed when set, so unset config keeps exact
  SQLAlchemy defaults and stays compatible with SQLite pools in tests.
- tests/test_db_session.py: extended with default (pre_ping on, no sizing
  passed) and configured-sizing assertions; existing replica-URL tests kept.

Verified locally: test_db_session/test_db 4 passed; search/emails/bootstrap
regressions 87 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RkKdtHRLG4wSLh6PVsp8J
@seonghobae seonghobae enabled auto-merge July 6, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant