API Performance¶
Test: 1 July 2026 · one user · six months of data (200 factories, 4,000 machines, 5,000 tasks)
Results¶
| # | Screen | Before | After | Status |
|---|---|---|---|---|
| 1 | Task list | 437 ms | 23 ms | Fixed |
| 2 | User signup (50 people at once) | 67 s | 1.25 s | Fixed |
| 3 | Factory list | 345 ms | 24 ms | Fixed (target 30 ms) |
| 4 | Machine list | 351 ms | 320 ms | Fixed |
| 5 | Notifications (heavy use) | 10 ms | 4 ms | Fixed |
| 6 | Factory mind map | blocks event loop | 0.79 s max under 6 concurrent renders | Fixed |
Task list under load (1 user, 1 minute test): 417 ms → 60 ms.
What was wrong¶
| Screen | Problem |
|---|---|
| Task list | Server queried the database separately for every row (notes, factory, assignees). |
| User signup | Password + QR generation froze the app for everyone else. |
| Factory list | Sends modules, areas, categories the list screen never shows. |
| Machine list | Counts every machine on every page open. |
| Notifications | Loads all history, no date limit. |
| Mind map | The async endpoint called synchronous Graphviz rendering directly on the event loop. Fixed 2026-07-01: a bounded async semaphore (MINDMAP_RENDER_CONCURRENCY, default 1) gates render slots; token validation, DB reads, and Graphviz PDF work run in Starlette's threadpool only after a slot is acquired, so queued requests no longer hold database sessions. Output uses per-request temporary directories. |
What to do next¶
| Screen | Fix |
|---|---|
| User signup | Fixed — async QR worker; benchmark: scripts/performance/run_signup_benchmark.py |
| Factory list | Fixed — lightweight FactoryListItem schema + selectinload detail query. |
| Machine list | Fixed — cursor pagination without default COUNT(*); exact totals opt-in via include_totals=true. |
| Notifications | Fixed |
| Mind map | Fixed - bounded threadpool render + isolated temporary render directory |
Factory list follow-ups¶
The list latency fix shipped on 30 June 2026. Before closing this item, complete these checkups:
| # | Checkup | Owner | Status |
|---|---|---|---|
| 1 | Confirm with the mobile client that GET /api/factories no longer needs nested relation data (sectors, production_categories, production_areas, modules, sub_modules) in the list payload. Detail remains on GET /api/factories/{id}. |
Mobile + backend | Open |
| 2 | Split get_factory() into a lean lookup (existence / ID checks on write paths) and a detail loader with selectinload for all five M2M relations. Avoid loading relations on quote, task, and calendar validation calls. |
Backend | Open |
| 3 | Add contract tests: list response shape (FactoryListItem scalars only, no relation keys) and pagination headers (X-Total-Count, X-Total-Pages, X-Current-Page). |
Backend | Open |
| 4 | Extend the detail query-efficiency test to seed all five M2M relations, not only sectors, so selectinload regressions on any collection are caught. |
Backend | Open |
| 5 | Decide whether consultants must be restricted on GET /api/factories/{id} to match list behavior (created_by == current_user). Today list filters consultants; detail does not. |
Product + backend | Open |
Run tests: volume-testing.md
Machine list pagination contract (2026-07-01)¶
Default GET /api/machines no longer runs COUNT(*) or returns X-Total-Count / X-Total-Pages.
| Mode | Query param | Response headers |
|---|---|---|
| Default | none | X-Current-Page, X-Has-Next-Page |
| Exact totals | include_totals=true |
above plus X-Total-Count, X-Total-Pages |
T1 benchmark rerun: docs/volume-results/run-20260701-121011/ — machine list p95 351 ms → 320 ms (15 samples, 1 client).
Factory mind map render safety (2026-07-01)¶
Local verification with Graphviz installed (dot 15.0.0, Python graphviz==0.20.3) against uvicorn on port 8002:
| Probe | Result |
|---|---|
| Single real mindmap request | HTTP 200, 165 ms, 146 KiB PDF |
| Six concurrent real mindmap requests | all HTTP 200, max 790 ms |
/api/openapi.json during six mindmap requests |
HTTP 200, 4 ms |
/api/factories?limit=1 during six mindmap requests |
HTTP 200, 17 ms |
The endpoint no longer blocks the event loop or holds database sessions while queued. It still performs CPU and process work, so high-volume PDF generation should remain bounded or move to a job queue if usage grows.
Deployment dependencies¶
| Requirement | Source | Notes |
|---|---|---|
Python graphviz==0.20.3 |
requirements/runtime.txt |
Wrapper only; installed with pip install -r requirements/runtime.txt |
Graphviz dot binary |
OS package | Required at runtime; e.g. apt install graphviz or brew install graphviz |
MINDMAP_RENDER_CONCURRENCY |
Environment | Optional; default 1; caps concurrent mindmap renders per worker process |