From aa8ec5a527736ae29b7a8afecff540377fc12a13 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Thu, 22 Oct 2026 07:59:52 +0300 Subject: [PATCH 1/2] fix: default DB_POOL_TIMEOUT to 5 s --- AGENTS.md | 2 +- app/settings.py | 2 +- tests/test_settings.py | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e4ac333..72a6a4a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ Endpoints inject a use case with `FromDI(UseCase)` from `modern_di_fastapi`, cal ### Pool and server sizing -`DB_POOL_SIZE` (positive) and `DB_MAX_OVERFLOW` (non-negative) size the pool, and a request that finds it empty waits for a connection. Granian's `backpressure` is left at its default: it limits open connections per worker, idle keep-alive ones included, so setting it to the pool size makes new connections wait while the app is idle. `APP_WORKERS` sets the Granian worker count, and each worker has its own pool. `DB_POOL_TIMEOUT` (default 30 s) bounds the wait for a connection, and a timeout returns 503 through `exceptions.database_unavailable_handler`. advanced-alchemy wraps the timeout in a `RepositoryError`, so the handler is also registered for `RepositoryError` and re-raises anything not caused by a pool timeout. `DB_POOL_RECYCLE` defaults to SQLAlchemy's `-1` (never). Set it below the idle timeout of any proxy or load balancer in front of Postgres, so a connection it dropped is not handed out and retried after a 1-2 s backoff. +`DB_POOL_SIZE` (positive) and `DB_MAX_OVERFLOW` (non-negative) size the pool, and a request that finds it empty waits for a connection. Granian's `backpressure` is left at its default: it limits open connections per worker, idle keep-alive ones included, so setting it to the pool size makes new connections wait while the app is idle. `APP_WORKERS` sets the Granian worker count, and each worker has its own pool. `DB_POOL_TIMEOUT` (default 5 s) bounds the wait for a connection, and a timeout returns 503 through `exceptions.database_unavailable_handler`. advanced-alchemy wraps the timeout in a `RepositoryError`, so the handler is also registered for `RepositoryError` and re-raises anything not caused by a pool timeout. `DB_POOL_RECYCLE` defaults to SQLAlchemy's `-1` (never). Set it below the idle timeout of any proxy or load balancer in front of Postgres, so a connection it dropped is not handed out and retried after a 1-2 s backoff. ### Use cases and lost connections diff --git a/app/settings.py b/app/settings.py index 0b69f56..7c80993 100644 --- a/app/settings.py +++ b/app/settings.py @@ -19,7 +19,7 @@ class Settings(pydantic_settings.BaseSettings): db_pool_size: pydantic.PositiveInt = 5 db_max_overflow: pydantic.NonNegativeInt = 0 db_pool_pre_ping: bool = False - db_pool_timeout: float = 30 + db_pool_timeout: float = 5 db_pool_recycle: int = -1 app_host: str = "0.0.0.0" # noqa: S104 diff --git a/tests/test_settings.py b/tests/test_settings.py index 83ae543..b197b51 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -20,6 +20,10 @@ def test_pool_pre_ping_is_off_by_default() -> None: assert Settings().db_pool_pre_ping is False +def test_pool_timeout_defaults_to_five_seconds() -> None: + assert Settings().db_pool_timeout == 5 # noqa: PLR2004 + + def test_log_level_is_case_insensitive_and_reaches_app_logging() -> None: custom = Settings(log_level="WARNING") From 260e3be1ed8ffc7305434e497edfa111fb1119cd Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Thu, 22 Oct 2026 08:19:54 +0300 Subject: [PATCH 2/2] test: drop default pool timeout test --- tests/test_settings.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_settings.py b/tests/test_settings.py index b197b51..83ae543 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -20,10 +20,6 @@ def test_pool_pre_ping_is_off_by_default() -> None: assert Settings().db_pool_pre_ping is False -def test_pool_timeout_defaults_to_five_seconds() -> None: - assert Settings().db_pool_timeout == 5 # noqa: PLR2004 - - def test_log_level_is_case_insensitive_and_reaches_app_logging() -> None: custom = Settings(log_level="WARNING")