Skip to content

perf(frontend): stop serialising independent requests during app init - #321

Open
HAAHIT wants to merge 1 commit into
mainfrom
perf/appstate-init-parallel
Open

perf(frontend): stop serialising independent requests during app init#321
HAAHIT wants to merge 1 commit into
mainfrom
perf/appstate-init-parallel

Conversation

@HAAHIT

@HAAHIT HAAHIT commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

User description

Last of the fixes from the slow-account-creation investigation. This is the one that multiplied the backend latency into the delay users actually felt.

Problem

appState.init() ran four requests strictly one after another, each waiting on the full round trip of the one before:

/api/workspaces → /api/workspaces/invites/me → /api/state → /api/schema

Only two of those links are real dependencies. From the nginx access log after a signup, the calls landed 3, 6, and 7 seconds apart:

10:53:10  POST /api/auth/verify-email
10:53:13  GET  /api/workspaces
10:53:19  GET  /api/workspaces/invites/me
10:53:21  GET  /api/state

Whatever the per-request backend cost is, this chain paid it four times in series.

Fix 1 — the two workspace calls are independent

/api/workspaces/invites/me depends only on get_current_user:

@router.get("/api/workspaces/invites/me")
async def get_invites(user=Depends(get_current_user)):

It needs nothing from the workspace list. The old code even issued it before activeWorkspace was assigned, so it could never have been using it. Promise.all now runs both together — one round trip saved on every app load, not just after signup.

Fix 2 — don't block navigation on the schema

/api/schema is no longer awaited before navigating. It only feeds the chat UI, which already handles its absence:

  • realSchema = $state<SchemaTable[] | null>(null) — initialised null
  • AppShell.svelte defaults the prop: realSchema = null

So the next screen renders fine without it and fills in when it arrives. Awaiting it held up both isLoaded and the redirect, leaving the user on a loading screen waiting for data that screen did not need in order to appear.

It now loads in the background via the existing fetchSchemaAsync() helper rather than a second copy of that logic.

Result

The serial chain drops from four round trips to two, and the second no longer blocks the redirect.

Verification — and its limits

  • svelte-check --threshold error: 0 errors
  • production build (npm run build, adapter-static): succeeds
  • dependency analysis above, confirmed against the backend route and the component prop defaults

I want to be straight about what I did not do: I could not capture an authenticated browser trace showing the two requests overlapping, because init() bails at /api/auth/me 401 before reaching them and I did not want to create an account to get past that. There is also no frontend test runner in package.json to assert the call ordering. So the concurrency claim rests on the code and the dependency analysis, not on a measured before/after like the backend PRs in this series. Worth a reviewer's eye on that specific point.

🤖 Generated with Claude Code


CodeAnt-AI Description

Speed up app startup by parallelizing workspace loading and background-loading chat schema data

What Changed

  • Workspace and invitation data now loads at the same time instead of in sequence
  • Navigation and setup screens appear without waiting for chat schema data
  • Chat schema continues loading in the background and becomes available when ready

Impact

✅ Faster sign-in and app startup
✅ Shorter loading screens before navigation
✅ Chat remains available while schema data loads

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Signing in ran four requests strictly one after another, each waiting on the
full round trip of the one before:

    /api/workspaces -> /api/workspaces/invites/me -> /api/state -> /api/schema

Only two of those links are real dependencies. Two fixes:

/api/workspaces and /api/workspaces/invites/me are independent. The invites
endpoint depends only on get_current_user and needs nothing from the workspace
list -- the old code even issued it before activeWorkspace was assigned, so it
could never have been using it. Awaiting them in sequence cost a full extra
round trip on every app load. Promise.all now runs them together.

/api/schema is no longer awaited before navigating. It only feeds the chat UI,
which already declares realSchema as SchemaTable[] | null, initialises it to
null, and whose components default the prop to null -- so the next screen
renders fine without it and fills in when it arrives. Awaiting it held up both
isLoaded and the redirect, leaving the user on a loading screen waiting for
data that screen did not need. It now loads in the background through the
existing fetchSchemaAsync helper rather than a second copy of that logic.

The serial chain drops from four round trips to two, and the second of those
no longer blocks the redirect.

svelte-check reports 0 errors and the production build succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@HAAHIT
HAAHIT requested a review from Entropy-rgb as a code owner August 3, 2026 16:01
@codeant-ai

codeant-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 3108d43 Aug 03, 2026 · 16:01 16:03

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@HAAHIT, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fc5bba8c-4d91-4b0d-9510-cf26f994a73c

📥 Commits

Reviewing files that changed from the base of the PR and between 5aebabc and 3108d43.

📒 Files selected for processing (1)
  • frontend/src/lib/appState.svelte.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codeant-ai codeant-ai Bot added the size:S This PR changes 10-29 lines, ignoring generated files label Aug 3, 2026
Comment on lines +76 to +79
const [workspaces, invites] = await Promise.all([
apiCall("/api/workspaces"),
apiCall("/api/workspaces/invites/me"),
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The combined promise makes workspace initialization fail as a unit when the independent invitations request returns an error. Because neither result is assigned until both requests resolve, a successful workspace response is discarded and callers can continue with an empty or stale workspaces list and no activeWorkspace. Handle the two requests independently so an invitations failure does not prevent workspace loading. [logic error]

Severity Level: Major ⚠️
- ❌ Workspace navigation can fail when invitation loading errors.
- ⚠️ Successful workspace responses are discarded during partial failures.
- ⚠️ Users may be redirected with no active workspace.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** frontend/src/lib/appState.svelte.ts
**Line:** 76:79
**Comment:**
	*Logic Error: The combined promise makes workspace initialization fail as a unit when the independent invitations request returns an error. Because neither result is assigned until both requests resolve, a successful workspace response is discarded and callers can continue with an empty or stale `workspaces` list and no `activeWorkspace`. Handle the two requests independently so an invitations failure does not prevent workspace loading.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

// Awaiting it here held up isLoaded and the redirect below for a whole
// round trip, so the user sat on a loading screen waiting for data
// that the next screen does not need in order to appear.
void this.fetchSchemaAsync();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Starting the schema request without awaiting or associating it with the current database allows its response to update realSchema after the user switches databases or logs out. fetchSchemaAsync() assigns the response unconditionally, so an older request can overwrite the newly selected database's schema or repopulate schema after logout. Track the request generation or database/session identity and ignore responses that no longer match the current state. [stale reference]

Severity Level: Major ⚠️
- ❌ Database switching can display the wrong schema.
- ⚠️ Sidebar schema data may describe the previous database.
- ⚠️ Logout can be followed by stale schema state restoration.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** frontend/src/lib/appState.svelte.ts
**Line:** 181:181
**Comment:**
	*Stale Reference: Starting the schema request without awaiting or associating it with the current database allows its response to update `realSchema` after the user switches databases or logs out. `fetchSchemaAsync()` assigns the response unconditionally, so an older request can overwrite the newly selected database's schema or repopulate schema after logout. Track the request generation or database/session identity and ignore responses that no longer match the current state.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S This PR changes 10-29 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant