perf: full session list (limit 10000) refetched on every session open #461

Open
opened 2026-07-25 23:35:26 +02:00 by dries · 0 comments
Owner

Code-quality audit (P2). frontend/src/pages/session-detail/SessionDetail.tsx:383-392.

const [sessionIndex, setSessionIndex] = useState<{ rootId: string; sessions: Session[] } | null>(null);
useEffect(() => {
  if (!session?.id) return;
  let cancelled = false;
  api.sessions({ limit: 10_000 }).then((sessions) => {
    if (!cancelled) setSessionIndex({ rootId: session.id, sessions: sessions || [] });
  }).catch(...);
  return () => { cancelled = true; };
}, [session?.id]);

Why it matters

Opening any session fetches up to 10,000 session rows to serve three small needs:

  • one parent lookup (:392),
  • a descendant-id closure (:395-407) — an O(depth x n) fixpoint loop over the whole list,
  • tree stat aggregation (:725).

The full payload is then held in component state for the lifetime of the page. Each of those 10,000 rows is a GetSessions row, which on the backend costs 11 correlated subqueries (see the GetSessions ticket) — so this is the client-side amplifier of that query. Every session open pays it.

Suggested fix

Add GET /api/session/{id}/tree returning just the ancestors and descendants of one session, and have the backend compute the closure in SQL (recursive CTE on parent_id). Failing that, reuse apiStore.cachedSessions instead of refetching, and drop the limit: 10_000.

Acceptance criteria

  • Opening a session no longer fetches the full session list.
  • Parent lookup, descendant closure, and tree stats still work for deep subagent trees.
  • Payload size for a typical session open is bounded by the tree size, not the table size.

Effort: M.

Code-quality audit (P2). `frontend/src/pages/session-detail/SessionDetail.tsx:383-392`. ```js const [sessionIndex, setSessionIndex] = useState<{ rootId: string; sessions: Session[] } | null>(null); useEffect(() => { if (!session?.id) return; let cancelled = false; api.sessions({ limit: 10_000 }).then((sessions) => { if (!cancelled) setSessionIndex({ rootId: session.id, sessions: sessions || [] }); }).catch(...); return () => { cancelled = true; }; }, [session?.id]); ``` ## Why it matters Opening **any** session fetches up to 10,000 session rows to serve three small needs: - one parent lookup (`:392`), - a descendant-id closure (`:395-407`) — an O(depth x n) fixpoint loop over the whole list, - tree stat aggregation (`:725`). The full payload is then held in component state for the lifetime of the page. Each of those 10,000 rows is a `GetSessions` row, which on the backend costs 11 correlated subqueries (see the `GetSessions` ticket) — so this is the client-side amplifier of that query. Every session open pays it. ## Suggested fix Add `GET /api/session/{id}/tree` returning just the ancestors and descendants of one session, and have the backend compute the closure in SQL (recursive CTE on `parent_id`). Failing that, reuse `apiStore.cachedSessions` instead of refetching, and drop the `limit: 10_000`. ## Acceptance criteria - [ ] Opening a session no longer fetches the full session list. - [ ] Parent lookup, descendant closure, and tree stats still work for deep subagent trees. - [ ] Payload size for a typical session open is bounded by the tree size, not the table size. Effort: M.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dries/ocman#461
No description provided.