Skip to main content

Observability & On-call

This page implements §13 of the architecture: OpenTelemetry for traces, metrics and logs; Grafana (Tempo, Mimir/Prometheus, Loki) for storage, dashboards and alerting; Sentry for errors; synthetic checks per cell.

1. Telemetry design​

1.1 Standard attributes​

Set once in the tenant-context middleware and job wrapper, so every span, log line and error carries them.

AttributeExampleOn tracesOn logsOn metrics
service.name, service.versionspyrl-api, git SHAYesYesYes
deployment.environmentprodYesYesYes
cell.idp1YesYesYes
process.roleapi, worker, ingestYesYesYes
tenant.idUUIDYesYesOnly on a small set of metrics, for the top-N tenants (cardinality control)
tenant.planstandardYesYesYes (low cardinality)
spyrl.modulepayYesYesYes
user.role_hashshort hashYesNoNo
job.queue, job.namepayroll, pay.run.calculateYesYesYes
request.idULIDYesYesNo

User IDs appear in traces as opaque UUIDs only; names, emails and phone numbers never do.

1.2 Tenant-aware metrics without cardinality explosions​

  • RED metrics (rate, errors, duration) per route template and module, labelled by cell.id and tenant.plan, not by tenant.id.
  • A per-tenant aggregation job in the collector (or a recording rule) emits tenant_requests_total, tenant_errors_total and tenant_p95_seconds for the top 50 tenants by traffic per cell, plus any tenant on a watch list (a new Enterprise tenant, a tenant with an open SEV).
  • For per-tenant questions about the long tail, query logs and traces (which carry tenant.id), not metrics.
  • Business metrics per tenant (payroll run status, punches per day, ingest lag) are computed in the database and pushed to the control plane health scores, not into Prometheus.

1.3 PII redaction​

Defence in depth, because a single leaked log line with salary or Aadhaar data is a reportable incident.

LayerControl
CodeDomain objects with sensitive fields implement toLog() that masks them; a lint rule flags logging of whole request bodies
Logger (pino)redact paths for password, token, authorization, cookie, pan, aadhaar, account_number, ifsc, salary, ctc, gross, net, bank, face, otp, lat, lng (coordinates rounded to 2 decimals if logged at all)
HTTP instrumentationRequest and response bodies are not captured; query strings are stripped of known sensitive parameters
Collectorattributes and transform processors drop or hash any attribute matching sensitive key patterns; a regex scrubber for PAN-like and Aadhaar-like values in log bodies, as a last line
SentrybeforeSend scrubbing with the same key list; "send default PII" off; breadcrumbs exclude form values; session replay disabled
RetentionApplication logs 30 days, traces 14 days, metrics 13 months (aggregates), Sentry 90 days. Security and access logs (auth events, audit exports, ALB/CloudFront/WAF access logs, CloudTrail, VPC flow logs) are kept 1 year in an ap-south-1 S3 bucket: at least 180 days for the CERT-In direction and one year for the DPDP Rules, verify (security, privacy)
ResidencyPick an India-hosted region for the observability backend if the vendor offers one, or self-host the Grafana stack in ap-south-1; redaction makes residual personal data minimal either way. Verify the vendor region before go-live
AccessGrafana and Sentry via SSO, MFA; production logs readable by engineers on the on-call rota and the security lead only

A weekly job samples logs for PAN/Aadhaar/IFSC patterns; any hit is a SEV3 security ticket, and a SEV2 if real data is found.

2. SLOs, SLIs and error budgets​

Targets are from architecture §13. Windows are rolling 28 days.

