Slow work runs inside requests, and on SQLite every request holds the write lock while it waits #220
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#220
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
One slow request can stall the whole instance.
django-tasks-dbis configured (config/settings/base.py:499-503) and nothing enqueues anything. Found in the 2026-09-15 code audit.Why one request blocks everyone
ATOMIC_REQUESTSis on (config/settings/base.py:122), and SQLite runs withtransaction_mode: IMMEDIATE(config/sqlite.py:37).non_atomic_requestsappears nowhere.sqlite.pydocstring says the wait is "milliseconds", which is only true for fast requests.The requests that are not fast
CaptureCreateView.post→fetch_page, with a 10 s timeout plus 5 s for robots.txt (plugins/fetching.py:44-46).CompanyLogoActionViewfetches remote images.ReportPDFView,CVExportViewandSendDocumentsrender in the request.pdf.pyalso launches a new Chromium for every document.export_downloadbuilds a zip of every file the account owns, in memory (core/views_export.py:33). The export and delete-account pages build the whole export just to show counts (views_export.py:16,accounts/views.py:360).api/api.py:290-312callsnotify()synchronously, so a batch from the extension waits on every notifier's network timeout.A single capture from a slow job board, or one CV PDF on a Raspberry Pi, holds up the other gunicorn workers, the scheduler and
db_worker. Anything that waits past the 20 s busy timeout fails with "database is locked" (the same shape as #206). Gunicorn's default 30 s--timeoutcan also kill a worker mid-render (Dockerfile:256-260).Proposal
non_atomic_requestsand wrap only their writes in shorttransaction.atomic()blocks. Consider making plain GETs non-atomic on SQLite.django-tasks-dband show a "working…" state that htmx polls:count()queries, notbuild_document.GUNICORN_CMD_ARGSand default to--timeout 120 --max-requests 500 --max-requests-jitter 50.django-tasks-db, and show a "working…" state #247