Files
HiveGrid-Wireframes/hivegrid-cloud/docs/observability/performance-baseline.md
T

8.0 KiB

Performance Baseline — Phase 02 Quickwins (Task #36)

This document tracks the expected and observed effect of the 8 quickwin optimizations shipped under task #36 (docs/improvement-plan/02-observability/performance-quickwins.md).

Status

  • Code-level changes: shipped to main 2026-05-17 (8 atomic commits + this baseline doc).
  • /metrics endpoint status: code shipped for API and worker runtimes. The API exposes token-protected GET /metrics when METRICS_ENABLED=true; worker admin servers expose token-protected /metrics when enabled and keep health probes on a separate port.
  • Live /metrics baseline: operator-owned. The endpoint code exists, but the live before/after numbers still require an environment with METRICS_ENABLED=true, METRICS_TOKEN, scrape access and a controlled load run.

Per-item expected gains (code-level reasoning)

Item File Mechanism Expected gain
1. N+1 in admin users list apps/web/app/routes/admin/accounts/users.tsx + apps/web/app/utils/db/logs.db.server.ts Replace N findFirst calls with 1 groupBy + 1 findMany DB queries on the admin users-list loader: O(N) → O(1) (101→2 for a 100-user page). Largest practical win on this route.
2. Duplicate tenant fetch apps/web/app/utils/session.server.ts + apps/web/app/routes/admin/accounts/users.tsx + apps/web/app/routes/oauth/github/callback.tsx setLoggedUser now returns defaultTenant; callers reuse it instead of fetching again One fewer getTenant() round-trip per sign-in / impersonation start (≈ 1 DB query/login).
3. S3 client + signer caching apps/api/src/storage.ts Memoize S3 storage adapter, signed-URL adapter and per-day SigV4 signing key by SHA256-of-config Eliminates per-call adapter construction + HMAC chain (4 HMAC-SHA256 ops) on every signed URL. Per-request CPU win in hot upload/download paths.
4. clearSubscriptionsCache thrash apps/web/app/utils/services/.server/subscriptionService.ts + apps/web/app/utils/db/subscriptions/tenantSubscriptionProducts.db.server.ts New clearTenantSubscriptionCache(tenantId) replaces a global wipe in cancelTenantSubscriptionProduct Subscription-change webhooks no longer evict every tenant's cached subscription. Tenant N=1000 → only 1 eviction per event instead of 1000.
5. CSRF header-first check apps/web/app/utils/session.server.ts Already-resolved in commit 83a57f1c; regression test added No additional perf change in #36; documents the historical fix and now-enforced contract: 0 body-clones when the header carries the token.
6. Recovery-code rejection sampling apps/web/app/utils/auth/mfa/recovery-codes.server.ts Drop while(set.size < n) loop; generate batch + max 2 retries Generates 10 codes in one batch (≥99.999% of calls); throws on systemic failure instead of looping forever. Bounds wall-clock generation cost.
7. UUID collision loop apps/api/src/auth/session/session-store.ts while (sessions.has(sessionId)) → single insert; throw on collision 1 ID generation per createSession instead of unbounded retry. UUID-v4 has 122 bits of entropy, so a collision is a systemic error to surface, not mask.
8. CSRF token size apps/web/app/utils/session.server.ts randomBytes(100).toString("base64") → randomBytes(32).toString("hex") Token shrinks ~136 chars → 64 chars. Saves ~70 bytes/cookie and ~70 bytes/response per CSRF-bearing endpoint. Aggregate response-body reduction across the cluster.

Test coverage added

  • apps/web/app/utils/db/logs.db.server.test.ts — getLastUserLogsByUserIds uses exactly 1 groupBy + 1 findMany.
  • apps/web/app/utils/session.server.test.ts — setLoggedUser returns defaultTenant; validateCSRFToken never clones the request body when the header carries the token; generateCSRFToken emits 64 hex chars.
  • apps/api/src/storage.test.ts — repeated config hits the adapter cache; bucket change misses; secret rotation misses.
  • apps/web/app/utils/services/.server/subscriptionService.test.ts — clearTenantSubscriptionCache does not enumerate other tenants; clearAllSubscriptionsCache invalidates all tenant entries.
  • apps/web/app/utils/auth/mfa/recovery-codes.server.test.ts — generation completes under a strict wall-clock bound (regression guard against unbounded rejection sampling).
  • apps/api/src/auth/session/session-store.test.ts — UUID collision now throws instead of silently retrying.

Live baseline (operator-owned)

Populate the table below by running oha -n 5000 -c 50 against staging twice for a comparable before/after window and scraping API/worker /metrics snapshots with Authorization: Bearer <METRICS_TOKEN>. The code path is shipped; the live baseline values are operational evidence and should be recorded only from the target environment.

Metric Before After Delta
p50 latency, GET /admin/users TBD TBD TBD
p95 latency, GET /admin/users TBD TBD TBD
Prisma query count / request (admin users list) TBD TBD TBD
p95 latency, signed-URL endpoint TBD TBD TBD
p95 latency, login flow TBD TBD TBD
Cache evictions per Stripe webhook TBD TBD TBD
CSRF cookie size (bytes) ~136 hex equiv. 64 -53%

Notes

  • All eight items shipped as atomic commits; any single item can be reverted in isolation.
  • Item 5 was already in tree from 83a57f1c fix: enforce csrf on settings mutations. It is recorded here for completeness and now carries an explicit no-body-read assertion.
  • Path corrections were necessary on items 2 and 6 — see the per-item Progress Log entries in docs/improvement-plan/02-observability/performance-quickwins.md.
  • Date: 2026-05-17
  • Owner: Phase-02 quickwins agent
  • Live-test hardware: operator-owned (fill with the target environment used for the controlled load run)