Files
support_backend/specs/016-load-concurrency-testing/data-model.md
T
saqib mirandClaude Sonnet 5 015ef62b71 plan(016-load-concurrency-testing): design assignment/SLA/escalation race fixes
research.md nails down the exact mechanism for each real race the audit
found: a partial unique index for assignment double-assignment, a
Ticket-style version counter for SLA pause/resume/sweep, and a partial
unique index for escalation-rule idempotency — each traced to the specific
repository/service code that has the gap today. data-model.md and plan.md
carry the resulting schema and repository-contract changes; quickstart.md
defines the real-infra verification steps for each user story.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 16:55:32 +05:30

4.0 KiB

Data Model: Load and Concurrency Testing

All changes below are additive to existing models — no existing column is removed or retyped, and no existing consumer (012-admin-list-views, 015-reporting-dashboards) needs any change, since none of them write to Assignment/SLARun/EscalationEvent directly (all writes already go through the repositories being changed here).

SLARun (existing model, one new field)

Field Type Notes
version Int @default(0) NEW. Optimistic-concurrency counter, identical convention to Ticket.version (003-ticketing). Incremented on every successful updateWithVersion call.

Migration: additive ALTER TABLE sla_runs ADD COLUMN version INTEGER NOT NULL DEFAULT 0; — every existing row defaults to 0, which is exactly the version any in-flight or future updateWithVersion call expects for a run nobody has updated since this migration ran.

Assignment (existing model, no column change — one new index)

New raw partial unique index (Prisma schema DSL cannot express a partial predicate directly, so this is added via a raw-SQL migration step, same approach already used elsewhere in this project for Postgres-specific constraints):

CREATE UNIQUE INDEX assignments_one_current_per_ticket
  ON assignments (ticket_id)
  WHERE is_current = true;

Enforces at the database level: a ticket may have at most one Assignment row with isCurrent = true at any moment, closing the race research.md §1 describes. The existing non-unique @@index([ticketId, isCurrent]) is unaffected and stays for the repository's own findCurrent lookup.

EscalationEvent (existing model, no column change — one new index)

CREATE UNIQUE INDEX escalation_events_ticket_rule_unique
  ON escalation_events (ticket_id, rule_id)
  WHERE rule_id IS NOT NULL;

Enforces at the database level: a given rule may fire at most once per ticket over that ticket's lifetime (manual escalations, where rule_id IS NULL, are explicitly excluded and remain repeatable). Closes the race research.md §3 describes.

Repository contract changes

SlaRunRepository

  • update(id, data)replaced by updateWithVersion(id, expectedVersion, data): Promise<SLARun | null>, mirroring TicketsRepository.updateStatus's exact shape: an atomic updateMany({where:{id, version: expectedVersion}, data:{...data, version:{increment:1}}}), returning the fresh row on success (count === 1) or null on a stale-version mismatch. Every existing call site (pause, resume, complete, runBreachDetectionSweep) is updated to pass its own last-read version and to retry (re-read + recompute + re-call) up to 3 times on a null result before giving up silently (matching the sweep's own existing no-throw, best-effort style — these are internal transitions with no HTTP caller waiting on a 409).

AssignmentRepository

  • createAssignment(data) — same signature and return type; internally catches a Prisma P2002 on the new assignments_one_current_per_ticket index and retries the entire transaction (bounded to 3 attempts) before rethrowing.

EscalationEventRepository

  • create(data) — same signature; internally catches a Prisma P2002 on the new escalation_events_ticket_rule_unique index and returns the pre-existing row for that (ticketId, ruleId) pair (a findFirst({where:{ticketId, ruleId}}) fallback) instead of throwing, so EscalationService.fire's caller sees a normal EscalationEvent either way — a duplicate trigger is invisible to the caller, not an error.

Test-only entities (not persisted — in-memory test scaffolding)

  • Load test report (tests/load/): { endpoint: string; connections: number; durationSec: number; requestsPerSec: number; latencyP50Ms: number; latencyP90Ms: number; latencyP99Ms: number; non2xxCount: number; rateLimitedCount: number } — printed to console and written as JSON under tests/load/reports/<endpoint>-<timestamp>.json (gitignored) for each run, satisfying FR-008's separation of rate-limited responses from genuine failures.