Skip to content

fix(celery): celery limits and honest failures: time limits, statement timeout, transient fio errors keep retrying, task health sees swallowed failures - #113

Merged
jplacht merged 3 commits into
mainfrom
fix/bcr-09-celery-limits
Nov 3, 2026
Merged

jplacht merged 3 commits into
mainfrom
fix/bcr-09-celery-limits

Conversation

@jplacht

@jplacht jplacht commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

What and why

No task had a time limit and Postgres had no statement_timeout, so one hung task or query could stop the single-concurrency worker (every email, webhook and FIO refresh). FIO outages counted toward the retry cap, so after ~2.5 h of FIO trouble every active user was failed until an admin reset. Tasks that catch their error and return False looked healthy on the Task health page. This bounds every task and query, lets FIO outages heal on their own, and makes Task health tell the truth.

Changes

Findings: PERF-06, PERF-09, READ-01, PERF-08, PERF-18, PERF-20

  • Every task gets soft 120 s / hard 150 s time limits; gamedata_admin_import gets 2 h. The plots import stops at its soft limit instead of counting it as one failed planet.
  • Postgres statement_timeout from SERVICE_TYPE: web (django) 15 s, worker/beat (celery) 120 s, migrations (manage, set in entrypoint.sh) none. Every service must keep SERVICE_TYPE set (docker-compose does; Procfile now does for worker/beat).
  • Transient FIO errors (transport, 429, 5xx) no longer count toward MAX_RETRIES; they back off by the time since the last success, 15 min..2 h. A 401 still fails at once.
  • Task health records a task returning False as a failure (returned False). Tasks whose False wasn't a failure now return True/None (refresh_planet with nothing due, user refresh skips, warm_market_summary); the FIO webhook task returns False after logging.
  • expires on periodic tasks below their prod beat interval (measured in Axiom); full imports bulk_create in batches of 1000.
  • Verification/reset/changed emails retry 3 times (30/60/120 s) and log email_send_failed once; dead Celery config (CELERY_WORKER_CONCURRENCY, acks_late/typo ignore_results on cxpc) removed.

Task names, beat schedules, log events and fields are unchanged; no API, model or migration change.

How it was verified

  • QA: PASS (code review, plus a full walk in the e2e Docker stack), 2 rounds.
  • uv run ruff check, ruff format --check, ty check, pytest (with coverage) all pass, before and after merging current main (bcr-07, no conflicts).
  • Postgres-only statement_timeout tests run on Postgres 16 in Docker: SHOW statement_timeout is 15s, a query past the timeout is cancelled.
  • In the e2e stack: the worker resolves 120/150 s (admin import 7200/7500 s); web DB sessions show 15s, the worker's 2min. With FIO blocked, gamedata_refresh_exchanges shows failing on Task health (returned False) and ok again once FIO is reachable; 12 blocked user FIO refreshes leave the row retrying with the error count still 0.
  • Not covered by a test: a task actually being interrupted at its soft limit. A prefork worker inside pytest crashes on macOS and didn't enforce the limit on Linux, so AC1 is covered by tests of the settings and each task's resolved limits.

Deploy

Backend only. Make sure each service in the deployment sets SERVICE_TYPE (django for the web, celery for worker and beat); a missing value means the web's 15 s.

🤖 Generated with Claude Code

jplacht and others added 3 commits November 3, 2026 12:44
…t timeout, transient fio errors keep retrying, task health sees swallowed failures

Findings: PERF-06, PERF-09, READ-01, PERF-08, PERF-18, PERF-20

- Every task gets soft 120 s / hard 150 s time limits; admin imports 2 h. The plots import stops at its soft limit.
- Postgres statement_timeout by SERVICE_TYPE: web 15 s, worker/beat 120 s, migrations none.
- Transient FIO errors (transport, 429, 5xx) no longer count toward MAX_RETRIES and back off 15 min..2 h; 401 still fails.
- Task health records a task returning False as a failure; non-failure False returns became True/None.
- expires on periodic tasks below their beat interval; full imports bulk_create in batches of 1000.
- Email sends retry 3 times with backoff and log once; dead Celery config removed.

Spec: specs/2026-11-02-bcr-09-celery-limits.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI points CELERY_RESULT_BACKEND at a dummy URL, and an eager apply() with a retry stores
its result there. Test send_with_retry directly with a fake bound task instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · -2 duplication

Metric Results
Complexity 0
Duplication -2

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@jplacht
jplacht merged commit 16d3289 into main Nov 3, 2026
9 checks passed
@jplacht
jplacht deleted the fix/bcr-09-celery-limits branch November 3, 2026 12:28
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