کاربر «پرداخت» را می‌زند. درگاه تراکنش را Commit می‌کند، اما پاسخ در شبکه گم می‌شود. سرویس فروشگاه Timeout می‌بیند و درخواست را با یک شناسه تازه تکرار می‌کند؛ حساب مشتری دوباره بدهکار می‌شود. تمام Instanceها روشن‌اند و Availability ظاهراً خوب است، اما سیستم تاب‌آور نیست؛ چون در یک شکست مبهم، نتیجه کسب‌وکار و صحت داده را حفظ نکرده است.

تست تاب‌آوری سیستم یا Resilience Testing بررسی می‌کند یک جریان حیاتی هنگام خرابی، فشار یا رفتار ناقص وابستگی چگونه مقاومت می‌کند، چه خدمتی را با کیفیت کاهش‌یافته ادامه می‌دهد، چگونه جلوی گسترش خرابی را می‌گیرد و در چه مدت و با چه صحتی به وضعیت قابل‌اعتماد برمی‌گردد. موضوع فقط «سرویس دوباره ۲۰۰ داد» نیست؛ Duplicate side effect، سفارش معلق، Retry storm، Backlog، داده ازدست‌رفته و نیاز به مداخله دستی نیز بخشی از نتیجه‌اند.

این راهنما از Critical Flow و Failure Mode شروع می‌کند، سپس Expected degraded behavior، Invariant، Timeout/Retry/Idempotency/Circuit Breaker/Bulkhead/Load shedding، Recovery objective، Fault injection، Observability و Evidence را به یک Test Plan عملی وصل می‌کند. یک شبیه‌ساز قطعی Node.js نیز نشان می‌دهد چرا Retry بدون Contract می‌تواند خودِ خرابی را تشدید کند.

تست تاب‌آوری سیستم چیست؟

Resilience Testing مجموعه‌ای از آزمون‌هاست که توان سیستم برای پیش‌بینی، تحمل، محدودکردن اثر، بازیابی و سازگارشدن در برابر شرایط نامطلوب را می‌سنجد. این شرایط می‌توانند Timeout شبکه، خطای موقت یا دائم وابستگی، کاهش Capacity، Crash، Queue lag، Replica lag، پرشدن Disk، DNS failure، Clock skew یا خطای عملیاتی باشند.

NIST SP 800-160 Volume 2 در دامنه مشخصِ Cyber Resiliency از توان Anticipate، Withstand، Recover و Adapt صحبت می‌کند. این سند استاندارد جامع تست تاب‌آوری همه نرم‌افزارها نیست، اما چهار فعل آن یک یادآوری خوب است: فقط Recovery را نسنجید؛ رفتار حین اختلال و یادگیری پس از آن نیز مهم است.

پاسخ کوتاه

یک تست تاب‌آوری خوب چنین سؤالی دارد: «اگر Failure X در شرایط Load Y و State Z رخ دهد، آیا جریان حیاتی C در محدوده Degradation مشخص باقی می‌ماند، Invariantهای داده حفظ می‌شوند، اثر به دامنه مجاز محدود است و سیستم در Recovery budget تعریف‌شده بدون عارضه پنهان برمی‌گردد؟»

Reliability، Availability، Resilience و Recoverability یکی نیستند

مفهوم پرسش اصلی نمونه شاهد
Reliability آیا سرویس در بازه و شرایط تعریف‌شده نتیجه درست می‌دهد؟ SLI/SLO، نرخ موفقیت درست، Incident
Availability چه سهمی از زمان/درخواست، خدمت قابل‌استفاده است؟ Good request ratio با Denominator روشن
Resilience هنگام اختلال چگونه اثر را تحمل و مهار می‌کند؟ Degradation، Isolation، Invariant، User impact
Recoverability چگونه و در چه مدت به حالت قابل‌اعتماد برمی‌گردد؟ Detection، Restore، Backlog drain، Verification
Disaster Recovery پس از رخداد بزرگ، سرویس و داده با چه RTO/RPO بازمی‌گردند؟ Failover/Restore drill و صحت داده

یک API می‌تواند Available باشد اما پاسخ stale یا Duplicate بسازد. همچنین سیستمی که پس از Restart بالا می‌آید، اگر پیام‌های Queue را دوبار اعمال کند Recoverableِ صحیح نیست. معیار باید از نتیجه کاربر و داده شروع شود، نه صرفاً Up بودن Process.

تست تاب‌آوری چه تفاوتی با Chaos Engineering دارد؟

Fault Injection عملی است که خطا را به سیستم وارد می‌کند. Chaos Engineering یک رشته آزمایش‌محور برای آزمودن فرضیه درباره رفتار سیستم در شرایط آشفته است. Resilience Testing دامنه نتیجه‌محورتری دارد: می‌تواند از Unit test با Fake clock تا Integration test شبکه، Load+Failure، Restore drill و Chaos experiment کنترل‌شده استفاده کند.

  • Fault Injection: «چگونه خطا را ایجاد کنیم؟»
  • Chaos Experiment: «چه فرضیه‌ای را با Steady State، Blast Radius و Stop condition آزمایش کنیم؟»
  • Resilience Test: «کدام قابلیت تحمل/مهار/بازیابی و کدام Outcome را اثبات کنیم؟»

برای طراحی Steady State، Hypothesis، Blast Radius، Kill switch و اجرای امن GameDay، راهنمای مستقل مهندسی آشوب و Chaos Experiment را ببینید. این مقاله مالک Failure-mode coverage، Degradation، Correctness و Recovery evidence است و آن فرایند را تکرار نمی‌کند.

