oto_ingest_shed_total
| Type | counter |
| Labels | reason — pool_exhausted, in_flight, queue_depth |
| Registered in | internal/ingestion/service/metrics.go, incremented in internal/ingestion/api/shed.go |
| Alertable | yes, on a sustained rate |
| Rule | OtoIngestShedding in deploy/prometheus/oto-rules.yaml |
What it counts
Section titled “What it counts”Requests answered 503 with a Retry-After as deliberate backpressure.
Shedding is a feature (C17, ADR 0007), not a failure. Alertmanager retries 5xx — and only 5xx — for about five minutes, so a 503 is a designed and sufficient backpressure channel. A 429 would be a 4xx, and Alertmanager deletes a notification permanently and silently on any 4xx, which is why oto has no rate limiter on this path.
So: a brief spike during a storm is the system working. A sustained rate is spending the upstream’s retry budget on queueing, and once it is spent the notification is gone.
The reasons, and what each one actually says
Section titled “The reasons, and what each one actually says”reason |
What was full | First thing to look at |
|---|---|---|
queue_depth |
the ingest River queue is past MaxQueueDepth (default 25 000) |
the workers, not the database |
pool_exhausted |
the ingest pgx pool has no free connection | Postgres latency, and the ingest queue workers sharing that pool |
in_flight |
the concurrency gate filled and the acquisition budget (ingest.acquire_timeout, 500 ms) elapsed |
the same two, one level up |
The distinction is the point: “we are out of database” and “the workers are behind” have different fixes.
What to check
Section titled “What to check”oto_job_queue_depthforqueue="ingest",state="available"— the 25 000 default is roughly two and a half minutes of backlog at 16 workers, chosen to sit inside Alertmanager’s retry budget.GET /readyz— thepoolsblock reports saturation without scraping/metrics.oto_ingest_process_duration_seconds: are batches slow, or merely numerous?- Postgres:
log_min_duration_statementis 500 ms in the dev compose; look for the statement that got slow, and for lock waits.
What to do
Section titled “What to do”queue_depth: addingestworkers or pods. Do not simply raiseMaxQueueDepth— that converts “shed early enough to be retried” into “accept work the upstream will give up on”, which turns a 202 into a promise oto cannot keep.pool_exhausted: raiseOTO_DB_INGEST_SHARE_PERCENTor the total pool, and checkOTO_DB_INGEST_STATEMENT_TIMEOUTis still being honoured. Remember the accept handler and theingestqueue workers share this pool.in_flight:MaxInFlightshould equal the ingest pool size. Setting it higher moves the queue into pgx where it is invisible and unmeasured.- Never respond by returning 429 or 4xx from this path.