Skip to main content
Audience: On-call engineers, SREs, platform operators Last updated: 2026-02-23 Escalation chain: On-call engineer -> Team lead -> VP Engineering -> CTO

Table of Contents

  1. Decision Latency Spike (>200ms P99)
  2. Worker Backlog (Queue Depth >100)
  3. Database Connection Exhaustion
  4. Redis OOM / Eviction
  5. API 5xx Error Rate >1%
  6. DLQ Overflow (>500)
  7. Certificate Expiry
  8. Node Capacity Exhaustion
  9. Database Disk Full
  10. Auth Failures Spike

General Incident Workflow

  1. Acknowledge the alert within 5 minutes.
  2. Assess severity using the matrix below.
  3. Communicate status in #kaireon-incidents Slack channel.
  4. Mitigate using the relevant playbook.
  5. Resolve and confirm metrics return to normal.
  6. Post-mortem within 48 hours for SEV1/SEV2 incidents.

1. Decision Latency Spike (>200ms P99)

Severity: SEV2 (SEV1 if sustained >10 minutes or >500ms P99)

Detection

  • Alert: kaireon_decision_latency_ms P99 > 200ms for 3 consecutive minutes.
  • Dashboard: Grafana > KaireonAI Decisions > Latency panel.
  • Metrics:

Diagnosis

  1. Identify the bottleneck layer:
  2. Check for resource contention:
  3. Check for slow queries:
  4. Check for model loading delays:

Resolution

  1. Immediate (if >500ms P99): Scale up API replicas:
  2. If database-bound: Kill long-running queries and investigate:
  3. If Redis-bound: Flush stale caches:
  4. If scoring-bound: identify the slow model via kaireon_scoring_latency_ms (and check kaireon_scoring_model_failure_total for failing models), then add API capacity per step 1. Scoring has no runtime env-var toggle; tune the model configuration in the Algorithms module instead.

Prevention

  • Set HPA target CPU to 60% (not 80%) for headroom.
  • Enable scoring result caching with a 30-second TTL for repeat customer lookups.
  • Run weekly load tests against the decision endpoint with production-like traffic.
  • Maintain database query performance baselines; alert on 50% regression.
  • Pre-warm model caches on pod startup using an init container.

2. Worker Backlog (Queue Depth >100)

Severity: SEV2 (SEV1 if depth >500 or growing >50/min)

Detection

  • Alert: kaireon_active_worker_jobs > 100 for 5 minutes.
  • Dashboard: Grafana > KaireonAI Workers > Queue Depth panel.
  • Metrics:
  • Queue names: batch-jobs, dsar-jobs, journey-jobs, retrain-jobs, seed-jobs (the five BullMQ queues). Decision requests via /api/v1/recommend are served synchronously and do not flow through a worker queue.

Diagnosis

  1. Determine which queue is backed up:
  2. Check worker health:
  3. Check if workers are stuck on a specific job:
  4. Check for upstream rate changes:

Resolution

  1. Scale workers immediately:
  2. If workers are crash-looping, restart them:
  3. If a single queue is stuck, isolate it: the worker has no per-queue skip env var. Pause the offending queue via BullMQ tooling (e.g. Bull Board), or restart the workers to clear a hung job:
  4. If the queue is Redis-backed and Redis is the bottleneck:

Prevention

  • Configure KEDA autoscaler to trigger at queue depth 50 (not 100).
  • Set per-job-type timeouts (default 60s) so stuck jobs do not block workers.
  • Implement circuit breakers for downstream dependencies (connectors, external APIs).
  • Monitor queue depth trend, not just threshold; alert on sustained growth rate.
  • Set max retry count to 3 with exponential backoff to prevent retry storms.

3. Database Connection Exhaustion

Severity: SEV1

Detection

  • Alert: pgbouncer_active_connections / pgbouncer_max_connections > 0.9 for 2 minutes.
  • Alert: Application logs contain remaining connection slots are reserved or too many connections.
  • Dashboard: Grafana > PostgreSQL > Connection Pool panel.
  • Metrics:

Diagnosis

  1. Check PgBouncer status:
  2. Check for connection leaks in the application:
  3. Check PostgreSQL directly:
  4. Check for long-running transactions holding connections:

Resolution

  1. Kill idle-in-transaction connections (>5 min):
  2. Reload PgBouncer if it is stuck:
  3. Temporarily increase PgBouncer pool size:
  4. If a specific service is leaking, restart it:

Prevention

  • Set idle_in_transaction_session_timeout = 30s in PostgreSQL.
  • Configure PgBouncer server_idle_timeout = 600 to reclaim idle server connections.
  • Use connection pooling in the application layer (Prisma connection limit).
  • Set max_client_conn in PgBouncer to 2x expected peak.
  • Add connection acquisition timeout (5s) in the application to fail fast.
  • Audit code for missing finally blocks that release connections.

4. Redis OOM / Eviction or Quota Exhaustion