از Critical Flow شروع کنید، نه از ابزار Fault

پرسش «کدام Pod را Kill کنیم؟» زود است. ابتدا مشخص کنید کدام جریان برای کاربر و کسب‌وکار حیاتی است. یک Critical Flow باید آغاز، پایان، Actor، Dependency، Side effect و نتیجه قابل‌مشاهده داشته باشد.

نمونه Critical Flow پرداخت

  1. کاربر Order را با مبلغ Canonical ریال تأیید می‌کند.
  2. سرویس پرداخت درخواست را با Idempotency key به PSP می‌فرستد.
  3. PSP ممکن است پاسخ Sync یا Callback غیرهمزمان بدهد.
  4. Ledger، Order و Payment باید به State سازگار همگرا شوند.
  5. کاربر یک نتیجه قابل‌فهم و راه اقدام بعدی می‌بیند.

مرز آزمون شامل Browser/App، API gateway، Order، Payment، PSP sandbox/fake، Database، Queue، Reconciliation و Notification است. چیزی که خارج Scope می‌ماند نیز باید نوشته شود.

Failure Mode Analysis را به سناریوی آزمون تبدیل کنید

راهنمای Reliability Testing در Azure Well-Architected پیشنهاد می‌کند سناریوها از Critical Flow و Failure mode ساخته و بر اساس Impact و Likelihood اولویت‌بندی شوند. برای هر Mode فقط علت فنی ننویسید؛ اثر کاربر، مکانیزم دفاعی و Oracle را نیز اضافه کنید.

Failure mode اثر محتمل انتظار تاب‌آوری Oracle
PSP Timeout after commit نتیجه مبهم و Duplicate Idempotency + Reconciliation یک Charge و یک نتیجه نهایی
Latency شدید Cache Thread/Connection exhaustion Deadline + Circuit + Fallback Latency budget و عدم Cascade
Queue consumer down Backlog و نتیجه دیرهنگام Durable queue + Recovery drain عدم Loss/Duplicate و زمان Drain
یک Zone از دسترس خارج Capacity و Replica کاهش می‌یابد Isolation/Failover/Load shed SLO جریان و دامنه اثر
DB replica stale نمایش State قدیمی Consistency policy/Fallback Bounded staleness و عدم تصمیم غلط
Disk full Write failure یا Corruption Fail closed/Alert/Recovery عدم Acknowledge کاذب و صحت پس از Restore

برای امتیازدهی Impact×Likelihood×Uncertainty و انتخاب Modeهای مهم، از راهنمای تست مبتنی بر ریسک استفاده کنید. Rare بودن یک Failure، دلیل حذف سناریوی با Impact فاجعه‌بار نیست.

قرارداد یک Resilience Test

هر تست را با قالب نسخه‌دار زیر تعریف کنید:

  1. Decision/Risk: نتیجه قرار است کدام تصمیم Release/Architecture را پشتیبانی کند؟
  2. Critical flow: Actor، آغاز، پایان و Outcome چیست؟
  3. Baseline condition: Load، Data، Topology و Health اولیه چیست؟
  4. Failure mode: دقیقاً کدام رفتار، در کجا و چه زمانی؟
  5. Fault profile: Duration، Probability، Distribution و Target selection؛
  6. Expected degradation: چه چیزی ادامه، محدود، Queue یا رد می‌شود؟
  7. Invariant: چه حقیقتی تحت هیچ شرایطی نباید شکسته شود؟
  8. Recovery objective: Detect، Mitigate، Restore، Drain و Verify تا چه زمان؟
  9. Scope/Safety: Blast radius، Authorization، Stop و Rollback؛
  10. Oracles: User، business، data، dependency، resource و telemetry؛
  11. Evidence: Trace/Log/Metric/Audit/Data diff/Timeline؛
  12. Pass/Fail/Inconclusive: معیارهای صریح و Owner تصمیم.

Steady State کافی نیست؛ Invariant هم لازم است

نرخ HTTP ۲۰۰ ممکن است ثابت بماند درحالی‌که دو Charge برای یک Order ایجاد شده است. Steady State رفتار قابل‌مشاهده جریان را می‌سنجد؛ Invariant حقیقتی است که حتی در Failure باید حفظ شود.

Invariantهای نمونه پرداخت

  • برای هر Intent پرداخت، حداکثر یک Debit معتبر ثبت شود؛
  • مجموع Ledger debit/credit متوازن بماند؛
  • هر Callback با شناسه یکسان حداکثر یک Side effect داشته باشد؛
  • Order از State نهایی به Pending برنگردد؛
  • Acknowledge موفق فقط پس از Durable commit صادر شود؛
  • هر State مبهم وارد Reconciliation قابل‌ردیابی شود؛
  • مبلغ Canonical ریال با نمایش تومان مخلوط نشود.

برای طراحی State، Event، Idempotency و Saga در سطح سرویس‌ها، استراتژی تست میکروسرویس‌ها مرز Contract تا E2E را پوشش می‌دهد.

Recovery را به یک Timeline قابل‌اندازه‌گیری بشکنید