SLOSLI (how it is measured)Target Stage ATarget Stage BMonthly error budget (Stage A / B)
Availability (web + API)Share of non-5xx responses for user-facing routes, plus synthetic login checks, per cell99.5%99.9%~3.6 h / ~43 min
API read latencyShare of GET requests under 300 ms at p95 per route classp95 under 300 mssame5% of 5-minute windows may breach
API write latencyShare of mutating requests under 800 ms at p95p95 under 800 mssame5% of windows
Punch acceptanceTime from punch request to 202 Accepted and from device push to stored punchp95 under 2 ssame1% of punches above 5 s
Payroll computeDuration of pay.run.calculate normalised to 1,000 employeesunder 5 minsame2 slow runs per month per cell
Notification latencyOutbox insert → provider acceptedp95 under 60 ssame1% above 5 min
Payslip availabilityPayslips published within 1 h of run lock99%99.5%—

Error-budget policy

  • Budget above 50%: normal release train.
  • Budget 25–50%: releases continue; the next sprint includes at least one reliability item from the top error sources.
  • Budget under 25%: wave 1 and wave 2 rollouts pause except fixes; release manager and engineering lead review.
  • Budget exhausted: feature freeze for the affected cell until the budget recovers or a review accepts the risk.
  • The rollout orchestrator reads burn rates directly and refuses to advance a wave if the cell burns faster than 2× the budget rate during the soak (upgrades).

3. Alert catalogue​

Alerts page only for user-visible impact or imminent data risk. Everything else goes to the ops channel or a ticket. Severity matches the incident levels in backup and DR.

AlertConditionSeverityRunbook
AvailabilityFastBurn5xx/availability burn rate over 14× budget for 5 min and over 14× for 1 h (multi-window)SEV1/SEV2 pagerb/availability
AvailabilitySlowBurnBurn rate over 3× for 6 hSEV3 ticketrb/availability
SyntheticLoginFailingLogin or punch synthetic fails 3 consecutive runs in a cellSEV2 pagerb/synthetics
LatencyP95Highp95 over 2× SLO for 15 min on read or write classSEV3, SEV2 if during payroll windowrb/latency
DBCPUHighRDS CPU over 85% for 15 minSEV2 pagerb/db-cpu
DBConnectionsSaturatedPgBouncer waiting clients over 50 for 5 min, or RDS connections over 90% of maxSEV2 pagerb/db-connections
DBStorageLowFree storage under 15% or projected full within 7 daysSEV3; SEV2 under 5%rb/db-storage
DBReplicaLagRead-replica lag over 60 s for 10 minSEV3rb/replica-lag
RLSDeniedrls_denied_total over 0 for a write path, or unexpected spike on readsSEV2 page (possible isolation bug)rb/rls-denied
CrossTenantGuardApp guard detects a payload tenant_id different from the contextSEV1 pagerb/cross-tenant
QueueBacklogOldOldest job age over 10 min in notifications, or over 30 min in payroll, attendanceSEV2 page in payroll window, else SEV3rb/queues
DLQGrowingAny DLQ over 100 jobs or growing for 30 minSEV3rb/dlq
PayrollRunFailedpay.run.calculate failed after retries for any tenantSEV2 page (payroll window), else SEV3rb/payroll-run
IngestLagDevice or GPS ingest lag over 5 minSEV2rb/ingest
EmailBounceRateHighSES bounce rate over 4% or complaint rate over 0.08% (account-level risk)SEV2rb/ses-reputation
SmsProviderErrorsSMS/WhatsApp provider error rate over 10% for 10 minSEV3, SEV2 if OTPrb/sms-failover
FaceProviderDegradedFace verify timeouts over 5% for 10 minSEV3 (punches fall back to unverified)rb/face-provider
CertificateExpiringAny TLS certificate under 14 days to expirySEV3; SEV2 under 3 daysrb/certificates
BackupMissingNo successful snapshot or cross-region copy in 26 hSEV2rb/backups
RestoreVerifyFailedWeekly automated restore test failedSEV3rb/restore-verify
ErrorSpikeNewReleaseNew Sentry issue with over 50 events in 30 min after a deploySEV3; halts the rollout waverb/release-halt
ControlPlaneDownControl-plane API synthetic fails 3 runsSEV2 (cells continue on cached lookups)rb/control-plane
WAFBlockSpikeWAF blocks over 10× baseline for 15 minSEV3, security reviewrb/waf
AuthFailureSpikeFailed logins over 10× baseline for a tenant or IP rangeSEV3, SEV2 if credential stuffing confirmedrb/auth-abuse
NodeDiskOrMemory (Stage A)EC2 disk over 85% or memory over 90%SEV3rb/stage-a-node