Severity: SEV2 (SEV1 if decision cache is fully evicted or all writes blocked by quota)
Free-tier quota note: On Upstash / Fly Redis / similar pay-by-ops services, this incident may present as ERR max requests limit exceeded. Limit: 500000 rather than OOM. The most common cause is idle BullMQ worker polling with WORKER_INPROCESS=1 — five workers polling Redis with the BRPOPLPUSH command burn ~1.3M ops/month with zero queued work. The fix is WORKER_INPROCESS=0 + cron-driven drain; see worker-mode-and-cron-drain runbook §8 for the full diagnosis + resolution. Real /recommend traffic on a typical playground is ~75-120K ops/month — well within the 500K free tier if the worker isn’t burning it idle.

Detection

  • Alert: redis_memory_used_bytes / redis_memory_max_bytes > 0.9 for 5 minutes.
  • Alert: redis_evicted_keys_total rate > 0 for 3 minutes.
  • Dashboard: Grafana > Redis > Memory panel.
  • Metrics:

Diagnosis

  1. Check memory breakdown:
  2. Identify largest keys:
  3. Check which key patterns consume the most memory:
  4. Check for abnormal client connections:

Resolution

  1. Flush non-critical caches first:
  2. If specific key patterns are bloated, expire them:
  3. Scale Redis vertically (if managed):
  4. Switch eviction policy if needed:

Prevention

  • Set TTLs on all cache keys (decision cache: 60s, session: 24h, feature: 300s).
  • Use volatile-lru eviction policy so only keys with TTLs are evicted.
  • Monitor memory usage trend and scale proactively at 70%.
  • Separate cache Redis from session/queue Redis to isolate failure domains.
  • Implement key size limits in the application layer (reject values >1MB).

5. API 5xx Error Rate >1%

Severity: SEV2 (SEV1 if >5% or sustained >10 minutes)

Detection

  • Alert: rate(http_responses_total{status=~"5.."}[5m]) / rate(http_responses_total[5m]) > 0.01 for 3 minutes.
  • Dashboard: Grafana > KaireonAI API > Error Rate panel.
  • Metrics:

Diagnosis

  1. Identify which endpoints are failing:
  2. Check application logs for errors:
  3. Check if it correlates with a deployment:
  4. Check downstream dependencies:
  5. Check for resource pressure:

Resolution

  1. If caused by a bad deployment, rollback:
  2. If caused by a downstream failure: the circuit breakers are always on — watch kaireon_circuit_breaker_state_change_total to confirm the breaker for the failing dependency has tripped, and wait for it to half-open once the dependency recovers.
  3. If caused by OOM kills, increase memory:
  4. If a specific route is the problem: there is no application route-blocklist env var. Block the route at the ingress/ALB (or roll back the deploy that introduced the regression) while you investigate.

Prevention

  • Implement canary deployments (10% traffic for 5 minutes before full rollout).
  • Add structured error logging with request IDs for traceability.
  • Set memory limits with 20% headroom above observed peak.
  • Run integration tests in staging before promoting to production.
  • Implement retry with backoff for transient downstream failures.

6. DLQ Overflow (>500)

Severity: SEV2 (SEV1 if the dead-letter queue is growing fast or holds DSAR-erasure jobs)
The DLQ is a single BullMQ queue named dead-letter. A job from any of the five source queues (batch-jobs, dsar-jobs, journey-jobs, retrain-jobs, seed-jobs) is moved there after it exhausts its retries. Decision requests are served synchronously by /api/v1/recommend and are not queued, so they never appear in the DLQ.

Detection

  • Alert: kaireon_dlq_depth > 500 for 10 minutes.
  • Dashboard: Grafana > KaireonAI Workers > Dead Letter Queue panel.
  • Metrics (the kaireon_dlq_depth gauge is labelled by tenant):

Diagnosis

  1. Measure the dead-letter queue depth directly:
  2. Find which failures dominate (the worker logs Moved job to dead-letter queue with the originating sourceQueue):
  3. Check if the DLQ growth correlates with a deployment or config change:

Resolution

There is no bundled DLQ replay CLI. Re-drive failed jobs by fixing the root cause and re-enqueueing them onto their source queue via BullMQ tooling (e.g. a Bull Board admin UI pointed at REDIS_URL, or a one-off script using the bullmq client). The five live queues are drained by the /api/v1/cron/drain-queues endpoint — see the Worker Mode + Cron Drain runbook.
  1. If the failures were transient (downstream outage now recovered): confirm the dependency is healthy, then re-enqueue the dead-letter jobs onto their source queue in controlled batches with BullMQ.
  2. If the messages are poison pills (permanent schema/validation errors): export the payloads for forensics, then clear the waiting list.
  3. If a bad deploy caused the spike: roll back the worker, which stops new jobs entering the DLQ, then re-drive the accumulated jobs once the fix ships.

Prevention

  • Set DLQ alert threshold at 100 (not 500) for earlier detection.
  • Implement automatic DLQ replay with exponential backoff (max 3 retries).
  • Add DLQ message classification (transient vs. permanent failure).
  • Archive DLQ messages to S3 daily for audit and forensics.
  • Add schema validation before enqueue to reject malformed messages early.