MTTR یک عدد مبهم است مگر نقطه آغاز/پایان روشن باشد. Timeline پیشنهادی:

  1. T0 — Fault starts: اختلال واقعاً آغاز می‌شود.
  2. TTD — Time to Detect: Health model/Alert مشکل را تشخیص می‌دهد.
  3. TTM — Time to Mitigate: اثر مهار می‌شود؛ Circuit، Shed، Failover یا اقدام انسانی.
  4. TTR — Time to Restore: جریان خدمت به محدوده مورد انتظار برمی‌گردد.
  5. Backlog drain: Queue/کار عقب‌افتاده بدون Overload یا Duplicate پردازش می‌شود.
  6. Data verification: Reconciliation و Invariant صحت را تأیید می‌کنند.
  7. Full recovery: Temporary flag، Capacity اضافه و Manual workaround پاک می‌شوند.

RTO و RPO را کجا استفاده کنیم؟

RTO هدف زمانی بازیابی کسب‌وکار پس از Disruption و RPO تحمل ازدست‌رفتن داده نسبت به نقطه بازیابی است. آن‌ها برای هر Retry کوچک مناسب نیستند و جای SLO جریان، TTD یا Backlog drain را نمی‌گیرند. اگر Restore/Failover داده را می‌آزمایید، RTO/RPO و صحت End-to-End را جدا ثبت کنید.

Fault Model باید رفتار واقعی بسازد

«Dependency down» فقط یک Mode است. سیستم‌های توزیع‌شده بیشتر با شکست‌های ناقص و مبهم آسیب می‌بینند:

  • Latency ثابت، Jitter، Tail latency و Timeout؛
  • Connection reset قبل یا بعد از ارسال Body؛
  • HTTP ۴۲۹/۵۰۳ با یا بدون Retry-After؛
  • پاسخ ۲۰۰ با Payload ناقص/قدیمی/ناسازگار؛
  • Partial commit، Timeout after commit و Late response؛
  • Duplicate، Delay، Reorder یا Loss پیام؛
  • Network partition یک‌طرفه؛
  • Slow dependency که Health check هنوز آن را Healthy می‌بیند؛
  • Failure هم‌زمان با Deploy، Peak load یا Recovery.

Fault باید در Locus درست Inject شود. بستن Connection در Client mock با Packet loss روی مسیر واقعی و با PSP که Commit کرده ولی Response گم شده، Oracleهای متفاوتی دارد.

Timeout و Deadline را آزمایش کنید

Timeout فقط یک عدد Config نیست. باید Connect، TLS، Request write، Server processing، Response read و کل Deadline کاربر را در نظر بگیرد. اگر Parent request لغو شد، Child call نیز باید Cancellation را بگیرد؛ وگرنه کار بی‌مصرف ادامه می‌یابد و Capacity می‌سوزد.

سناریوهای ضروری Timeout

  • پاسخ درست درست قبل و درست بعد از Deadline؛
  • Timeout در Connect در برابر Read؛
  • Timeout پس از Side effect؛
  • Child timeout بزرگ‌تر از Parent deadline؛
  • لغو Client در میانه عملیات؛
  • هم‌زمانی Timeout با Retry داخلی SDK؛
  • Deadline propagation بین چند Hop.

راهنمای AWS درباره Timeout، Retry، Backoff و Jitter توضیح می‌دهد که Timeout باید بر پایه Latency رفتار Dependency انتخاب شود و Retry خودخواهانه می‌تواند Load را افزایش دهد. مقدار یک سرویس را بدون اندازه‌گیری و Context به سرویس دیگر کپی نکنید.

Retry را به‌عنوان یک Load Generator پنهان ببینید

سه لایه که هرکدام چهار Attempt دارند می‌توانند یک درخواست کاربر را به ۴×۴×۴ یعنی ۶۴ تلاش پایین‌دستی تبدیل کنند. فصل Cascading Failures در Google SRE هشدار می‌دهد Retryها ممکن است Overload را تشدید کنند و بر Backoff تصادفی، محدودکردن Retry و Budget سراسری تأکید می‌کند.

قرارداد Retry

  • فقط Errorهای واقعاً Transient و عملیات Retry-safe؛
  • Attempt و Deadline کل محدود؛
  • Exponential backoff همراه Jitter؛
  • Honor کردن Server hint مانند Retry-After در صورت قرارداد؛
  • یک لایه مسئول Retry، نه تکثیر بی‌خبر در Stack؛
  • Retry budget در سطح Process/Service؛
  • Metric جدا برای Logical request و Downstream attempt؛
  • عدم Retry برای Validation، Authorization و خطای دائمی.

Idempotency؛ پاسخ به ابهام Timeout after commit

Retry بدون Idempotency برای عملیات Mutating خطرناک است. Client باید Intent را با یک شناسه پایدار بیان کند؛ Server نیز Token، پارامترها و نتیجه را در یک Contract اتمیک نگه دارد و برای Retry همان Intent پاسخ معادل بدهد.

راهنمای AWS برای Idempotent API توضیح می‌دهد چرا حدس Duplicate از روی یکسان‌بودن پارامترها کافی نیست، چگونه Caller-provided request ID Intent را روشن می‌کند و چرا ثبت Token و Mutation باید سازگار باشد. تست‌های مهم:

  • همان Token + همان Payload هم‌زمان و پشت‌سرهم؛
  • همان Token + Payload متفاوت؛ باید Conflict/Validation روشن باشد؛
  • Response گم‌شده پس از Commit و Retry دیرهنگام؛
  • Crash بین ثبت Token و Side effect؛
  • Expiration/Retention Token و Late request؛
  • دو Worker که هم‌زمان Duplicate را می‌گیرند؛
  • Reconciliation میان PSP، Inbox، Payment و Ledger.

آزمایش واقعی: Retry تازه در برابر Idempotency ثابت

