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.