Runbooks live in docs/runbooks/ in the main repository, one file per rb/ key, each with: symptoms, dashboards to open, likely causes, safe mitigations (for example a kill switch), escalation, and how to verify recovery. An alert without a runbook cannot be merged.

4. Dashboards​

DashboardAudiencePanels
Platform overviewOn-call, leadershipAvailability and burn per cell, active incidents, release wave status, top errors
Cell healthOn-callRED by module, DB CPU/IOPS/connections, PgBouncer, Redis, queue depth and age per queue, ingest lag, node or task health
Payroll windowPayroll SME, on-call (25th–5th)Runs in progress and failed by tenant, compute duration, payslip publishing, notification backlog, bank and statutory file generation errors
Tenant drill-downSupport, customer successFor one tenant: request rate, errors, slow routes, failed jobs, connector health, last logins (from logs and traces filtered by tenant.id)
IntegrationsOn-callProvider latency and error rates (SES, MSG91, Expo, Rekognition, maps), connector failures by key
SecuritySecurity leadAuth failures, MFA challenges, WAF blocks, RLS denials, support sessions active, privileged actions
MobileMobile teamCrash-free sessions per version, app versions in use vs the minimum, offline queue depth reported on sync, OTA adoption
CapacityEngineering leadSection 5 trigger metrics with distance to threshold

5. Capacity triggers​

Reviewed monthly after the payroll window, and alerting at 80% of each threshold.

TriggerThreshold (architecture §13 and §2.1)Action
DB CPU at monthly peakOver 60% sustainedScale the instance class; if already large, plan a new pooled cell
DB sizeOver 500 GBNew pooled cell for new tenants; archive old GPS partitions
Tenants per cellOver 300New pooled cell
Active employees per cellOver 30kNew pooled cell
Neighbour impactA tenant's p95 regression attributable to another tenantTighten that tenant's quotas; move the heavy tenant to its own or a lighter cell
GPS ingestOver ~200 writes/s sustainedExtract ingest as a separate service (architecture §2.1)
Payroll computeLargest tenant's run over 10 minutesExtract the payroll-compute worker; parallelise by employee batches
Stage A host toilOver ~4 h/week of host operations, or more than 50 tenants / 10k employeesStart the Stage A → B migration (deployment)

6. On-call process​

ItemPolicy
RotaWeekly primary and secondary from engineers who have completed on-call onboarding (shadow one week, reverse-shadow one week). Hand-off Monday 11:00 IST with a written summary
Hours24×7 paging for SEV1/SEV2. SEV3 handled in business hours (09:00–19:00 IST, Monday–Saturday, matching Indian SME working days)
ResponseAcknowledge a page within 15 min (SEV1) or 30 min (SEV2). If primary does not acknowledge in 15 min, secondary is paged; after 30 min, the engineering lead
Payroll window25th–5th: secondary is also on heightened alert during 09:00–21:00 IST, and a payroll SME is reachable
ToolsGrafana alerting → a paging tool (for example Grafana OnCall or PagerDuty) → phone and app; incident channel per incident; status page
LoadTarget under 2 pages per week outside business hours. More than that for 2 weeks triggers an alert-quality review
CompensationOn-call allowance plus time off in lieu after night pages (team policy, set by leadership)
AccessOn-call has production read access, the kill switches, and rollout pause. Data changes in production need a second person (four-eyes) except during a declared SEV1
After the shiftReview every page: was it actionable, was the runbook good enough? Fix or delete noisy alerts