یک شبیه‌ساز قطعی با Node.js ۲۴.۱۸.۰ ساخته و اجرا شد. Fake PSP ابتدا Charge را Commit و سپس پاسخ را Drop می‌کند. Client برای ۲۰ Order یک بار Retry می‌کند. تنها تفاوت این است که نسخه اول در Retry کلید تازه می‌سازد و نسخه دوم همان کلید Intent را نگه می‌دارد.

// ساده‌شده: Failure پس از Commit رخ می‌دهد.
try {
  psp.charge({ key: firstKey, orderId, dropResponse: true });
} catch (error) {
  psp.charge({ key: retryKey, orderId });
}
timeout_after_commit
new_key_per_retry:
  logical_orders: 20
  downstream_attempts: 40
  actual_charges: 40
  duplicate_charges: 20

stable_idempotency_key:
  logical_orders: 20
  downstream_attempts: 40
  actual_charges: 20
  duplicate_charges: 0

تعداد Attempt هر دو ۴۰ است، پس نمودار Request count به‌تنهایی تفاوت درستی را نشان نمی‌دهد. Oracle مستقل Charge/Ledger آشکار می‌کند که کلید ثابت Duplicate را حذف کرده است. این مدل محدود است: Atomicity واقعی، Race، Expiration و PSP مستقل را شبیه‌سازی نمی‌کند؛ برای آن‌ها Integration test لازم است.

Circuit Breaker را با State و Timing تست کنید

Circuit Breaker پس از نشانه‌های Failure، Callهای تازه را سریع رد می‌کند تا Dependency فرصت Recovery داشته باشد. سه State رایج Closed، Open و Half-open هستند. فقط «باز شد» را تست نکنید:

  • کدام Error/Latency در Sliding window شمرده می‌شود؟
  • Minimum call count و Threshold چیست؟
  • در Open state پاسخ کاربر و Fallback چیست؟
  • چند Probe در Half-open مجاز است؟
  • Probeهای هم‌زمان می‌توانند موج تازه بسازند؟
  • پس از Recovery آیا Counter/Metric درست Reset می‌شود؟
  • Config مشترک باعث بازشدن هم‌زمان همه Instanceها می‌شود؟

مستندات Resilience4j Circuit Breaker، Retry، Rate Limiter و Bulkhead را Decoratorهای جدا معرفی می‌کند. کنار هم قراردادن آن‌ها به‌خودی‌خود درست نیست؛ ترتیب و ترکیب باید با Failure contract آزمایش شود.

Bulkhead و Isolation؛ خرابی را محلی نگه دارید

Bulkhead منابع یک جریان یا Dependency را از بقیه جدا می‌کند: Thread pool، Connection pool، Queue، Concurrency limit، Tenant quota یا Cell/Zone مستقل. آزمون باید نشان دهد Saturation بخش گزارش‌گیری، Checkout را از Connection محروم نمی‌کند.

Oracleهای Isolation

  • مصرف Pool در Failure domain هدف و جریان Control؛
  • p95/p99 و Goodput جریان غیرمرتبط؛
  • Queue length و Reject reason هر Bulkhead؛
  • عدم نشت Thread/Connection پس از Timeout؛
  • Fairness میان Tenantها و جلوگیری از Noisy neighbor؛
  • Recovery مستقل بدون Restart کل سرویس.

Load Shedding و Backpressure را عمداً فعال کنید

سیستم تاب‌آور گاهی باید بخشی از کار را سریع و صریح رد کند تا همه کارها کند و ناموفق نشوند. Load shedding باید Priority و Cost را بداند، Response قابل‌فهم بدهد و از Queue بی‌نهایت جلوگیری کند.

  • کار Critical از کار تزئینی جدا باشد؛
  • Reject پیش از مصرف منبع گران رخ دهد؛
  • Client بداند Retry کند، Queue کند یا متوقف شود؛
  • Queue age و Deadline مانع پردازش کار بی‌ارزش شوند؛
  • Recovery با هجوم Backlog دوباره سیستم را Down نکند.

Failure+Load را کنار هم اجرا کنید؛ یک Replica loss در ۱۰٪ Load ممکن است بی‌اثر و در Peak به Cascade تبدیل شود. برای ساخت Workload و سنجش Goodput، Tail latency، Saturation و شرایط توقف، راهنمای تست عملکرد را ببینید.

آزمایش واقعی دوم: Retry Amplification در قطعی سخت

در بخش دوم همان شبیه‌ساز، ۱۰۰ درخواست هنگام قطعی کامل Dependency اجرا شدند. سیاست ساده سه Attempt برای هر Logical request را با Budget مشترک پنج Call و Circuit باز مقایسه کرد:

hard_outage
naive_three_attempts:
  logical_requests: 100
  downstream_calls: 300
  calls_per_request: 3

shared_budget_and_open_circuit:
  logical_requests: 100
  downstream_calls: 5
  calls_per_request: 0.05
  rejected_by_dependency: 5
  shed_while_circuit_open: 95

نسخه محافظت‌شده Availability ایجاد نکرد؛ ۹۵ درخواست را عمداً Shed کرد. ارزش آن مهار فشار پایین‌دستی و Outcome صریح است. در محصول واقعی باید UX، Retry-after policy، Priority، Budget و Half-open recovery جدا سنجیده شوند.

Graceful Degradation را به Requirement تبدیل کنید

عبارت «سرویس Gracefully degrade می‌شود» قابل تست نیست. دقیق بنویسید کدام قابلیت حذف، Stale، Queue یا Read-only می‌شود و چه چیزی هرگز نباید تغییر کند.

Dependency failure رفتار کاهش‌یافته مجاز رفتار غیرمجاز
Recommendation لیست عمومی یا حذف Widget Fail شدن Checkout
Notification ثبت Outbox و ارسال دیرتر Rollback پرداخت موفق
Search نتایج Cache با برچسب تازگی نمایش قیمت منقضی به‌عنوان قطعی
PSP Pending قابل‌پیگیری/PSP جایگزین مجاز Retry کور یا «ناموفق» کاذب
Reporting Read-only/گزارش دیرتر مصرف Pool تراکنش اصلی

Degraded mode مسیر کم‌استفاده است و خودش ممکن است خراب باشد. آن را دوره‌ای، زیر Load و همراه Exit از حالت کاهش‌یافته اجرا کنید.

Failover فقط تغییر Endpoint نیست

Failover ممکن است DNS، Connection pool، Session، Cache، Leader election و داده را درگیر کند. Pass واقعی نیازمند این شواهد است:

  • Trigger درست و نه حساسیت بیش‌ازحد به Transient fault؛
  • عدم Split brain یا Double writer؛
  • RPO واقعی و Reconciliation داده؛
  • Clientها Connection قدیمی را رها می‌کنند؛
  • Capacity مقصد زیر Load کافی است؛
  • Failback نیز آزمایش و State موقت پاک می‌شود؛
  • Manual step، دسترسی و Runbook در فشار واقعی قابل اجراست.

آزمون Provider/Region، Backup/Restore و DR ابری مالکیت جدا دارد؛ برای آن از راهنمای تست ابری و Recovery استفاده کنید.

Recovery بدون Backlog drain کامل نیست

وقتی Consumer ده دقیقه Down بوده، بالا آمدن Process پایان Incident نیست. Queue ممکن است به‌اندازه ده دقیقه کار عقب‌افتاده داشته باشد؛ Drain تهاجمی می‌تواند Dependency تازه‌بازیابی‌شده را دوباره Overload کند.

معیارهای Backlog Recovery

  • Backlog count، Oldest message age و Arrival/Drain rate؛
  • Duplicate، Out-of-order و Dead-letter؛
  • Goodput، نه صرفاً Consume rate؛
  • Effect روی ترافیک Online؛
  • Throttle و Priority هنگام Drain؛
  • زمان همگرایی Ledger/Read model؛
  • Poison message و توقف Partition؛
  • کامل‌شدن Reconciliation پس از صفرشدن Queue.

خرابی Kubernetes را با PDB اشتباه نفهمید

مستندات Kubernetes درباره Disruption میان اختلال ارادی و غیرارادی فرق می‌گذارد و صریح می‌گوید PodDisruptionBudget همه علت‌های Unavailability را مهار نمی‌کند. PDB برای Evictionهای ارادی خاص است؛ Node failure، Network partition یا حذف مستقیم بعضی Resourceها ممکن است از آن عبور کنند.

سناریوهای Kubernetes

  • Eviction ارادی در محدوده PDB؛
  • Node loss غیرارادی و Capacity باقی‌مانده؛
  • Pod restart با Startup کند و Probe نامناسب؛
  • Rolling update هم‌زمان با Peak load؛
  • Zone loss و Anti-affinity واقعی؛
  • Termination grace و کار In-flight؛
  • CrashLoop که Health automation را تشدید می‌کند.

محیط آزمون باید Fit for Failure باشد

کپی کامل Production همیشه ممکن یا لازم نیست، اما ویژگی مرتبط با تصمیم باید Fidelity کافی داشته باشد: Topology، Capacity ratio، Retry config، Timeout، Queue، Persistence، Health check و Observability. اگر Staging یک Instance بدون Load دارد، نتیجه Zone loss روی Production چندمنطقه‌ای قابل تعمیم نیست.

  • Environment Manifest و نسخه Config؛
  • Data مصنوعی و Dependency fake/sandbox با Fault contract؛
  • Load profile و Capacity scale factor؛
  • تفاوت‌های شناخته‌شده با Production؛
  • Fault injector isolation و Cleanup؛
  • Health gate پیش و پس از Run؛
  • Secret/Permission حداقلی و Audit.

مقاله مدیریت محیط تست، IaC و Drift قالب Manifest و Health gate را دقیق‌تر ارائه می‌کند.

Fault Injection را در نردبان کم‌خطر اجرا کنید

  1. Unit: Fake clock، exception، cancellation و state machine؛
  2. Component: Stub/Fake dependency با Delay/Error/partial response؛
  3. Integration: Proxy/network fault، DB/Queue واقعی و Atomicity؛
  4. Staging/GameDay: Topology و Load نزدیک تصمیم؛
  5. Canary Production: فقط پس از Readiness، Scope کوچک و Stop خودکار؛
  6. Recurring regression: سناریوهای پایدار در CI/Nightly؛

Production مرحله اجباری نیست. راهنمای تست Shift-Right و Testing in Production درباره Canary، Synthetic data، Guardrail، Rollback و حریم داده مرزهای ایمن را توضیح می‌دهد.

Safety Gate پیش از هر Fault

  • Authorization کتبی و Owner کسب‌وکار/فنی؛
  • Known issueهای بحرانی ابتدا Fix شده‌اند؛
  • Scope و Target selection Fail-closed است؛
  • Load، رویداد تجاری و Change freeze بررسی شده‌اند؛
  • Stop condition خودکار و Kill switch دستی تست شده‌اند؛
  • Rollback/Recovery مستقل و On-call حاضر است؛
  • داده، پرداخت، پیامک و ایمیل واقعی مسدود/محدودند؛
  • Monitoring و Correlation پیش از Fault سالم‌اند؛
  • Communication و Incident channel آماده است؛
  • Cleanup و Verification پس از Stop تعریف شده‌اند.

