Skip to main content

Convex Performance Runbook

Use this document when the admin app feels slower, a dashboard starts flickering under load, or a client reports stale or delayed operational data.

This is not a generic scaling guide.

It is the maintenance runbook for this repo after the Convex data-fetching and hot-path refactor.

Current Baseline​

The repo already has the important foundations in place:

  • cached client-side Convex queries in apps/admin/src
  • stable-query behavior for high-churn admin screens
  • shared session/access provider patterns
  • reduced full-screen loading churn
  • backend hot-path cleanup in:
    • apps/admin/convex/system.ts
    • apps/admin/convex/billing.ts
    • apps/admin/convex/routing.ts

Do not add speculative complexity before there is evidence the current approach is under pressure.

What Counts As A Real Signal​

Investigate when one or more of these becomes true:

  • admin pages feel slow on normal operator workflows, not just cold boot
  • dashboards visibly re-render too often or take too long to settle
  • billing or routing screens lag after filters change
  • Convex functions start reading far more documents or bytes than expected
  • mutations begin retrying due to contention
  • a table has grown enough that previously acceptable indexed .collect() usage becomes expensive

Do not investigate based on vague anxiety alone.

First Checks​

Start here before changing schema or adding denormalized state:

  1. Reproduce the slow path in the admin app.
  2. Check the relevant Convex functions in the dashboard or Insights UI.
  3. Identify whether the issue is:
    • too many subscriptions
    • too many documents read
    • too many bytes read
    • mutation contention
    • repeated N+1 lookups
  4. Confirm the hot path is real in production or staging-like data, not just local noise.

What To Inspect By Area​

Dashboard / Reporting​

Check:

  • apps/admin/convex/system.ts
  • apps/admin/src/features/dashboard/components/dashboard-page.tsx
  • apps/admin/src/features/activity/components/activity-feed-page.tsx

Watch for:

  • broad date-window queries growing too large
  • too many independent live dashboard subscriptions
  • summary queries that should move to snapshot/materialized-summary patterns

Billing​

Check:

  • apps/admin/convex/billing.ts
  • apps/admin/src/features/billing/components/billing-operations-page.tsx

Watch for:

  • invoice history tables growing large enough that status-based scans become costly
  • receivables calculations reading too much invoice history
  • installer-specific delivery-event filtering growing too expensive

Routing​

Check:

  • apps/admin/convex/routing.ts
  • apps/admin/src/features/routing/components/*

Watch for:

  • repeated installer capacity checks becoming hot
  • high assignment churn causing many indexed assignment-count reads
  • contention around routing mutations

Escalation Order​

Use the lightest fix that addresses the real bottleneck.

Level 1​

Do first:

  • tighten indexes
  • replace broad scans with narrower indexed reads
  • batch repeated ctx.db.get(...) lookups
  • move repeated shared reads behind one provider or one wrapper query
  • reduce unnecessary live subscriptions

Level 2​

Do only if Level 1 is not enough:

  • introduce snapshot/materialized-summary queries for dashboard analytics
  • denormalize installer active-load counters
  • denormalize receivables summaries

These are valid patterns, but they add maintenance cost and should be justified by real usage.

Current “Eventually” Items​

These are not required now.

Revisit only if evidence shows they are hot:

  • denormalized active-assignment counters for installer capacity
  • denormalized receivables summaries for billing dashboards
  • more explicit snapshotting for analytics-style dashboard cards

For this product shape, these are scale-stage optimizations, not baseline requirements.

Rules For Future Changes​

  • Do not add raw convex/react usage in admin feature components; use the wrapper layer in apps/admin/src/core/data/client
  • Do not add new dashboard/reporting queries that start with full-table .collect() unless the table is clearly small and expected to stay small
  • Prefer proving a hot path with real measurements before introducing denormalized state
  • When you add a new backend domain with operational dashboards, audit its read shape before shipping

Validation After Any Performance Work​

Run all of these:

  • pnpm --filter @ied/admin typecheck
  • pnpm --filter @ied/admin lint
  • pnpm --filter @ied/admin exec convex dev --once

If the change affects runtime behavior in a visible admin workflow, also click through the affected screen manually.