labs/zava-learning/sre-config/agent-config/skills/performance-investigation/SKILL.md
Use for Zava Learning incidents where the platform is reachable but a compute tier is degraded — elevated 5xx from the APIs, slow quiz responses, exceptions, an API with no healthy instances, or a back-office batch job failing on the reporting-worker VM (e.g. nightly grade exports). Diagnoses from Application Insights / Log Analytics / Syslog and remediates the application or worker tier.
npx skillsauth add microsoft/sre-agent performance-investigationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Resource Group: @@RG@@. Services (Container Apps): learner-portal, course-api, assessment-api.
Quiz launch path: portal -> assessment-api -> course-api.
Confirm the network/edge is healthy first (App Gateway backend healthy, NSG clean) so you don't misattribute an app fault. Then:
cloud_RoleName.az containerapp revision list shows only ACTIVE revisions, so a
deactivated lane looks like it has "no revisions" — list with --all and inspect
properties.active / properties.latestRevisionName. The latent cause is often a scale floor of
zero in IaC (appLaneMinReplicas: 0); the durable fix restores a non-zero minimum.ms=<duration>), find the step-change in latency and align it to the most recent assessment-api revision/image deployment, then inspect src/assessment-api/server.js for expensive synchronous work added on the request path (e.g. a synchronous KDF/crypto call). This is a code regression — mitigate live by rolling back to the prior revision, then fix durably with a code PR.If the symptom is the nightly grade-export job failing (no quiz/portal impact — this is a back-office
batch job on a dedicated VM vm-zava-reporting-*):
Syslog for ProcessName == "zava-export" — the worker logs a heartbeat on
success and a FAILED line plus the raw OS error (export write error: ...) on failure. Read the
raw error to identify the failure mode.Perf table carries Logical Disk % Used Space and
Free Megabytes for the worker. A data volume (/data) at ~100% used / near-zero free is the
signal that the export writes are failing for lack of space.az vm run-command invoke ... --scripts "df -h /data")./data/exports) via az vm run-command. Re-check that the next export run logs a success heartbeat.src/reporting-worker/cloud-init.yaml
— a PR that enforces retention (or grows the data disk in infra/modules/vm.bicep) is the lasting fix.The quiz lanes read PostgreSQL on the request path, so a DB fault produces app-shaped symptoms on a
clean edge + healthy replicas. Do NOT stop at "the DB looks slow" and report root cause unknown —
identify WHICH database surface — log-first, confirm from the lane's error detail, then optionally
verify in the database:
ContainerAppConsoleLogs_CL for the failing quiz-* app /
assessment-api) for the request shape: a slow ms=<duration> on /quiz/* → index/latency;
a burst of status=500 on /quiz/* (and any pool error: lines) → a backend DB failure.detail field (it is NOT written to the logs): curl the lane's /health
(returns 503 {"status":"degraded","detail":"<pg error>"} when the DB path is broken) or
/quiz/<courseId> (500 {"error":"...","detail":"<pg error>"}). The detail names the cause
directly — e.g. password authentication failed (28P01) → bad DB secret; too many connections
/ remaining connection slots / connection-limit → pool exhaustion; a slow-but-200 /quiz with
climbing ms= → missing index. This needs no DB credentials and is the primary confirmation.az postgres flexible-server list, admin password from Key
Vault db-password, then az postgres flexible-server execute --querytext "<SQL>"):
zava_query): check the question_bank indexes —
SELECT indexname FROM pg_indexes WHERE tablename='question_bank'; and
SELECT relname,seq_scan,idx_scan FROM pg_stat_user_tables WHERE relname='question_bank';.
A missing index on question_bank with seq-scans climbing is the root cause; the durable fix is
to (re)build it (REINDEX/CREATE INDEX) — delivered as a PR.zava): check the connecting role's connection limit —
SELECT rolname,rolconnlimit FROM pg_roles WHERE rolname='app_pool'; and live connections in
pg_stat_activity. A rolconnlimit of 1 (or far below the pool size) is the root cause; restore
a sane limit (ALTER ROLE app_pool CONNECTION LIMIT <n>).db-password-secretlane)
was rotated to an invalid value — confirm the auth-failure log signature; the fix is to restore a
valid value INTO db-password-secretlane (copy it from the baseline db-password secret with
az keyvault secret set) and then force the lane to re-read it by creating a NEW revision
(az containerapp update --set-env-vars FORCE_ROTATE=<timestamp>). A plain az containerapp revision restart is NOT enough — Container Apps resolve Key Vault secret references at
revision-creation time, so a restart reuses the cached (still-invalid) value; only a new revision
re-reads the secret. Then re-check the lane returns 200. Do NOT run az containerapp secret set /
re-assert the secret's identityref (unnecessary — the reference is already correct), and do NOT
repoint pg-password to a different Key Vault secret (e.g. the shared db-password) — that masks the
fault and breaks lane isolation; only the db-password-secretlane value should change.az containerapp revision activate) to bring back zero-replica lanes./data).If the root cause is in application source, the app code lives under src/ in @@REPO@@. After
the live mitigation (revision rollback/restart), the durable code fix is delivered as a GitHub PR
by the pr-delivery skill and recorded as a Change Request by servicenow-change-management.
PagerDuty acknowledgement, status/summary notes, and resolution are owned by the
pagerduty-incident-update skill.
Confirm /api/quiz/* returns 200 and the API success rate is back to baseline.
development
Use to package a completed Zava Learning incident analysis for the audience. First present a branded in-thread executive summary (markdown with the before/after visuals inline), then produce the downloadable deliverables — a PowerPoint deck, an HTML email, and a Teams notification — using the Zava corporate template. The calling agent may narrow this to a subset (e.g. only the HTML report). Produces content and artifacts; it does not send them. Assembles the output of rca-analysis, evidence-before-after, recommendations-next-steps, and pr-delivery.
development
Use to package a completed Zava Learning weekly governance audit (NSG / network security, RBAC / least-privilege, or cloud cost) into a single branded, downloadable PowerPoint deck in the Zava house style. The calling audit agent passes its findings (a list of rows with severity) and a short posture summary; this skill renders the deck, applies redaction, and returns the attachment download link. Produces the artifact; it does not send it.
development
Use whenever a Zava Learning investigation produces a durable fix that needs change management — after a GitHub PR is opened for an Infrastructure-as-Code or application code root cause, raise a ServiceNow Change Request referencing the PR and attach the RCA report. The single owner of ServiceNow Change Request and attachment operations.
development
Use whenever you are about to emit operator-visible content — a chat/thread message, a PagerDuty or ServiceNow note, a commit message or pull-request body, or any report artifact (HTML, PowerPoint, Teams card). Deterministically masks secrets, credentials, tokens, private keys, URI-embedded passwords, and PII so they never appear in the thread or in any deliverable. This is a cross-cutting guardrail invoked by the output-producing skills, not a runbook step.