بخش REL ۱۲ در AWS Well-Architected تست الزامات Function/Performance، تحلیل Incident، Chaos و GameDayهای منظم را کنار هم می‌گذارد. ابزار Fault بدون Playbook و Remediation loop بلوغ ایجاد نمی‌کند.

Observability بخشی از Oracle است، نه تزئین Dashboard

برای اثبات مهار و Recovery باید Logical request را از Retry attempt، User error را از Dependency error و Business outcome را از HTTP status جدا کنید. راهنمای OpenTelemetry درباره Observability نقش Trace، Metric و Log را توضیح می‌دهد؛ اما Instrumentation باید با Correlation و Cardinality کنترل‌شده برای سؤال آزمون طراحی شود.

سیگنال‌های حداقلی

  • User: success/degraded/rejected/unknown با Latency؛
  • Business: Order finalization، Charge، Reconciliation mismatch؛
  • Dependency: Call، Attempt، Timeout، Reject، Circuit state؛
  • Resource: CPU، Memory، Pool، Queue، Thread، FD؛
  • Recovery: Detect/Mitigate/Restore/Drain/Verify timestamps؛
  • Data: Duplicate، Loss، Stale، Conflict، DLQ؛
  • Experiment: Run ID، Target، Fault on/off و Stop reason.

Run ID باید از Fault injector تا Trace، Log، Metric و Audit قابل‌جست‌وجو باشد. نباید PII، PAN، OTP یا Token را برای Correlation وارد Telemetry کنید.

Failure Evidence Pack چه دارد؟

  1. Test contract و نسخه Architecture/Config؛
  2. Baseline run و Fault run با Load یکسان؛
  3. Timeline دقیق Fault، Detect، Stop، Mitigate، Restore و Verify؛
  4. Targetهای واقعی و Blast radius مشاهده‌شده؛
  5. User/business/data/resource Oracles؛
  6. Trace نمونه با Logical ID و Attempt ID جدا؛
  7. Queue/DB/Ledger reconciliation؛
  8. تفاوت Expected و Observed؛
  9. Classification: Pass، Hypothesis rejected، Inconclusive یا Safety stop؛
  10. Root-cause hypothesis، Remediation owner و Regression link.

اگر Failure متناوب یا زمان‌بندی‌محور است، روش Evidence و بازتولید در راهنمای باگ Cannot Reproduce کمک می‌کند Observation را از فرضیه جدا نگه دارید.

سناریوی کامل ایرانی: Checkout و PSP

هدف و Baseline

در Load عادی ۵۰ Order بر ثانیه، ۹۹٫۵٪ Intentهای پرداخت باید در ۳۰ ثانیه به Final یا Pending قابل‌پیگیری برسند؛ p95 پاسخ اولیه زیر ۲ ثانیه باشد؛ برای هر Intent حداکثر یک Charge و Ledger متوازن باشد. داده مصنوعی است و PSP با Fake قابل‌برنامه‌ریزی جایگزین می‌شود.

Failure set

  • ۵٪ Timeout قبل از Commit؛
  • ۵٪ Timeout بعد از Commit؛
  • ۴۲۹ با Retry-After؛
  • Callback تکراری و خارج از ترتیب؛
  • Queue consumer به‌مدت ۹۰ ثانیه متوقف؛
  • Latency p99 به ۸ ثانیه می‌رسد؛
  • Replica خواندن ۳۰ ثانیه عقب است؛
  • یک Payment instance هم‌زمان Restart می‌شود.

Expected behavior

  • Timeout قبل از Commit فقط در Budget و با همان Idempotency key Retry شود؛
  • Timeout بعد از Commit به Duplicate Charge منجر نشود و State مبهم Reconcile شود؛
  • ۴۲۹ بدون موج هم‌زمان و مطابق Policy Backoff شود؛
  • Callback تکراری Side effect تازه نسازد؛
  • Backlog پس از Recovery با Rate کنترل‌شده و بدون اثر بر Checkout Drain شود؛
  • پس از Circuit open، UI «در حال بررسی» با شناسه پیگیری نشان دهد، نه «پرداخت ناموفق» قطعی؛
  • Notification failure، نتیجه مالی را Rollback نکند؛
  • تمام Amountها Canonical IRR باشند؛ تومان فقط Presentation است.

ریسک‌های بومی

  • ارقام فارسی/عربی/لاتین در شناسه و مبلغ؛
  • ی/ی، ک/ک و ZWNJ در نام پذیرنده؛
  • Timestamp UTC برای Event و Asia/Tehran/جلالی فقط برای نمایش؛
  • Callback چند PSP با قرارداد Error متفاوت؛
  • اختلال اینترنت یا دسترسی سرویس ثالث؛
  • عدم امکان ارسال واقعی پیامک، OTP یا پرداخت در Test؛
  • حریم خصوصی موبایل، شناسه ملی و رسید.

ماتریس آزمون تاب‌آوری پرداخت

