Skip to content

performance: optimise timeline scheduling algorithm — hot loop, sort, sequential upserts #178

@NickMonrad

Description

@NickMonrad

Overview

Code review (2026-04-01) found several algorithmic performance issues in the timeline scheduling route.

Findings

1. featureResourceHours() Called Up to 200,000 Times — Critical

File: server/src/routes/timeline.ts:774,814

featureResourceHours() iterates all stories/tasks of a feature to compute a resource hours map. It's called inside the resource-levelling simulation loop (up to 1,000 steps) × per manual feature × per resource type. For 10 manual features × 20 resource types × 1,000 steps = 200,000 calls, each re-iterating tasks.

Fix: Pre-compute featureResourceHours for all features before the simulation loop and cache in a Map.

2. Kahn's Sort O(n² log n) — High

File: server/src/routes/timeline.ts:651

queue.sort() called on every iteration of the while (queue.length > 0) loop. For 500 features: ~125,000 comparisons. A min-heap gives O(F log F) — ~27× faster at 500 features, ~110× at 2,000.

Fix: Replace with a min-heap / priority queue.

3. Feature Timeline Upserts Sequential — Critical

File: server/src/routes/timeline.ts:956-966

for (const fId of processed) { await prisma.timelineEntry.upsert(...) } — sequential DB round trips. Story upserts at line 953 correctly use Promise.all — features should too.

Fix: await Promise.all([...processed].map(fId => prisma.timelineEntry.upsert(...)))

4. computeParallelWarnings Re-queries Already-Loaded Data — High

File: server/src/routes/timeline.ts:338-342

After scheduling finishes (which already loaded the full hierarchy), computeParallelWarnings issues fresh findMany queries for features and resource types already in memory.

Fix: Pass the already-loaded data to computeParallelWarnings instead of re-querying.

5. Story Scheduling O(S²) Per Feature — Medium

File: server/src/routes/timeline.ts:909-940

For each story, the code iterates sibling list from the start to find position. Pre-computing all story positions in one pass per feature reduces to O(S).

Acceptance Criteria

  • featureResourceHours pre-computed and cached before simulation loop
  • Feature upserts use Promise.all
  • computeParallelWarnings accepts pre-loaded data
  • Kahn's sort replaced with priority queue
  • Timeline generation measurably faster on project with 50+ epics

Metadata

Metadata

Assignees

No one assigned

    Labels

    performancePerformance improvement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions