From 599df6274e060cc23496508785b11ae271ea1c61 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 24 Oct 2026 11:19:38 +0300 Subject: [PATCH] docs: note telemetry cost and OTel sampling env vars --- AGENTS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index b2dde28..cbdf55a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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