Fault Mechanism under test Pass evidence
Timeout before commit Bounded retry + same key یک Intent، حداکثر یک Charge
Timeout after commit Idempotency + Query/Reconcile نتیجه نهایی بدون Duplicate
Hard PSP outage Circuit + Load shed Call amplification محدود و UX صریح
Slow PSP Deadline + Bulkhead Checkout غیرپرداختی سالم می‌ماند
Duplicate callback Inbox dedup + State rule یک Ledger transition
Out-of-order callback Monotonic state/Version Final به Pending برنمی‌گردد
Consumer outage Durability + Backpressure Loss صفر و Drain در Budget
Replica lag Read consistency policy State گمراه‌کننده نمایش داده نمی‌شود
Instance restart In-flight ownership کار نه گم و نه دوبار اعمال می‌شود
Recovery surge Drain throttle Online Goodput و Dependency سالم

ابزار را بر اساس Fault و Evidence انتخاب کنید

هیچ ابزار واحدی همه Failureها را با Fidelity مناسب نمی‌سازد:

نیاز گزینه محدودیت آزمون
Fault در کد/State Fake clock، Test double، Fault hook Network/infra واقعی نیست
TCP latency/reset/toxicity Proxy مانند Toxiproxy Semantics PSP را نمی‌فهمد
Linux network/resource NetEm/cgroup/process controls نیازمند Isolation و Cleanup
Kubernetes disruption Eviction/Pod/Node/Chaos tools PDB و provider behavior متفاوت
Cloud fault Provider Fault Injection service Scope، Identity، هزینه و Lock-in
Application patterns Resilience library/SDK Config درست را تضمین نمی‌کند

Toxiproxy یک Proxy برای شبیه‌سازی شرایط شبکه در تست است. PoC باید نشان دهد Fault واقعاً به Target درست می‌رسد، قابل‌تکرار و قابل‌پاک‌سازی است، Safety control دارد و Evidence قابل Correlation می‌سازد؛ محبوبیت ابزار معیار قبولی نیست.

Resilience Test را در CI و عملیات زمان‌بندی کنید

Lane نمونه Gate
PR Unit fault، Fake dependency، Idempotency race کوچک Invariant و Config regression
Merge/Nightly Proxy fault، DB/Queue، Load متوسط Mechanism و Recovery budget
Pre-release Top Failure mode + Peak ratio + Failover Critical-flow decision
GameDay چند تیم، Runbook، Failure ترکیبی Readiness و Remediation
Production canary Target کوچک، Synthetic/low-risk SLO guardrail و Stop فوری

Regression پایدار را خودکار کنید؛ Fault پرریسک، Restore هزینه‌بر یا آزمایش نیازمند قضاوت را الزاماً روی هر Commit اجرا نکنید. هر Lane باید Owner، Budget، Environment و Artifact داشته باشد.

متریک‌های سالم برنامه تست تاب‌آوری

  • Critical-flow Failure-mode coverage بر اساس Risk، نه تعداد Fault؛
  • TTD، TTM، TTR، Backlog drain و Data verification جدا؛
  • User Goodput و Degraded-success rate؛
  • Retry amplification = downstream attempts / logical requests؛
  • Duplicate/Lost/Unknown side effects؛
  • Circuit open/half-open outcome و False trip؛
  • Blast-radius containment برای Control flow/Tenant/Zone؛
  • Manual steps و Recovery toil؛
  • Inconclusive rate به‌علت Environment/Telemetry؛
  • Remediation completion و Regression recurrence؛
  • Escaped resilience incidents و Gap علت؛
  • هزینه هر Signal مفید و Review time.

«تعداد Chaos experiment» یا «درصد سرویس‌های دارای Circuit breaker» Outcome نیست. ممکن است تعداد بالا برود و Duplicate مالی همچنان دیده نشود.

ضدالگوهای رایج

  1. Kill کردن Pod به‌عنوان کل Resilience: شکست‌های مبهم و داده‌ای نادیده می‌مانند.
  2. شروع از Production: Readiness و Fidelity لایه‌های کم‌خطر استفاده نشده‌اند.
  3. Retry همه خطاها: Error دائمی و Overload تشدید می‌شود.
  4. Idempotency فقط در Client: Atomic server contract و Race پوشش ندارد.
  5. Circuit breaker برابر موفقیت: UX، Recovery و Half-open سنجیده نشده‌اند.
  6. ۲۰۰ برابر سلامت: Business/Data invariant گم می‌شود.
  7. Recovery برابر Process up: Backlog و Reconciliation باقی است.
  8. Fault بدون Load: Cascade در Capacity واقعی کشف نمی‌شود.
  9. Load بدون Failure: Degraded path هیچ‌گاه تمرین نمی‌شود.
  10. یک Timeout برای همه Dependencyها: Deadline و Latency context نادیده است.
  11. Fallback پنهان: داده Stale بدون برچسب به کاربر داده می‌شود.
  12. Fault random بدون سؤال: خروجی جالب اما تصمیم‌ناپذیر است.
  13. Tool-first: Scope و Oracle قربانی قابلیت Demo می‌شوند.
  14. Postmortem بدون Regression: یادگیری دوباره از بین می‌رود.

برنامه ۳۰روزه پیاده‌سازی

هفته اول: جریان و Failure mode

  • سه Critical Flow و مالک آن‌ها را انتخاب کنید؛
  • Dependency/Side effect/State map بسازید؛
  • Failure modeها را با Incident و Risk اولویت دهید؛
  • Invariant و Recovery timeline بنویسید.

هفته دوم: آزمون مکانیزم‌ها

  • Timeout/Deadline/Cancellation را در Component test پوشش دهید؛
  • Retry budget و Idempotency race را اجرا کنید؛
  • Circuit/Bulkhead/Degradation را با Oracle مستقل بسنجید؛
  • Telemetry Logical request/Attempt را جدا کنید.

