پاسخ به رخداد و پستمورتمهای بدون سرزنش: نوشتن Runbookهای بهتر و تعریف بهتر SLO/SLI
سرویس checkout ما ساعت ۲ بامداد یک روز جمعه از کار افتاد. تا وقتی دوباره آن را برگرداندیم، شش ساعت فروش را در آخر هفتهای که کمپین فروش ویژه داشتیم از دست داده بودیم. علت فنی، تمامشدن ظرفیت connection pool دیتابیس بود. اما علت واقعی این بود که برای این سناریوی خرابی Runbook نداشتیم، مسیر روشنی برای escalation وجود نداشت و مانیتورینگ ما تا وقتی کاربران عملاً خطا نمیدیدند هشداری صادر نمیکرد.
پستمورتم آن رخداد بیرحمانه بود؛ اما همان هم نقطهٔ عطف شد. از آن به بعد دیگر با رخدادها مثل آتشسوزیهایی که فقط باید خاموششان کرد برخورد نکردیم و قابلیت اطمینان (reliability) را بهعنوان یک دیسیپلین مهندسی جدی گرفتیم.
این مقاله همان چیزهایی را پوشش میدهد که ما یاد گرفتیم: چطور SLOهایی تعریف کنیم که واقعاً مهماند، Runbookهایی بنویسیم که واقعاً استفاده میشوند، رخدادها را بدون هرجومرج مدیریت کنیم و پستمورتمهایی برگزار کنیم که جلوی تکرار مشکل را میگیرند.
SLO، SLI و بودجهٔ خطا: چطور درست تعریفشان کنیم
بیشتر تیمها یا اصلاً SLO ندارند یا SLOهای قلابی دارند — عددهایی که همینطوری از هوا انتخاب شدهاند و هیچ ربطی به تجربهٔ کاربر یا تصمیمهای مهندسی ندارند.
SLOهای خوب شیوهٔ اولویتبندی کارها را عوض میکنند. اگر بودجهٔ خطایتان سالم است، فیچرهای جدید را منتشر کنید. اگر در حال سوختن است، روی قابلیت اطمینان تمرکز کنید.
SLI: چیزی که واقعاً اندازه میگیرید
شاخص سطح سرویس (Service Level Indicator یا SLI) باید تجربهٔ کاربر را بازتاب دهد، نه سلامت سرور را.
SLIهای بد:
- میزان مصرف CPU
- میزان مصرف حافظه
- تعداد Podهای در حال اجرا
SLIهای خوب:
- نرخ موفقیت درخواستها (درخواستهای غیر 5xx تقسیم بر کل)
- تأخیر (latency) درخواستها در p99
- تازگی دادهها (data freshness)
SLI دسترسپذیری:
sum(rate(http_requests_total{status!~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))SLI تأخیر:
sum(rate(http_request_duration_seconds_bucket{le="0.2"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))SLO: هدف شما
| SLO | داونتایم مجاز در ماه | خطاهای مجاز (در ۱ میلیون درخواست) | | ------ | ---------------------- | ---------------------------- | | 99% | ۷٫۲ ساعت | ۱۰٬۰۰۰ | | 99.5% | ۳٫۶ ساعت | ۵٬۰۰۰ | | 99.9% | ۴۳٫۸ دقیقه | ۱٬۰۰۰ | | 99.95% | ۲۱٫۹ دقیقه | ۵۰۰ | | 99.99% | ۴٫۳ دقیقه | ۱۰۰ |
رویکرد ما:
- Checkout / Auth: 99.95%، با p99 < 200ms
- APIهای داخلی: 99.9%، با p99 < 500ms
- سیستمهای Batch: 99.5%
از هدف پایینتری شروع کنید؛ بعداً سختگیرانهترش کنید.
SLO بر اساس سفر کاربر
کاربران به سفرشان در محصول اهمیت میدهند، نه به تکتک سرویسها.
journeys:
- name: checkout
slo:
availability: 99.95%
latency_p99: 3s
components:
- api-gateway
- auth-service
- cart-service
- inventory-service
- payments-service
- order-service
measurement:
endpoint: /api/v1/checkout/health
rum_event: checkout_completedبودجهٔ خطا: عملیاتیکردن SLOها
thresholds:
- budget_remaining: 50%
actions:
- notify: slack
- budget_remaining: 25%
actions:
- freeze: non_critical_deployments
- budget_remaining: 10%
actions:
- freeze: all_deployments
- meeting: reliability_review
- budget_remaining: 0%
actions:
- focus: reliability_onlyپیادهسازی مانیتورینگ SLO
slos:
- name: requests-availability
objective: 99.95
sli:
events:
error_query: sum(rate(http_requests_total{status=~"5.."}[{{.window}}]))
total_query: sum(rate(http_requests_total[{{.window}}]))نوشتن Runbookهایی که واقعاً استفاده میشوند
Runbookهای خوب این ویژگیها را دارند:
- در یک نگاه قابل مرورند (Scannable)
- عملیاتی و قابل اجرا هستند (Actionable)
- تست شدهاند (Tested)
ساختار Runbook
# Runbook: Payments API High Error Rate
## Detection
Alert: PaymentsAPIHighErrorRate
## Step 1: Check provider status
curl -s https://status.stripe.com/api/v2/summary.json
## Step 2: Check recent deploys
kubectl rollout history deployment/payments-api
## Step 3: Rollback if needed
kubectl rollout undo deployment/payments-api
## Step 4: Check DB pool
curl http://payments-api/debug/metrics | grep db_poolتستکردن Runbookها (نمونهٔ PHP / Laravel)
public function testDatabaseConnectionExhaustionRunbook(): void
{
// Simulate connection exhaustion
$this->simulateDbPoolExhaustion();
// Verify alert condition
$metrics = $this->fetchMetrics('/debug/metrics');
$this->assertLessThan(5, $metrics['db_pool_available']);
// Apply mitigation
$this->scaleServiceReplicas(10);
// Verify recovery
$this->assertTrue($this->serviceRecovered());
}پاسخ به رخداد: رویکردی ساختیافته
سطوح شدت (Severity)
SEV1: Complete outage or data loss
SEV2: Major degradation
SEV3: Minor impactنقشها در رخداد
- Incident Commander (فرمانده رخداد) — هماهنگ میکند
- Tech Lead (مسئول فنی) — دیباگ میکند
- Comms Lead (مسئول ارتباطات) — اطلاعرسانی میکند
قالب کانال رخداد
🔴 INCIDENT: Checkout Errors
Severity: SEV2
Impact: Success rate 82%
Roles:
IC: @alice
Tech: @bob
Comms: @carol
Timeline:
14:32 Alert fired
14:40 Stripe returning 503s
14:45 Circuit breaker engaged
15:15 Resolvedخودکارسازی مدیریت رخداد (PHP)
class IncidentBot
{
public function declareIncident(array $data): Incident
{
$incident = Incident::create([
'title' => $data['title'],
'severity' => $data['severity'],
'status' => 'investigating',
]);
$this->createSlackChannel($incident);
$this->notifyPagerDuty($incident);
return $incident;
}
public function resolveIncident(Incident $incident): void
{
$incident->update(['status' => 'resolved']);
$this->schedulePostmortem($incident);
}
}پستمورتمهای بدون سرزنش
نپرسید: چه کسی باعث این اتفاق شد؟ بپرسید: چه چیزی اجازه داد این اتفاق بیفتد؟
قالب پستمورتم
## Summary
Checkout degraded for 43 minutes.
## Root Cause
Circuit breaker threshold too high.
## Action Items
| Action | Owner | Deadline |
|--------|-------|----------|
| Lower threshold | @bob | Jan 22 |
| Add alert | @alice | Jan 23 |پیگیری اکشنآیتمها (PHP)
class ActionItemTracker
{
public function weeklyDigest(): void
{
$overdue = ActionItem::overdue()->get()->groupBy('owner');
foreach ($overdue as $owner => $items) {
$this->notifyOwner($owner, $items);
}
}
}اندازهگیری بهبود قابلیت اطمینان
| سنجه | قبل | بعد | | ---------------------- | ------- | ------ | | MTTR | ۴ ساعت | ۳۵ دقیقه | | رخدادهای تکراری | ۴ در هر فصل | ۱ در هر فصل | | بودجهٔ خطای باقیمانده | 12% | 58% |
جمعبندی
قابلیت اطمینان یک دیسیپلین است:
- SLOها به شما میگویند چه زمانی اوضاع خراب است
- Runbookها کمک میکنند مشکل را برطرف کنید
- نقشهای مشخص در رخداد جلوی هرجومرج را میگیرند
- پستمورتمها از تکرار مشکل جلوگیری میکنند
نکات کلیدی:
- SLOها را حول سفرهای کاربر تعریف کنید
- از بودجهٔ خطا برای هدایت تصمیمها استفاده کنید
- Runbookهای عملیاتی و قابل اجرا بنویسید
- Runbookها را بهطور منظم تست کنید
- رخدادها را ساختیافته مدیریت کنید
- پستمورتمها را بدون سرزنش نگه دارید
- اکشنآیتمها را بیوقفه پیگیری کنید
این یادداشت ترجمهٔ فارسی نوشتهٔ خودم است — نسخهٔ اصلی (انگلیسی) در dev.to