الملفات
ghaymah-exam-Anass-Ehab-SRE/q2-postmortem/postmortem-report.md

388 أسطر
17 KiB
Markdown
خام اللوم التاريخ

هذا الملف يحتوي على أحرف Unicode غامضة

هذا الملف يحتوي على أحرف Unicode قد تُخلط مع أحرف أخرى. إذا كنت تعتقد أن هذا مقصود، يمكنك تجاهل هذا التحذير بأمان. استخدم زر الهروب للكشف عنها.

# 🔴 Incident Postmortem Report — OOMKilled Application Crash
**Incident ID:** INC-2026-0726
**Severity:** P1 — Critical
**Duration:** 45 minutes
**Date:** July 26, 2026
**Author:** DevOps Team
**Status:** Resolved
---
## 1. Executive Summary
On July 26, 2026, the **Ghayma REST API** application hosted on a cloud container platform experienced a **45-minute outage** caused by repeated **OOMKilled** (Out of Memory Killed) events. The container runtime terminated the application process multiple times after it exceeded its allocated memory limit. Each restart triggered the same memory spike, creating a crash loop that prevented the service from recovering.
**Impact:**
- 100% service unavailability for 45 minutes
- All API consumers (frontend clients, monitoring, health probes) received connection errors
- Approximately **2,700 failed requests** during the outage window (estimated ~60 req/min baseline)
- Container orchestrator marked the pod/container as `CrashLoopBackOff` after repeated restart failures
---
## 2. Incident Timeline
| Time (UTC) | Event |
| :--- | :--- |
| **11:00** | 🟢 Application running normally. Memory usage stable at ~120MB (limit: 256MB) |
| **11:12** | 📈 Traffic spike begins — external batch job sends large payload requests to `POST /api/v1/items` |
| **11:15** | ⚠️ Memory usage crosses **200MB** (78% of limit). No alerts triggered |
| **11:18** | 🔴 Memory hits **256MB** limit. Kernel OOM killer terminates the container process (`OOMKilled`) |
| **11:18** | 🔄 Container runtime automatically restarts the container (Restart #1) |
| **11:20** | 🔴 Application starts, loads cached data into memory, immediately OOMKilled again (Restart #2) |
| **11:2011:45** | 🔁 **Crash loop** — container restarts 12 times. Orchestrator applies exponential backoff (`CrashLoopBackOff`) |
| **11:32** | 📟 On-call engineer alerted via PagerDuty after health check failures exceed 10 minutes |
| **11:38** | 🔍 Engineer identifies OOMKilled events in container logs and platform event stream |
| **11:45** | 🛠️ Engineer increases memory limit from **256MB → 512MB** and deploys hotfix |
| **11:48** | 🔄 Application restarts successfully. Memory stabilizes at ~180MB |
| **11:50** | 📉 Batch job completes. Traffic returns to normal levels |
| **12:03** | 🟢 Service fully confirmed stable. Incident resolved |
**Total downtime:** 45 minutes (11:18 — 12:03 UTC)
---
## 3. Root Cause Analysis
### 3.1 Direct Cause
The container was configured with a **memory limit of 256MB**, which was insufficient to handle traffic spikes. When an external batch job sent a burst of large `POST` requests with sizable JSON payloads, the Node.js process accumulated in-memory data (parsed request bodies, in-memory item storage, response buffers) beyond the container's limit.
The Linux kernel's **OOM Killer** terminated the process when it attempted to allocate memory beyond the `256MB` cgroup limit.
### 3.2 Contributing Factors
```
┌──────────────────────────────────────────────────────────────────┐
│ ROOT CAUSE BREAKDOWN │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 1. INSUFFICIENT MEMORY LIMIT │
│ └─ Container limited to 256MB (too tight for Node.js) │
│ │
│ 2. NO MEMORY-AWARE AUTO-SCALING │
│ └─ Only 1 replica running, no HPA/scaling policy │
│ │
│ 3. UNBOUNDED IN-MEMORY DATA STORE │
│ └─ Items array grows indefinitely with no cap │
│ │
│ 4. NO MEMORY ALERTS / EARLY WARNING │
│ └─ Alert only triggered after 10 min of health failures │
│ │
│ 5. NO REQUEST PAYLOAD SIZE LIMIT │
│ └─ Express accepted arbitrarily large JSON bodies │
│ │
└──────────────────────────────────────────────────────────────────┘
```
### 3.3 Why Did Restarts Fail to Recover?
Each restart reloaded the same conditions:
1. Container starts → Node.js initializes (~80MB baseline)
2. Pending queued requests immediately hit the API
3. Large payloads + in-memory storage push memory past the limit
4. OOMKilled again within seconds
5. Orchestrator enters **CrashLoopBackOff** with increasing delays between restarts
---
## 4. Remediation Actions Taken
| Action | Status |
| :--- | :--- |
| Increased container memory limit to 512MB | ✅ Done (hotfix) |
| Restarted container with new limits | ✅ Done |
| Confirmed service recovery and stability | ✅ Done |
---
## 5. Recommendations & Prevention
### 5.1 Application-Level Fixes
#### A. Limit Request Payload Size
```javascript
// In src/index.js — restrict incoming JSON body size
app.use(express.json({ limit: '1mb' }));
```
> Prevents a single large request from consuming excessive memory.
#### B. Cap In-Memory Data Store
```javascript
// Limit the items array to prevent unbounded growth
const MAX_ITEMS = 1000;
app.post('/api/v1/items', (req, res) => {
if (items.length >= MAX_ITEMS) {
return res.status(429).json({
success: false,
error: 'Maximum item limit reached'
});
}
// ... rest of handler
});
```
#### C. Set Node.js Memory Ceiling
```dockerfile
# In Dockerfile — set explicit V8 heap limit
CMD ["node", "--max-old-space-size=384", "src/index.js"]
```
> Ensures Node.js garbage collector runs more aggressively before hitting the container limit.
#### D. Use External Storage for Production
Replace the in-memory `items` array with a proper database (Redis, PostgreSQL, MongoDB) so application memory remains constant regardless of data volume.
---
### 5.2 Container & Infrastructure Fixes
#### A. Set Proper Resource Requests & Limits
```yaml
# Kubernetes Deployment example
resources:
requests:
memory: "256Mi" # Guaranteed minimum
cpu: "100m"
limits:
memory: "512Mi" # Hard ceiling
cpu: "500m"
```
> [!IMPORTANT]
> **Rule of thumb:** Set `limits.memory` to at least **2x** the average working set. Set `requests.memory` to the **steady-state average**.
#### B. Add Graceful Shutdown Handling
```javascript
process.on('SIGTERM', () => {
console.log('SIGTERM received. Shutting down gracefully...');
server.close(() => {
process.exit(0);
});
});
```
---
## 6. Auto-Scaling Policy Design
### 6.1 Horizontal Pod Autoscaler (HPA) Policy
The following auto-scaling policy prevents a single container from being overwhelmed by distributing load across multiple replicas:
```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ghayma-api-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ghayma-api
# Replica bounds
minReplicas: 2 # Always run at least 2 for high availability
maxReplicas: 10 # Cap to control costs
# Scaling metrics
metrics:
# Scale based on MEMORY usage (primary - prevents OOMKill)
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70 # Scale up when memory > 70% of limit
# Scale based on CPU usage (secondary)
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 75 # Scale up when CPU > 75%
# Scaling behavior (prevents flapping)
behavior:
scaleUp:
stabilizationWindowSeconds: 30 # React quickly to spikes
policies:
- type: Pods
value: 2 # Add up to 2 pods at a time
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300 # Wait 5 min before scaling down
policies:
- type: Pods
value: 1 # Remove 1 pod at a time
periodSeconds: 120
```
### 6.2 How This Policy Prevents Duplication of the Incident
```
Traffic Spike Detected
┌───────────────────────┐
│ Memory usage > 70% │──── YES ──▶ HPA adds new replicas (up to 10)
│ on existing pods? │ Load balancer distributes traffic
└───────────────────────┘ Memory per-pod stays under limit
│ NO │
▼ ▼
Normal operation Spike absorbed across multiple pods
No single pod reaches OOMKill threshold
```
### 6.3 Cloud Platform Auto-Scaling (Non-Kubernetes)
For managed container platforms (e.g., Ghayma Cloud, AWS ECS, Google Cloud Run):
| Setting | Value | Rationale |
| :--- | :--- | :--- |
| **Min instances** | 2 | Avoid cold starts & single point of failure |
| **Max instances** | 10 | Cost ceiling |
| **Scale-up trigger** | Memory > 70% OR CPU > 75% OR Concurrent requests > 50 | Multi-signal scaling |
| **Scale-up cooldown** | 30 seconds | React quickly to spikes |
| **Scale-down cooldown** | 5 minutes | Prevent flapping during variable traffic |
| **Memory per instance** | 512MB | 2x steady-state working set |
---
## 7. Early Detection — Monitoring & Alerting Strategy
### 7.1 Key Metrics to Monitor
| Metric | Source | Warning Threshold | Critical Threshold |
| :--- | :--- | :--- | :--- |
| **Container Memory Usage** | cAdvisor / Platform metrics | > 70% of limit | > 85% of limit |
| **Container Restart Count** | Kubelet / Platform events | ≥ 1 restart in 5 min | ≥ 3 restarts in 10 min |
| **OOMKilled Events** | Kernel / Container runtime | Any occurrence | N/A (always critical) |
| **Response Time (p95)** | Application `/metrics` endpoint | > 500ms | > 2000ms |
| **Error Rate (5xx)** | Load balancer / Application | > 1% | > 5% |
| **Health Check Failures** | Platform health probe | 1 consecutive failure | 3 consecutive failures |
| **Request Queue Depth** | Load balancer | > 50 pending | > 200 pending |
### 7.2 Alerting Rules (Prometheus / Grafana Example)
```yaml
# Alert: Memory approaching container limit
- alert: HighMemoryUsage
expr: |
(container_memory_usage_bytes{container="ghayma-api"}
/ container_spec_memory_limit_bytes{container="ghayma-api"}) > 0.70
for: 2m
labels:
severity: warning
annotations:
summary: "Ghayma API memory usage above 70%"
description: "Container {{ $labels.pod }} memory at {{ $value | humanizePercentage }}"
# Alert: OOMKill detected (CRITICAL — immediate page)
- alert: OOMKillDetected
expr: |
increase(kube_pod_container_status_restarts_total{container="ghayma-api"}[5m]) > 0
and kube_pod_container_status_last_terminated_reason{container="ghayma-api"} == "OOMKilled"
for: 0m
labels:
severity: critical
annotations:
summary: "OOMKill detected on Ghayma API"
description: "Pod {{ $labels.pod }} was OOMKilled. Immediate investigation required."
# Alert: High error rate
- alert: HighErrorRate
expr: |
rate(http_requests_total{status=~"5.."}[5m])
/ rate(http_requests_total[5m]) > 0.05
for: 3m
labels:
severity: critical
annotations:
summary: "Ghayma API error rate exceeds 5%"
```
### 7.3 Monitoring Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ MONITORING STACK │
│ │
│ ┌──────────┐ ┌─────────────┐ ┌──────────────────────┐ │
│ │ Ghayma │───▶│ Prometheus │───▶│ Grafana Dashboard │ │
│ │ API │ │ (scrapes │ │ (visualization + │ │
│ │ /metrics │ │ /metrics) │ │ alerting rules) │ │
│ └──────────┘ └──────┬──────┘ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Alert Manager │ │
│ │ (routing & │ │
│ │ deduplication)│ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ Slack │ │ PagerDuty│ │ Email │ │
│ │ #alerts │ │ on-call │ │ team │ │
│ └──────────┘ └──────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 7.4 Cloud Platform Native Tools
| Cloud Platform | Monitoring Service | Key Feature for OOM Detection |
| :--- | :--- | :--- |
| **AWS ECS / Fargate** | CloudWatch Container Insights | `MemoryUtilization` metric + CloudWatch Alarms |
| **Google Cloud Run** | Cloud Monitoring | `container/memory/utilization` + Alert Policies |
| **Azure Container Apps** | Azure Monitor | `MemoryWorkingSet` + Metric Alerts |
| **Kubernetes (any)** | Prometheus + Grafana | `container_memory_usage_bytes` + `kube_pod_container_status_last_terminated_reason` |
### 7.5 Recommended Grafana Dashboard Panels
1. **Memory Usage vs Limit** — Time series showing memory consumption relative to the container limit (with 70% and 85% threshold lines)
2. **Container Restarts** — Counter panel showing restart events over time
3. **Response Time Heatmap** — p50 / p95 / p99 latency distribution
4. **Request Rate & Error Rate** — Dual-axis graph (total requests + 5xx rate)
5. **Pod/Replica Count** — Shows HPA scaling activity over time
---
## 8. Lessons Learned
| # | Lesson | Action Item |
| :--- | :--- | :--- |
| 1 | Default memory limits were set too low without load testing | Conduct load tests before production deployment |
| 2 | Single-replica deployment has no resilience | Always run **≥ 2 replicas** for production services |
| 3 | Alert threshold (10 min) was too slow for a P1 outage | Reduce critical alert threshold to **2 minutes** |
| 4 | In-memory data stores are dangerous without bounds | Use external databases or enforce collection size limits |
| 5 | No runbook existed for OOMKilled incidents | Create and publish an OOMKilled response runbook |
---
## 9. Action Items Tracker
| # | Action Item | Owner | Priority | Deadline | Status |
| :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | Increase container memory limit to 512MB | DevOps | P0 | Done | ✅ |
| 2 | Add `express.json({ limit: '1mb' })` payload limit | Backend | P1 | +1 day | ⬜ |
| 3 | Implement HPA with memory-based scaling | DevOps | P1 | +3 days | ⬜ |
| 4 | Set up Prometheus memory alerts (70% / 85%) | DevOps | P1 | +3 days | ⬜ |
| 5 | Add OOMKilled alert rule (immediate paging) | DevOps | P0 | +1 day | ⬜ |
| 6 | Run minimum 2 replicas in production | DevOps | P1 | +1 day | ⬜ |
| 7 | Replace in-memory store with Redis/DB | Backend | P2 | +1 week | ⬜ |
| 8 | Conduct load testing with realistic traffic | QA | P2 | +2 weeks | ⬜ |
| 9 | Create OOMKilled incident runbook | DevOps | P2 | +1 week | ⬜ |
| 10 | Build Grafana dashboard for container metrics | DevOps | P2 | +1 week | ⬜ |
---
> **Report prepared by:** DevOps Team
> **Review date:** July 26, 2026
> **Next review:** August 2, 2026 (verify all P0/P1 items completed)