guardian / guardian/giant

[EPIC] WORKSPACES: PERFORMANCE IMPROVEMENTS

Open
#369 8 comments 0 reactions 0 assignees View on GitHub
discuss ux/ui workspaces
Dominant language
Scala
Stars
60
Forks
4
Avg merge
6d 17h
Merged PRs (30d)
18

Description

Very large workspaces (tens to hundreds of thousands of items) load slowly and frequently time out within the expected 60 seconds the client waits to start receiving the workspace JSON. Even though the download can take tens of seconds, the biggest cost is server-side: the entire tree is fetched from Neo4j, assembled in Scala, serialised as one unbounded JSON response (25–35 MB at 100k items), and the frontend re-fetches the whole thing every 60s while anything in the workspace is processing. Nothing is cached, paginated, or compressed.

The workspace payload mixes two things with very different change profiles:

- **structure** (tree shape, names, parents — only changes on mutations), and
- **processing status** (`Processing`/`Completed`/`Failed`, only meaningful for files, churns constantly during ingestion).

Today they're produced and served together, so we pay the cost of the volatile thing every time we want the stable one. **Splitting them apart is the central proposed change** and subsumes most of the bottlenecks below.

**Bottlenecks in the current code:**

1. Per-row `Resource` lookups for TODO + `EXTRACTION_FAILURE` in `getWorkspaceContents` ([Neo4jAnnotations.scala](backend/app/services/annotations/Neo4jAnnotations.scala#L111-L116)) — done for folders too, even though only files have processing state.
2. No lazy loading — `get`/`getContents` always return the full tree ([Workspaces.scala](backend/app/controllers/api/Workspaces.scala#L148-L161)); `isExpandable` is hard-coded `false` ([Workspace.scala](backend/app/model/annotations/Workspace.scala#L138-L141)).
3. No server-side caching or `ETag`s on the workspace endpoints.
4. Frontend polls and re-fetches the entire workspace every 60s during processing ([Workspaces.tsx](frontend/src/js/components/workspace/Workspaces.tsx#L533-L539)).
5. Search-within-folder fetches the whole tree just to extract blob URIs ([SearchContext.scala](backend/app/services/index/SearchContext.scala#L29-L41)).
6. Response body is uncompressed on the wire (covered by #515).

**Priority order:**

| # | Change | Effort | Impact |
|---|---|---|---|
| 1 | Response compression (#515) | Hours | High |
| 2 | Try `CYPHER runtime = parallel` | 1h + load test | Medium-High |
| 3 | Targeted folder blob query for search | ~½–1 day | Medium |
| 4 | **Structure/status split** — separate `/status` endpoint, frontend merges per-file status into the tree | **~1–1.5 weeks** | **Very High** — subsumes bottlenecks 1, 4 and parts of 6 |
| 5 | Workspace `version` counter + HTTP `ETag` on the structural endpoint | ~3–4 days | High |
| 6 | Re-measure; decide whether full lazy loading is still needed | — | — |
| 7 | Lazy tree loading | ~3–4 weeks | Very High — only fundamental fix, gated on #6 |
| 8 | Block-format migration | Conditional | Likely small |

Items 1–3 are a quick sprint. Item 4 is the headline change. Item 7 stays on the roadmap but is gated on measurements after the split, compression and caching land — those together may make the un-paginated tree fast enough that lazy loading isn't worth its cost.

See the comment below for the full per-bottleneck analysis, the structure/status split design, Neo4j 2026 opportunities, and a realistic difficulty breakdown of lazy loading.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.