Skip to content

Latest commit

 

History

History
190 lines (147 loc) · 8.46 KB

15-performance.md

File metadata and controls

190 lines (147 loc) · 8.46 KB

15 — Performance Standards

Status: Active Derives from: ADR 0002 — Initial Architecture (initial budgets in 04-technical-architecture.md § Performance Budgets), ADR 0005 — Live Classroom Media Stack (classroom join + bandwidth budgets). Public-read additions derive from ADR-0052; P02d-5's bounded caller and dynamic/no-store policy derive from ADR-0053.

Performance budgets, the rules that keep them, and the test discipline that protects them.

P02d-7 measurement decision — Accepted 2026-10-11. ADR-0056 defines the browser/Lighthouse measurement contract and hard/advisory split. Its normalized gzip definition covers same-/cross-origin Script responses from cold navigation through collection completion, including initial async/lazy loads and original-document inline executable bodies. It excludes inert data, evaluation-generated code and scripts requested after that window or by later interaction. This can be stricter than entry-chunk/build-time accounting; it is not merely a unit clarification. P7 delivers the always-running public demo + lighthouse job and its fail-closed browser/report/source controls. The hard gzip/CLS/automated-accessibility checks make this standard Active for that delivered scope. Timing medians remain advisory; production baselines, field telemetry and load tests remain Phase 11. The Step 4 record owns real CI receipts and bounded required-check activation.

Initial Budgets

Surface Budget
Public landing page (TTFB) < 200 ms server
Public landing page (LCP) < 1.5 s on fast 4G
Course catalog server response < 300 ms
Lesson player initial load < 500 ms
API p95 (read) < 200 ms
API p95 (write) < 500 ms
API p99 (read) < 500 ms
Classroom join time (token + connect) < 1.5 s
Frontend INP (interaction-to-next-paint) < 200 ms
Frontend CLS (cumulative layout shift) < 0.05
Cold start (worker, after restart) < 5 s

Budgets are reviewed quarterly against measured production metrics.

Backend Rules

Database Access

  • Index every foreign key.
  • Index tenant_id as the first column of composite indexes for tenant-owned tables.
  • Avoid N+1 — projection (Select(...)) preferred over Include chains.
  • Avoid AsTracking for read-only queries.
  • Slow queries (> 500 ms) logged with slow_query=true and reviewed weekly.
  • Each module's hottest read paths must have a covering index.

Caching

  • Read-through cache for stable, read-heavy data except the P02d-4 public API boundary below; renderer-cache choices remain separately owned.
  • Cache invalidation triggered by integration events from the producing module.
  • Cache keys carry the tenant, the organization where applicable, and the locale (Security Standards § Multi-Tenant + Organization Isolation Review Checklist).
  • TTL chosen per content type; default 5 minutes for catalog, 1 minute for course detail.
  • Cache hit ratio per cache name surfaced as a metric.

Customization's internal untranslated definition cache is a generation-driven exception to event/locale invalidation under ADR-0043 and P02d-3's Accepted G22 answer. Its architecture owner requires a fresh SQL generation probe; < 1 ms measures in-memory resolution only, not end-to-end latency. Cold/partial/fault reads use at most two SELECTs. Seeded timings and plans do not establish a production p95.

P02d-4 Accepted exception — 2026-10-03. Step 2 delivers the early public-prefix response policy; Step 3 delivers Education reads and records actual app-role query plans and local HTTP samples in its delivery record. Every /api/v1/public response, including middleware/errors, uses Cache-Control: no-store. No site/Education representation cache, ETag, Last-Modified or 304 is introduced; internal generation-keyed definitions remain independent. Record actual app-role consumer query plans and representative local cold/warm measurements without a production p95 claim. P02d-5/G37 accepts dynamic/no-store public rendering under ADR-0053; no shared Next representation cache is introduced. Internal definition caches remain independent. The accepted package owns that boundary under ADR-0052.

Pagination

  • All list endpoints are paginated as API Standards § Pagination specifies; that section owns the default and maximum limit, the answer to an absent limit, and when offset pagination is allowed.

Background Work

  • Anything > 200 ms server time that does not need to block the response is moved to a Hangfire job.
  • Job duration p95 monitored; jobs > 30 s have a long-running designation and a dedicated queue.
  • Long-running jobs are checkpointable / resumable when possible.

External Calls

  • Every outbound provider call has a timeout (≤ 10 s for synchronous, ≤ 60 s for jobs).
  • Retries with exponential backoff for transient failures.
  • Circuit breaker pattern for provider outages.
  • Outbound calls are not made inside DB transactions.

Memory

  • Avoid loading whole result sets when streaming would do.
  • Stream large file uploads to SeaweedFS/S3; never buffer the whole file in memory.
  • Avoid string concatenation in tight loops; use StringBuilder or pooled buffers.

Frontend Rules

Bundle Size

  • Initial JS payload on a public route: < 200 decimal KB (< 200000 bytes), measured using ADR-0056's normalized source scope.
  • Studio routes may exceed this; budget reviewed per route.
  • Lazy load below-the-fold blocks and modals.
  • Code-split heavy client libraries (rich-text editor, video player) via dynamic(...).

Rendering

  • Server Components by default.
  • Streaming with <Suspense> to ship hero content first.
  • Parallel data fetches in RSC via Promise.all.
  • Avoid sequential request waterfalls.

Images

  • next/image everywhere.
  • Explicit width/height to prevent layout shift.
  • Use modern formats (AVIF, WebP) where the browser supports them.
  • priority for above-the-fold hero images only.

Fonts

  • next/font self-hosted.
  • Subset to the languages actually used.
  • font-display: swap.

Web Vitals

  • Web Vitals collected via the Next.js reporting hook.
  • Dashboards track LCP, INP, CLS, FCP per route.
  • Regression on a critical route is a Sev-2 issue.

Accepted P02d-5 G35. Web-vitals reporting belongs to Phase 11; P02d-5 provides no hook, per ADR-0053. Observability Standards § Frontend Observability records that ownership.

Live Classroom

  • Join time < 1.5 s p95.
  • Token issuance < 200 ms.
  • LiveKit SFU node sized per 12-infrastructure.md — ~250 concurrent participants per 2 vCPU.
  • Egress workers separate from the SFU node.
  • TURN traffic monitored; > 30% sustained triggers a network review.

Load Testing

Required load tests (Phase 11 deliverable):

  • Public landing page at 1k RPS.
  • Course catalog list at 500 RPS.
  • Login + session at 100 RPS.
  • Classroom join at 50 RPS.
  • Recording start/stop at 10 RPS.

Tests run against staging weekly and before a major launch.

Performance Reviews

  • Quarterly performance review meeting.
  • Top 10 slowest endpoints inspected.
  • Top 10 most-expensive queries inspected.
  • Bundle size deltas reviewed.
  • Mobile Core Web Vitals reviewed.

Forbidden

  • Adding a route without a pagination cap.
  • Adding a list endpoint without an index supporting it.
  • Shipping a public route bundle > 250 KB gzipped without an ADR.
  • Calling external providers inside a database transaction.
  • Loading whole tables into memory ("just to filter in code").
  • Building dashboards that query without tenant_id as the first WHERE column.