هفته سوم: Integration و Recovery

  • Proxy/Queue/DB failure با Load کنترل‌شده اجرا کنید؛
  • Backlog drain و Reconciliation را اندازه بگیرید؛
  • Environment gap و Inconclusiveها را رفع کنید؛
  • Evidence pack و Runbook را استاندارد کنید.

هفته چهارم: GameDay و Regression

  • GameDay کم‌خطر با Safety gate اجرا کنید؛
  • یافته‌ها را Owner/Deadline و Test regression بدهید؛
  • PR/Nightly/Release lane را تفکیک کنید؛
  • Metrics نتیجه‌محور و برنامه ۶۰ روز بعد را تأیید کنید.

چک‌لیست تست تاب‌آوری سیستم

  • □ Critical Flow و Outcome کاربر/کسب‌وکار روشن است.
  • □ Failure mode از Risk/Incident/Architecture آمده است.
  • □ Baseline Load، Data، Topology و Config نسخه‌دارند.
  • □ Fault profile و Locus دقیق تعریف شده‌اند.
  • □ Expected degraded behavior قابل‌مشاهده است.
  • □ Business/Data invariant مستقل نوشته شده است.
  • □ Timeout، Deadline و Cancellation پوشش دارند.
  • □ Retry محدود، تصادفی و دارای Budget است.
  • □ Mutating retry قرارداد Idempotency و Reconciliation دارد.
  • □ Circuit، Half-open، Bulkhead و Load shed با Failure/Recovery تست شده‌اند.
  • □ TTD/TTM/TTR/Drain/Verify جدا اندازه‌گیری می‌شوند.
  • □ Recovery زیر Backlog و Load بررسی شده است.
  • □ Safety gate، Stop، Kill switch و Cleanup تمرین شده‌اند.
  • □ Logical request و Downstream attempt جدا Telemetry دارند.
  • □ Evidence شامل User، Business، Data و Resource است.
  • □ PII/Token/پرداخت واقعی از Test و Artifact دور است.
  • □ Pass، Fail، Inconclusive و Safety stop تعریف شده‌اند.
  • □ هر یافته Remediation owner و Regression test دارد.

سؤالات متداول تست تاب‌آوری

تست تاب‌آوری سیستم چیست؟

آزمونی است که رفتار یک جریان حیاتی را هنگام Failure یا Stress می‌سنجد: حفظ خدمت کامل یا کاهش‌یافته، مهار Blast radius، حفظ Invariantهای داده و بازیابی در Budget مشخص. نتیجه باید با شواهد کاربر، کسب‌وکار، داده و منابع پشتیبانی شود.

تفاوت Resilience Testing و Chaos Engineering چیست؟

Resilience Testing هدفِ اعتبارسنجی قابلیت تحمل، مهار و Recovery را دارد و می‌تواند از Unit تا DR drill را شامل شود. Chaos Engineering روش آزمایش‌محوری با Hypothesis، Steady State، Blast Radius و Stop condition است که یکی از ابزارهای این هدف به شمار می‌رود.

آیا Retry همیشه تاب‌آوری را بیشتر می‌کند؟

خیر. Retry فقط برای خطای Transient و عملیات Retry-safe مفید است. بدون Deadline، Backoff/Jitter، Attempt/Budget محدود و Idempotency می‌تواند Overload، Duplicate side effect و Cascading failure بسازد.

آیا تست تاب‌آوری باید در Production اجرا شود؟

خیر. بسیاری از مکانیزم‌ها در Unit، Component، Integration و Staging بهتر و کم‌خطرتر سنجیده می‌شوند. Production فقط برای Gapهایی که در محیط دیگر قابل پاسخ نیستند و پس از Readiness، Authorization، Scope کوچک، Stop خودکار و Recovery آماده قابل‌بررسی است.

چطور بفهمیم Recovery واقعاً کامل شده است؟

Up شدن Process کافی نیست. جریان باید به SLO برگردد، Backlog بدون آسیب Drain شود، Duplicate/Loss/Stale state با Reconciliation رد شود، Data invariant برقرار باشد و Temporary capacity/flag/manual workaround به وضعیت عادی بازگردد.

جمع‌بندی

تست تاب‌آوری با شکستن تصادفی سیستم شروع نمی‌شود؛ با یک Critical Flow، Failure Mode و وعده قابل‌اندازه‌گیری آغاز می‌شود. تیم باید بداند هنگام اختلال چه خدمتی حفظ یا کاهش می‌یابد، کدام Invariant هرگز نباید بشکند، اثر چگونه محدود می‌شود و Recovery از Detect تا Drain و Verify چه Budgetی دارد.

آزمایش Node این راهنما نشان داد دو پیاده‌سازی با ۴۰ Downstream attempt می‌توانند Outcome کاملاً متفاوتی داشته باشند: ۴۰ Charge و ۲۰ Duplicate در برابر ۲۰ Charge و صفر Duplicate. همچنین در قطعی سخت، Retry ساده ۳۰۰ Call ساخت، اما Attempt budget و Circuit باز فشار را به پنج Call محدود کرد و ۹۵ درخواست را صریحاً Shed کرد. نتیجه نه «Retry بد است» و نه «Circuit همه‌چیز را حل می‌کند»؛ نتیجه این است که هر مکانیزم تاب‌آوری باید زیر Failure واقعی، با Oracle صحت، Load، Recovery و Evidence مستقل آزمایش شود.

دیدگاهتان را بنویسید