Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Python is 3.14, dependencies managed by `uv`. The API is exposed on `:8000`.

**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 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 answers anything not caused by a pool timeout the way Litestar's default handler would: the debug page when `SERVICE_DEBUG` is on, a plain 500 otherwise. `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.

**Telemetry cost**: OpenTelemetry is on when `OPENTELEMETRY_ENDPOINT` is set, Sentry when `SENTRY_DSN` is set, and both run on every request. On this template with one Granian worker, each adds about 0.3 ms of CPU per request, and the two together cut read throughput by about 40%. OTel traces every request by default. To sample, set `OTEL_TRACES_SAMPLER=parentbased_traceidratio` and `OTEL_TRACES_SAMPLER_ARG` to the ratio; lite-bootstrap leaves the sampler unset, so the OTel SDK reads these. A ratio of 0.1 removes about half of OTel's cost, because unsampled requests skip the span exporter and most span work but still go through the ASGI middleware and the SQLAlchemy hooks. Sentry runs with `traces_sample_rate` unset, yet its ASGI and Litestar integrations build an unsampled transaction and a span per middleware on every request, so turning off its other default integrations saves little.

**Use cases and lost connections**: `pool_pre_ping` is off by default (`DB_POOL_PRE_PING`). A pooled connection that died while idle is handled by `db_retry.postgres_retry` instead, which saves a round trip on every checkout. That only works if the retried function owns its whole transaction, so every use case has this shape:

```python
Expand Down
Loading