7. Certificate Expiry

Severity: SEV1 (if <24 hours to expiry), SEV2 (if <7 days)

Detection

  • Alert: cert_expiry_seconds < 604800 (7 days) for warning.
  • Alert: cert_expiry_seconds < 86400 (24 hours) for critical.
  • Dashboard: Grafana > Infrastructure > Certificate Expiry panel.
  • Manual check:

Diagnosis

  1. Identify which certificate is expiring:
  2. Check cert-manager status (if using cert-manager):
  3. Check if auto-renewal failed:

Resolution

  1. If cert-manager is installed, force renewal:
  2. If cert-manager renewal is stuck, delete and recreate:
  3. If manual certificate, replace it:

Prevention

  • Use cert-manager with Let’s Encrypt for automatic renewal.
  • Set alerts at 30, 14, 7, 3, and 1 day(s) before expiry.
  • Run a weekly certificate audit job that scans all namespaces.
  • Maintain a certificate inventory spreadsheet with owners and expiry dates.
  • Test certificate renewal in staging monthly.

8. Node Capacity Exhaustion

Severity: SEV2 (SEV1 if pods are evicted or cannot schedule)

Detection

  • Alert: kube_node_status_condition{condition="MemoryPressure",status="true"} == 1.
  • Alert: kube_node_status_condition{condition="DiskPressure",status="true"} == 1.
  • Alert: Pending pods count > 0 for more than 5 minutes.
  • Dashboard: Grafana > Kubernetes > Node Resources panel.
  • Metrics:

Diagnosis

  1. Check node resource usage:
  2. Check for pending pods:
  3. Check for resource hogs:
  4. Check cluster autoscaler status:

Resolution

  1. If autoscaler is enabled, check if it is working:
  2. Manually add nodes if autoscaler is stuck:
  3. Evict non-critical workloads:
  4. If disk pressure, clean up:

Prevention

  • Set cluster autoscaler min/max to allow 30% headroom.
  • Use PodDisruptionBudgets to protect critical workloads during eviction.
  • Set resource requests and limits on all pods (no unbounded pods).
  • Schedule non-critical batch jobs during off-peak hours.
  • Run monthly capacity planning reviews based on growth trends.

9. Database Disk Full

Severity: SEV1

Detection

  • Alert: pg_database_size_bytes / pg_disk_total_bytes > 0.85 for 10 minutes.
  • Alert (RDS): FreeStorageSpace < 5GB for 5 minutes.
  • Dashboard: Grafana > PostgreSQL > Disk Usage panel.
  • Metrics:

Diagnosis

  1. Check current disk usage:
  2. Check for bloat:
  3. Check for WAL accumulation:
  4. Check for orphaned temp files:

Resolution

  1. Immediate: Increase disk (if RDS with storage autoscaling disabled):
  2. Run emergency VACUUM on bloated tables:
  3. Purge old data (if retention policy allows):
  4. Drop unused replication slots (WAL accumulation):

Prevention

  • Enable RDS storage autoscaling with a maximum limit.
  • Implement data retention policies with automated purge jobs (daily cron).
  • Run VACUUM ANALYZE on large tables nightly.
  • Partition large tables (decision_logs, audit_events) by month.
  • Monitor disk growth rate and project exhaustion date weekly.

10. Auth Failures Spike

Severity: SEV2 (SEV1 if suspected credential compromise)

Detection

  • Alert: rate(kaireon_auth_failures_total[5m]) > 10 for 3 minutes.
  • Alert: rate(kaireon_auth_failures_total[5m]) / rate(kaireon_auth_attempts_total[5m]) > 0.1 for 5 minutes.
  • Dashboard: Grafana > KaireonAI Security > Auth Failures panel.
  • Metrics:

Diagnosis

  1. Identify the failure reason:
  2. Check if failures are from a single IP (brute force):
  3. Check if OIDC provider is down:
  4. Check if tokens are expired due to clock skew:
  5. Check for recent secret/key rotation:

Resolution

  1. If brute force, block the source IP:
  2. If the OIDC/SSO provider is down: there is no fallback-auth env toggle. Service-to-service traffic authenticated with API keys is unaffected by an IdP outage; interactive SSO sign-in must wait for the IdP to recover.
  3. If key rotation broke auth, rollback the secret:
  4. If clock skew, sync NTP:
  5. If credential compromise is suspected:
    • Rotate all API keys and service account tokens immediately.
    • Revoke all active sessions.
    • Enable enhanced logging.
    • Notify the security team.

Prevention

  • Implement rate limiting on auth endpoints (10 failures per IP per minute).
  • Enable account lockout after 5 consecutive failures (30-minute window).
  • Use short-lived tokens (15 minutes) with refresh token rotation.
  • Monitor for credential stuffing patterns (many IPs, same usernames).
  • Run quarterly penetration tests on auth flows.
  • Sync NTP on all nodes; alert if clock drift >1 second.

Appendix: Useful Commands

Quick Health Check

Log Aggregation

Metrics Quick Reference