Measurement
GET /api/projects/{id}/timeline on the E2E fixture project — 3 tasks, 4 columns, 1 label:
| condition |
time |
| load average ~8 |
20.0 s (HTTP 200) |
| load average ~12 |
>60 s (curl gave up) |
That is with a fixture project of three tasks. The endpoint is not returning a large payload — the response is a few hundred bytes of task rows.
Why it matters beyond slowness
The Vue view sits on .project-timeline__loading for the whole duration, so in practice the timeline reads as broken rather than slow. The e2e spec waits 8 s for a surface to appear and fails, which is how this was found — tests/e2e/project-timeline.spec.ts:46.
It was masked until now by a 500 from a separate defect (leaked resolution context, fixed in #380). With the 500 gone, the slowness is what remains.
Where to look
TimelineController::forProject() does, per request:
setRegister() + setSchema('project') + find() for the RBAC gate
fetchProjectTasks() — all tasks for the project
fetchProjectDependencies() — dependencies for the task id set
Each goes through OpenRegister's ObjectService with RBAC on. Three tasks should not cost 20 seconds, so the likely candidates are a per-object permission resolution, an unpaginated read that fetches far more than the project's own rows, or an N+1 across the dependency lookup. I have not profiled it and am not asserting which.
Suggested first step
Time the three stages independently before changing anything. If stage 2 or 3 dominates, compare the row count actually fetched against the three tasks the project owns — this programme has already hit one case where a filter on a non-existent property returned every row with HTTP 200, and another where a bare control parameter was read as a property filter.
Measurement
GET /api/projects/{id}/timelineon the E2E fixture project — 3 tasks, 4 columns, 1 label:That is with a fixture project of three tasks. The endpoint is not returning a large payload — the response is a few hundred bytes of task rows.
Why it matters beyond slowness
The Vue view sits on
.project-timeline__loadingfor the whole duration, so in practice the timeline reads as broken rather than slow. The e2e spec waits 8 s for a surface to appear and fails, which is how this was found —tests/e2e/project-timeline.spec.ts:46.It was masked until now by a 500 from a separate defect (leaked resolution context, fixed in #380). With the 500 gone, the slowness is what remains.
Where to look
TimelineController::forProject()does, per request:setRegister()+setSchema('project')+find()for the RBAC gatefetchProjectTasks()— all tasks for the projectfetchProjectDependencies()— dependencies for the task id setEach goes through OpenRegister's
ObjectServicewith RBAC on. Three tasks should not cost 20 seconds, so the likely candidates are a per-object permission resolution, an unpaginated read that fetches far more than the project's own rows, or an N+1 across the dependency lookup. I have not profiled it and am not asserting which.Suggested first step
Time the three stages independently before changing anything. If stage 2 or 3 dominates, compare the row count actually fetched against the three tasks the project owns — this programme has already hit one case where a filter on a non-existent property returned every row with HTTP 200, and another where a bare control parameter was read as a property filter.