DDIA·مطالعهSTUDY

نقشه راهRoadmap / فصل 2Chapter 2 / حل تمرین‌هاSolutions

حل آموزشی تمرین‌های فصل 2Chapter 2 exercise solutions, explained

هر حل از سؤال مصاحبه شروع می‌کند، فرض‌های آموزشی را روشن می‌گوید، گام‌به‌گام استدلال می‌کند و یک نمودار دارد که خواندنش را یاد می‌گیری. هر حل به تمرین خودش برمی‌گردد. Each solution starts from the interview question, states its teaching assumptions, reasons step by step, and shows one diagram you learn to read aloud. Every solution links back to its exercise.

33 حل تشریحیworked solutions· متناظر با بخش تمرین‌های فصل 2Companion to Chapter 2 exercises

11 تمرین ۱ — محاسبهٔ بارExercise 1 — load math

L1 · پایهL1 · Foundationsاولویت اصلیCore priority25 دقیقه بحث25 min discussion

بازگشت به صورت تمرینBack to the exercise · ارزیابی و سطحAssessment and level

سؤال مصاحبهInterview question
برای دو میلیون سفارش در روز و اوج ده‌برابری، RPS و تعداد سه پیام داخلی به‌ازای هر سفارش را حساب کن. سپس یک view مشخص تعریف کن که هر سفارش در k مقصد آن نوشته می‌شود؛ write amplification، نرخ خواندن، اندازهٔ payload و lag را مقایسه کن و دربارهٔ materialization تصمیم بگیر.
Calculate average and peak orders/s and three internal notifications per order. Define a materialized view with k destinations, then compare extra writes, read savings, payload size and freshness.

پاسخ ۶۰ثانیه‌ایThe 60-second answer

میانگین ۲ میلیون تقسیم بر ۸۶۴۰۰ ثانیه ≈ ۲۳٫۱ سفارش در ثانیه است و اوج ≈ ۲۳۱٫۵؛ ضرب‌در ۳ پیام داخلی ≈ ۶۹ پیام در ثانیه میانگین و ≈ ۶۹۴ در اوج. این بار راحت در یک صف و یک پایگاه جا می‌شود. سؤال کش جداست: سه پیام داخلی سه نوشتن در کش نیست — اول یک view مشخص تعریف کن که هر سفارش در k مقصد آن نوشته می‌شود، بعد فقط وقتی متریالایز کن که صرفه‌جویی خواندن از k نوشتن اضافه به‌ازای هر سفارش بیشتر باشد.
Average is 2M / 86400 ≈ 23.1 orders/s, peak ≈ 231.5/s; times 3 notifications ≈ 69/s average and ≈ 694/s peak. These rates alone cannot establish queue or database capacity; payload, request work, indexes and fsync must be measured. The cache question is separate: three notifications are not three cache writes — define one view where each order lands in k destinations, then materialize it only when read savings beat k extra writes per order.

صورت مسئله و فرض‌هاProblem and assumptions

  • روز = ۸۶۴۰۰ ثانیه؛ ضریب اوج ۱۰ فرض آموزشی است، نه ردپای اندازه‌گیری‌شده.
  • یک رویداد سفارش ≈ ۱ کیلوبایت (اندازه آموزشی)؛ تحویل پیام‌ها at-least-once است.
  • کش، سطر خلاصهٔ سفارش با کلید order_id در k مقصد منطقی دارد؛ هزینهٔ replica فیزیکی را جدا حساب می‌کنیم.
  • Day = 86400 s; peak factor 10 is a teaching assumption, not a measured trace.
  • One order event ≈ 1 KB payload (teaching size); notification delivery is at-least-once.
  • The cache holds order-summary rows keyed by order_id in k logical destinations; physical replication costs are counted separately.

چرا این روش و این عددها؟Why this method and these numbers?

تقسیم سفارش روزانه بر ثانیه‌های روز، واحد را از سفارش/روز به سفارش/ثانیه تبدیل می‌کند؛ ضرب در سه فقط نرخ پیام را می‌دهد. تعداد مقصدهای کش k متغیر مستقلی است، پس نرخ نوشتن اضافه λ×k است. شرط سود کش از کم‌کردن هزینهٔ طراحی بدون کش و با کش به دست می‌آید؛ همهٔ هزینه‌ها باید در یک واحد مثل CPU-ms/ثانیه یا پول/ثانیه باشند. با دوبرابرشدن k هزینهٔ نگهداری دوبرابر می‌شود، حتی اگر تعداد اعلان ثابت بماند.

Dividing daily orders by seconds/day converts orders/day to orders/s; multiplying by three gives notification rate only. Cache destination count k is independent, so added writes are λ×k. The cache break-even inequality subtracts the with-cache cost from the without-cache cost; use one unit such as CPU-ms/s or currency/s. Doubling k doubles maintenance cost even with unchanged notifications.

مدل ذهنیMental model

دو ضرب متفاوت این‌جا زندگی می‌کنند و هرگز نباید قاطی شوند: پیام به‌ازای سفارش (خاصیت گردش‌کار) در برابر نوشتن کش به‌ازای سفارش (خاصیت viewای که تو طراحی می‌کنی: k مقصد). اولی داده شده، دومی انتخاب توست.
Two different multiplications live here and must never be merged: messages per order (a property of the workflow) versus cache writes per order (a property of the view you design: k destinations). The first is given; the second is your choice.

حل گام‌به‌گامStep by step

  1. اول نرخ‌ها ۲٬۰۰۰٬۰۰۰ ÷ ۸۶۴۰۰ ≈ ۲۳٫۱ سفارش در ثانیه میانگین و با ضریب ۱۰ ≈ ۲۳۱٫۵ در اوج. این دو عدد را همیشه جفتی نگه دار.
  2. بعد fan-out گردش‌کار هر سفارش ۳ پیام داخلی می‌سازد: میانگین ≈ ۶۹٫۴ و اوج ≈ ۶۹۴ پیام در ثانیه. با payload آموزشی ۱ کیلوبایت، اوج ≈ ۰٫۷ مگابایت در ثانیه است — یک صف معمولی جواب می‌دهد.
  3. قبل از شمردن، view را تعریف کن حالا view را تعریف کن: سطر خلاصه سفارش با کلید شناسه سفارش در k مقصد (مثلاً ۲ ریجن × ۲ رپلیکا یعنی k=۴). هر سفارش دقیقاً k نوشتن اضافه می‌سازد — نه ۳.
  4. قاعده سربه‌سر را اعمال کن نامساوی تصمیم: اگر (خواندن در ثانیه × ارزش یک خواندن سریع‌تر) از (سفارش در ثانیه × k × هزینه یک نوشتن) بیشتر شد، متریالایز کن؛ وگرنه read-through با TTL کوتاه.
  1. Rates first 2,000,000 ÷ 86,400 ≈ 23.1 orders/s average, ≈ 231.5 at a 10x peak. Always keep the pair together.
  2. Then the workflow fan-out Each order makes 3 internal messages: ≈ 69.4 average, ≈ 694/s at peak. At the teaching payload of 1 KB, peak ≈ 0.7 MB/s — one ordinary queue handles it.
  3. Define the view before counting Now define the view: order-summary rows keyed by order id in k destinations (e.g. 2 regions × 2 replicas, k=4). Each order adds exactly k extra writes — not 3.
  4. Apply the break-even rule Decision inequality: materialize when (reads/s × value of one faster read) exceeds (orders/s × k × cost of one write); otherwise read-through with a short TTL.

مثال محاسبه‌شدهWorked example

میانگین: ۲٬۰۰۰٬۰۰۰ ÷ ۸۶۴۰۰ = ۲۳٫۱۵ سفارش در ثانیه. اوج (۱۰ برابر): ۲۳۱٫۵. پیام‌ها: ≈ ۶۹٫۴ میانگین و ≈ ۶۹۴٫۴ در اوج. با payload آموزشی ۱ کیلوبایت، ترافیک اوج ≈ ۰٫۷ مگابایت در ثانیه است.

مثال view با k=۴: هر سفارش ۴ نوشتن اضافه (≈ ۹۲۶ در ثانیه در اوج). اگر view پنج‌هزار خواندن در ثانیه بدهد و هرکدام ۸ میلی‌ثانیه ذخیره کند، کار ذخیره‌شده نوشتن‌ها را محو می‌کند. با ۵ خواندن در ثانیه، متریالایزیشن می‌بازد — read-through بده. همان حساب، حکم برعکس.

Average: 2,000,000 / 86,400 = 23.15 orders/s. Peak (10x): 231.5/s. Notifications: ≈ 69.4/s average, ≈ 694.4/s peak. At a teaching payload of 1 KB, peak traffic ≈ 0.7 MB/s — one partitioned queue handles it.

View with k = 4: each order adds 4 cache writes (≈ 926/s at peak). If the view serves 5,000 reads/s saving an 8 ms DB read each, the saved work dwarfs the extra writes. At 5 reads/s, materialization loses — serve read-through. Same arithmetic, opposite verdict.

نمودار اسمبل نهاییFinal assembly diagram · محاسبه بار و تصمیم متریالایزیشن (اعداد آموزشی)Load math and the materialization decision (teaching numbers) · نمودار + جدول تصمیمplot + decision tableباز کردن در صفحه جدا ↗Open standalone ↗

دانلود SVG مستقلDownload standalone SVG

روایت مدرس / چطور این نمودار را بخوانیمTeacher narration / how to read this diagram
داری هفته لانچ یک خط سفارش را سایز می‌کنی: رویدادها می‌آیند، سه سرویس باید باخبر شوند و کسی پیشنهاد می‌دهد یک view را از قبل در k کپی محاسبه کنی. خواندن را از دو جفت میله سمت چپ شروع کن.
You are sizing the launch week of an order pipeline: events flow in, three services must hear about each, and someone proposes precomputing a view into k copies. Start from the two bar pairs on the left.
  1. میله‌های چپ: میانگین ۲۳٫۱ در برابر اوج ۲۳۱٫۵ سفارش در ثانیه — یک باند ۱۰ برابری که باید برایش ظرفیت بگذاری.
  2. میله‌های وسط: همان دو نرخ ضرب‌در ۳ پیام (≈ ۶۹ و ≈ ۶۹۴ در ثانیه) — fan-out گردش‌کار که از صف می‌گذرد، نه به کش می‌رود.
  3. پنل راست: viewای که تعریف می‌کنی — یک سفارش داخل، k نوشتن بیرون — با اندازه payload و بودجه lag کنارش.
  4. نوار پایین: نامساوی سربه‌سر که حساب را به تصمیم متریالایزیشن تبدیل می‌کند.
  1. Left bars: average 23.1 vs peak 231.5 orders/s — a 10x band you must provision for, not the average alone.
  2. Middle bars: the same two rates times 3 notifications (≈ 69/s, ≈ 694/s) — workflow fan-out through a queue, not into a cache.
  3. Right panel: the view you define — one order in, k writes out — with payload size and lag budget written next to it.
  4. Bottom strip: the break-even inequality that turns the arithmetic into a materialization decision.

نقطهٔ تصمیم: نقطه تصمیم سطر سربه‌سر است: خواندن در ثانیه × ارزش یک خواندن ذخیره‌شده در برابر k × هزینه یک نوشتن اضافه.

رد خرابی: خرابی مشتری داغ را دنبال کن: میله k نوشتن یک مستأجر ۱۰۰ برابر می‌شود ولی بقیه تخت می‌مانند. سطر سربه‌سر فقط برای همان مستأجر برعکس می‌شود — و به همین دلیل راه‌حل (ایزوله، سقف، ناهمگام) per-tenant است نه سراسری.

تضمین نشان‌داده‌شده: شکل صداقت حسابی را تضمین می‌کند: میانگین، اوج و fan-out به‌ازای سفارش روی یک مقیاس. اندازه واقعی پروداکشن را تضمین نمی‌کند.

تضمین نشان‌نداده‌شده: درباره معنای تحویل (تلاش مجدد at-least-once، ترتیب) یا این‌که کدام مقصد system of record است چیزی نمی‌گوید.

Decision point: The decision point is the break-even row: reads/s × value of one saved read versus k × cost of one extra write.

Failure trace: Trace the hot-tenant failure: the k-writes bar for one tenant grows 100x while the rest stay flat. The break-even row flips for that tenant alone — which is why the fix (isolate, cap, async) is per-tenant, not global.

Guarantee shown: The figure guarantees arithmetic honesty: averages, peaks and per-order fan-out on one scale. It does not guarantee production sizing — payload sizes, growth and real peak shapes must be measured.

Not shown: It says nothing about delivery semantics (at-least-once retries, ordering) or which destination is the system of record.

خودآزماییSelf-check

پهنای باند پیام‌های اوج را دوباره حساب کن اگر payloadها ۱۰۰ کیلوبایت بودند نه ۱ کیلوبایت. اول چه چیزی می‌شکند؟

پاسخ: پیام‌های اوج ≈ ۶۹۴ در ثانیه با ~۱ کیلوبایت ≈ ۰٫۷ مگابایت در ثانیه — برای یک صف ناچیز است. اگر ضریب اوج ۱۰۰ بود یا payloadها ۱ مگابایت، عمق صف و lag را دوباره چک کن؛ روش همان است.

Recompute the peak notification bandwidth if payloads were 100 KB instead of 1 KB. What breaks first?

Answer: Peak notifications ≈ 694/s at ~1 KB ≈ 0.7 MB/s — negligible for one queue. If the peak factor were 100 or payloads 1 MB, recheck queue depth, consumer lag and per-destination write capacity; the method is identical.

گزینه‌های جایگزینAlternatives

  • کش read-through با TTL کوتاه: هیچ نوشتن اضافه‌ای ندارد، فقط اولین خواندن بعد از انقضا یک خواندن از پایگاه می‌پردازد. وقتی خواندن کم است یا داده مدام عوض می‌شود بهتر است.
  • Read-through cache with short TTL: no extra writes at all, first read after expiry pays one DB fetch. Better when reads are rare or data changes constantly.

کد قابل اجراRunnable code

اجراشده و تأییدشده در همین محیطExecuted and verified in this environment

این کد آموزشی و ساده‌سازی‌شده است؛ فقط برای آشنایی با ایده. کد واقعی پروداکشن ملاحظات بسیار بیشتری دارد و هیچ‌کس این‌شکلی کد پروداکشن نمی‌زند.Simplified teaching code for building intuition only — not production code; nobody ships production code like this.

// Teaching math: 2M orders/day, 10x peak, 3 notifications per order,
// plus the materialized-view break-even verdict. All inputs labelled.
public class LoadDemo {
    public static void main(String[] a) {
        double ordersPerDay = 2_000_000;
        double avg = ordersPerDay / 86400.0;      // orders/s average
        double peak = avg * 10.0;                 // 10x teaching peak factor
        double notifAvg = avg * 3;                // 3 internal notifications/order
        double notifPeak = peak * 3;
        double peakMBs = notifPeak * 1024 / (1024 * 1024); // 1 KB teaching payload
        int k = 4;                                // view destinations: 2 regions x 2 replicas
        double extraWritesPeak = peak * k;        // NOT 3: k cache writes per order
        // Break-even: materialize iff reads/s x saved ms per read beats orders/s x k x write cost.
        double readWin = 5000, savedMs = 8, writeCostMs = 2;   // busy view: wins
        double readLose = 5;                                   // quiet view: loses
        boolean verdictWin = (readWin * savedMs) > (avg * k * writeCostMs);
        boolean verdictLose = (readLose * savedMs) > (avg * k * writeCostMs);
        System.out.printf("avg orders/s      = %.2f%n", avg);
        System.out.printf("peak orders/s     = %.1f%n", peak);
        System.out.printf("avg notif/s       = %.1f%n", notifAvg);
        System.out.printf("peak notif/s      = %.1f%n", notifPeak);
        System.out.printf("peak traffic      = %.2f MB/s (one queue holds it)%n", peakMBs);
        System.out.printf("peak extra writes = %.0f/s (k=%d)%n", extraWritesPeak, k);
        System.out.println("busy view verdict = " + (verdictWin ? "MATERIALIZE" : "READ-THROUGH"));
        System.out.println("quiet view verdict= " + (verdictLose ? "MATERIALIZE" : "READ-THROUGH"));
        if (Math.abs(avg - 23.15) > 0.01) throw new AssertionError(avg);
        if (Math.abs(notifPeak - 694.4) > 0.1) throw new AssertionError(notifPeak);
        if (!verdictWin || verdictLose) throw new AssertionError("break-even flipped");
        System.out.println("OK");
    }
}

اجرا: javac LoadDemo.java && java LoadDemo — فقط استاندارد جاوا، فوری.

خروجی مورد انتظار: میانگین ۲۳٫۱۵، اوج ۲۳۱٫۵، پیام اوج ۶۹۴٫۴ (۰٫۶۸ مگابایت در ثانیه)، نوشتن اضافه اوج ۹۲۶ با k=۴؛ حکم view پرترافیک MATERIALIZE و view کم‌خواننده READ-THROUGH؛ OK. (خروجی واقعی همین محیط)

Run: javac LoadDemo.java && java LoadDemo — stdlib Java only, instant.

Expected output: avg 23.15, peak 231.5, peak notifications 694.4 (0.68 MB/s), peak extra writes 926 (k=4); busy view MATERIALIZE, quiet view READ-THROUGH; OK. (actual output of this environment)

آزمون صحت و معیار عملیاتیCorrectness checks and operational criteria

اندازه بگیر: سفارش پایدار در ثانیه و ضریب اوج از لاگ سفارش؛ lag مصرف‌کننده روی هر ۳ صف؛ تأخیر نوشتن هر مقصد؛ lag نما در برابر بودجه اعلام‌شده؛ تست آشوب — یک مقصد view را بکش و مطمئن شو خواندن‌ها به پایگاه برمی‌گردند.
Measure: sustained orders/s and peak factor from the order log; consumer lag on each of the 3 queues; per-destination write latency; view lag against the stated budget; chaos check — kill one view destination and confirm reads degrade to the database.

پاسخ سؤال‌های پیگیریFollow-up answers

پیگیری (سینیور): اگر یک مشتری بزرگ k را صد برابر کند چه اتفاقی برای tail latency می‌افتد؟Follow-up (Senior): What happens to tail latency when one large customer increases k by a factor of 100?

حالا یک مشتری روی یک شارد غالب است: نرخ نوشتنش ۱۰۰ برابر شده، پس عمق صف و p99 همان شارد رشد می‌کند ولی بقیه تخت می‌مانند — همان hot key کلاسیک. میانگین آن را پنهان می‌کند؛ صدک‌های per-shard و per-tenant آشکارش می‌کنند. راه‌حل‌ها به ترتیب: جدا کردن مشتری بزرگ روی پارتیشن خودش، سقف یا دسته‌بندی k نوشتنش، سرو ناهمگام خواندن‌هایش با lag اعلام‌شده، و هشدار روی سهم نوشتن هر مستأجر پیش از اشباع شارد.

One tenant now dominates one shard: its write rate is 100x, so that shard's queue depth and p99 grow while others stay flat — the classic hot key. Averages hide it; per-shard and per-tenant percentiles reveal it. Fixes in order: isolate the big tenant on its own partition, cap or batch its k writes, serve its reads asynchronously with a stated lag, and alert on per-tenant write share before it saturates a shard.

اشتباهات رایجCommon mistakes

  • گفتن «۳ سرویس = ۳ نوشتن کش». پیام‌ها به صف سرویس‌ها می‌روند؛ نوشتن‌های کش به مقصدهای view می‌روند. k متفاوت، هزینه متفاوت، قرارداد تازگی متفاوت.
  • فقط میانگین (۲۳ در ثانیه) را گفتن و برای همان ظرفیت گرفتن. ظرفیت، صف و بودجه تأخیر باید اوج اعلام‌شده (۲۳۱ در ثانیه) به‌علاوه حاشیه را جذب کنند.
  • Saying '3 services = 3 cache writes'. Notifications go to service queues; cache writes go to view destinations. Different k, different cost, different freshness contract.
  • Quoting only the average (23/s) and sizing for it. Capacity, queues and latency budgets must absorb the stated peak (231/s) plus headroom.

منابع:Sources: Amazon · SDE III interview preparation · Google SRE · implementing SLOs

22 تمرین ۲ — نوشتن SLO و SLAExercise 2 — writing SLO and SLA

L2 · SeniorL2 · Seniorاولویت اصلیCore priority30 دقیقه بحث30 min discussion

بازگشت به صورت تمرینBack to the exercise · ارزیابی و سطحAssessment and level

سؤال مصاحبهInterview question
برای استعلام وضعیت سفارش SLI و SLO با بازه اندازه‌گیری، صدک تأخیر و دسترس‌پذیری تعریف کن؛ مرز success و timeout و خطای وابستگی را روشن کن؛ error budget و هشدار burn rate را طراحی کن و SLO داخلی را از SLA قراردادی جدا کن.
Define SLI/SLO with measurement windows, latency percentiles and availability for order-status reads; draw the line between success, timeout and dependency failure; design error budget and burn-rate alerts; separate internal SLO from contractual SLA.

پاسخ ۶۰ثانیه‌ایThe 60-second answer

برای استعلام وضعیت سفارش دو SLI با مخرج مشخص داریم: دسترس‌پذیری = پاسخ معنایی معتبر ÷ همهٔ درخواست‌های معتبر، و تأخیرِ مشروط = پاسخ موفق زیر ۳۰۰ms ÷ همهٔ پاسخ‌های موفق. هدف‌های آموزشی در پنجرهٔ ۳۰روزه، ۹۹٫۹٪ دسترس‌پذیری و ۹۹٪ پاسخ‌های موفق زیر آستانه‌اند. timeout در دسترس‌پذیری شکست است ولی در مخرج SLI تأخیرِ مشروط وارد نمی‌شود؛ پس هر دو را کنار هم بخوان. SLO هدف داخلی و SLA قرارداد با پیامد مشخص است.
Use explicitly scoped SLIs for order status: availability = semantically valid responses / eligible requests; conditional latency = successful responses under 300ms / all successful responses. Teaching 30-day goals are 99.9% availability and 99% of successful responses under threshold. A timeout fails availability but is excluded from conditional latency, so read both together. SLOs are internal objectives; SLAs are contracts with defined remedies.

صورت مسئله و فرض‌هاProblem and assumptions

  • آستانه‌ها (۳۰۰ میلی‌ثانیه، ۹۹٫۹٪، پنجره ۳۰ روزه) فرض آموزشی‌اند؛ واقعی‌ها از مطالعه تحمل کاربر و هزینه کسب‌وکار می‌آیند.
  • تایم‌اوت = ۱ ثانیه برای هر تلاش؛ وابستگی = خواندن موجودی/ریسک پشت فراخوان وضعیت.
  • هشدارها باید به یک اقدام برسند (rollback، shed load، fail open) — نمودار بدون اقدام کاغذدیواری است.
  • Thresholds (300 ms, 99.9%, 30-day window) are teaching assumptions; real ones come from user-tolerance studies and business cost.
  • Timeout = 1 s per attempt; dependency = inventory/risk read behind the status call.
  • Alerts must route to an action (rollback, shed load, fail open) — a chart with no action is wallpaper.

چرا این روش و این عددها؟Why this method and these numbers?

عدد آستانه از نیاز کاربر می‌آید. مثلاً اگر در یک فرض آموزشی، کاربر وضعیت سفارش را در حداکثر ۳ ثانیه بخواهد، بودجهٔ ۰٫۵ ثانیه شبکه + ۰٫۵ render + ۱ صف + ۰٫۷ وابستگی + ۰٫۳ سرویس جمعاً ۳ ثانیه است؛ این تقسیم، شاهد اندازه‌گیری نیست و باید با trace و آزمون کاربر تأیید شود. هدف ۳۰۰ms این پاسخ، بودجهٔ سرویس است؛ سه ثانیه سقف تجربهٔ کامل است. صدک‌های اجزا را نمی‌توان ساده جمع کرد و صدک کل نامید: باید زمان کل درخواست را اندازه گرفت.

در ۵۰میلیون درخواست، بودجهٔ خطای ۹۹٫۹٪ برابر (۱−۰٫۹۹۹)×۵۰میلیون=۵۰هزار شکست است؛ این شمار درخواست است، نه دقیقهٔ قطعی. ۳۰ روز برای دیدن چند چرخهٔ هفتگی فرض شده است. آستانه‌های نمونهٔ ۲٪ بودجه در یک ساعت و ۱۰٪ در شش ساعت، در ترافیک یکنواخت به‌ترتیب burn rate برابر ۱۴٫۴ و ۱۲ می‌دهند: درصد بودجهٔ مصرف‌شده ÷ سهم زمان از ۷۲۰ ساعت. در ترافیک متغیر باید نرخ خطا و حجم واقعی پنجره را بسنجیم. افزایش هدف به ۹۹٫۹۹٪ بودجه را ده برابر کوچک‌تر می‌کند و هزینهٔ قابلیت اطمینان را باید دوباره بررسی کرد.

چرا خودِ سه ثانیه؟ تا وقتی دادهٔ واقعی نداریم، دلیل تجربی برای این عدد نداریم و آن را فقط ورودی سناریو می‌گیریم. برای انتخاب واقعی، نرخ رهاکردن کار، درخواست تکراری و رضایت را در بازه‌های تأخیر بسنج؛ همراه مالک محصول بیشترین تأخیر قابل‌قبول و هزینهٔ کاهش آن را تعیین کن. تقسیم بودجهٔ سه ثانیه، انتخاب این هدف را اثبات نمی‌کند؛ فقط نشان می‌دهد بعد از پذیرش هدف چگونه آن را بین مسیرها بودجه‌بندی می‌کنیم.

Derive thresholds from user needs. For a teaching end-to-end target of three seconds, a provisional budget might be 0.5s network + 0.5s rendering + 1s queue + 0.7s dependency + 0.3s service. This allocation is an assumption requiring traces and user validation. This answer uses 300ms for the service, not the whole journey. Component percentiles do not add into an end-to-end percentile; measure the entire request.

At 50M requests, (1−0.999)×50M=50k allowed failures: a request count, not downtime minutes. Thirty days spans several weekly cycles. Under uniform traffic, consuming 2% of budget in one hour or 10% in six hours implies burn rates 14.4 and 12: budget fraction consumed divided by the time fraction of 720 hours. Variable traffic requires actual error rates and window volumes. Raising the goal to 99.99% shrinks the failure budget tenfold, requiring another cost assessment.

Why three seconds itself? Without user evidence there is no empirical justification; it is a scenario input only. Measure abandonment, repeated requests and satisfaction by latency band, then choose acceptable delay and reduction cost with the product owner. Dividing a three-second budget does not justify choosing that target; it only allocates an already accepted target.

مدل ذهنیMental model

SLI نسبت رویدادهای خوب به رویدادهای واجد شرایط است. SLO هدفی برای همین نسبت در یک پنجره است؛ error budget سهم شکست مجاز را مشخص می‌کند تا تصمیم انتشار و کار قابلیت اطمینان مبنای مشترک داشته باشد. SLA قرارداد با دامنه و پیامد مشخص است و در این مثال آستانهٔ آسان‌تری از SLO دارد؛ این تفاوت یک انتخاب قراردادی است، نه تعریف الزامی SLA.
An SLI is good events divided by eligible events. An SLO targets that ratio over a window. Its error budget bounds tolerated failure and informs release/reliability decisions. An SLA specifies contractual scope and consequences; its looser threshold in this example is a contract choice, not a universal requirement.

حل گام‌به‌گامStep by step

  1. اول صورت‌ها صورت و مخرج هر SLI را بنویس: تأخیر = خواندن موفق زیر ۳۰۰ms ÷ کل خواندن موفق؛ دسترس‌پذیری = پاسخ بدون خطا ÷ کل درخواست. پنجره: ۳۰ روزه غلتان.
  2. خط‌های خرابی را بکش موفقیت معنایی را مستقل از سرعت تعریف کن: پاسخ 2xx با وضعیت درست و تازگی مجاز، موفق است؛ پاسخ موفق کند، SLI تأخیر را بدتر می‌کند. timeout یک‌ثانیه‌ای و خطای وابستگی شکست دسترس‌پذیری‌اند و در SLI تأخیر مشروط شمرده نمی‌شوند؛ به همین دلیل نمودار تأخیر به‌تنهایی کافی نیست.
  3. بودجه و پیامدش بودجه: با SLO ۹۹٫۹٪ روی حجم آموزشی ۵۰ میلیون درخواست، ۵۰٬۰۰۰ خرابی مجاز است. سیاست اتمام: فریز انتشار غیرحیاتی + فقط کار قابلیت اطمینان.
  4. دو سرعت مصرف، دو اقدام هشدارها: fast-burn (۲٪ بودجه در ۱ ساعت → پیج و rollback) و slow-burn (۱۰٪ در ۶ ساعت → تیکت). هر هشدار به یک اقدام ران‌بوک وصل است.
  5. جدا کردن SLO از SLA جدایی SLO/SLA: مثلاً SLA قراردادی ۹۹٫۵٪ ماهانه با اعتبار ۱۰٪ — شل‌تر از SLO تا هفته بد معمولی جریمه مالی نسازد.
  1. Numerators first Write each SLI's numerator and denominator: latency = successful reads under 300ms ÷ all successful reads; availability = non-error responses ÷ all requests. Window: rolling 30 days.
  2. Draw the failure lines Define semantic success separately from speed: a correct, acceptably fresh 2xx succeeds; slow successes degrade latency. One-second timeouts and dependency failures fail availability and are excluded from conditional latency. A latency chart alone is therefore insufficient.
  3. Budget and its consequence Budget: at 99.9% SLO over the teaching 50M requests, 50,000 failures are allowed. Zero policy: freeze non-critical releases + reliability work only.
  4. Two burn speeds, two actions Alerts: fast-burn (2% of budget in 1h → page and rollback) and slow-burn (10% in 6h → ticket). Every alert connects to a runbook action.
  5. Split SLO from SLA SLO/SLA split: e.g. contractual SLA 99.5% monthly with 10% credit — looser than the SLO so a normal bad week never pays money.

مثال محاسبه‌شدهWorked example

حجم آموزشی: ۵۰ میلیون خواندن در ۳۰ روز (≈ ۱۹ در ثانیه). SLO ۹۹٫۹٪ → بودجه = ۵۰٬۰۰۰ خرابی مجاز. پیج fast-burn: ۲٪ (۱٬۰۰۰) در ۱ ساعت. تیکت slow-burn: ۱۰٪ (۵٬۰۰۰) در ۶ ساعت. SLO تأخیر: ۹۹٪ خواندن‌های موفق زیر ۳۰۰ms — SLI جدا.

Teaching volume: 50M status reads / 30 days (≈ 19/s). SLO 99.9% → budget = 0.001 × 50M = 50,000 allowed failures. Fast-burn page: 2% (1,000) within 1 hour. Slow-burn ticket: 10% (5,000) within 6 hours. Latency SLO: 99% of successful reads under 300 ms → at most 1% slow reads, tracked as its own SLI.

گام 1 از 2 · خط فکریStep 1 of 2 · reasoning thread · هشدار burn: پیج سریع با rollback و تیکت کند با تحقیقBurn alerts: fast-burn page with rollback, slow-burn ticket with investigation · توالیsequenceباز کردن در صفحه جدا ↗Open standalone ↗

دانلود نمودار مستقلDownload standalone diagram

روایت مدرس / چطور این نمودار را بخوانیمTeacher narration / how to read this diagram
نیمه‌شب پیجر بیدارت کرده: بودجه دارد می‌سوزد. شکل را بالا‌به‌پایین بخوان — اول مسیر پیج سریع با اقدام rollback، بعد مسیر تیکت کند با تحقیق.
The pager wakes you at night: the budget is burning. Read top to bottom — first the fast-burn page with its rollback action, then the slow-burn ticket with its investigation.
  1. دو فلش اول: مانیتور burn پیج می‌زند (۲٪ بودجه در ۱ ساعت) و آنکال انتشارها را فریز و rollback می‌کند — هشدار به اقدام می‌رسد نه به نمودار.
  2. فلش برگشتی: قطار انتشار «burn تخت شد» را برمی‌گرداند — تا راستی‌آزمایی نشود قفل باز نمی‌شود.
  3. شاخه dashed: burn کند (۱۰٪ در ۶ ساعت) تیکت می‌شود نه پیج — در همان شیفت بررسی می‌شود.
  4. دو فلش آخر: p99 بر اساس گروه برش می‌خورد (ریجن، نسخه، hit/miss) و بزرگ‌ترین گروه اول درست می‌شود — بعد حلقه با سبز شدن burn بسته می‌شود.
  1. First two arrows: the burn monitor pages (2% of budget in 1 hour) and on-call freezes releases and rolls back — the alert ends in an action, not a chart.
  2. Return arrow: the release train reports 'burn flat' — no unfreeze before verification.
  3. Dashed branch: the slow burn (10% in 6 hours) becomes a ticket, not a page — investigated inside the shift.
  4. Last two arrows: p99 is sliced by cohort (region, version, hit/miss) and the biggest cohort is fixed first — then the loop closes as the burn goes green.

نقطهٔ تصمیم: نقطه تصمیم سرعت هشدار است: پیج یعنی اقدام فوری، تیکت یعنی تحقیق در شیفت. سرعت اشتباه یعنی یا خستگی هشدار یا خواب‌ماندگی روی حادثه.

رد خرابی: اگر rollback بدون راستی‌آزمایی قفل را باز کند، همان نسخه خراب دوباره می‌سوزاند؛ اگر burn کند پیج شود، تیم به پیج بی‌اعتنا می‌شود.

تضمین نشان‌داده‌شده: شکل نشان می‌دهد هر سرعت burn به کدام اقدام می‌رسد. آستانه‌های تو را تعیین نمی‌کند — آن‌ها از تحمل کاربر و هزینه می‌آیند.

تضمین نشان‌نداده‌شده: درباره جبران‌های SLA و نسبت‌دادن خطا بین چند سرویس ساکت است — آن‌ها در پنل اسمبلی‌اند.

Decision point: The decision point is the alert speed: a page means act now, a ticket means investigate this shift. The wrong speed means either alert fatigue or sleeping through an incident.

Failure trace: Unfreezing without verifying the burn lets the same bad release burn again; paging a slow burn trains the team to ignore pages.

Guarantee shown: The figure shows which action each burn speed reaches. It does not set your thresholds — those come from user tolerance and cost.

Not shown: It is silent on SLA remedies and multi-service error attribution — those live in the assembly panel.

خودآزماییSelf-check

burn سریع ۲٪ در ۱ ساعت و burn کند ۱۰٪ در ۶ ساعت؛ هرکدام چه اقدامی می‌خواهد و چرا جابه‌جایشان خطرناک است؟

پاسخ: سریع: فریز و rollback فوری با راستی‌آزمایی؛ کند: تیکت و برش p99 در شیفت. جابه‌جایی یعنی یا تأخیر روی حادثه یا خستگی هشدار.

Fast burn is 2% in 1 hour, slow burn 10% in 6 hours; what action does each demand, and why is swapping them dangerous?

Answer: Fast: freeze and roll back now, then verify; slow: ticket and slice p99 inside the shift. Swapping means either a late response or alert fatigue.

نمودار اسمبل نهاییFinal assembly diagram · از کسر SLI تا مصرف بودجه و هشدار (آستانه‌های آموزشی)From SLI fractions to budget burn and alerts (teaching thresholds) · پنل + خط مصرفpanel + burn timelineباز کردن در صفحه جدا ↗Open standalone ↗

دانلود SVG مستقلDownload standalone SVG

روایت مدرس / چطور این نمودار را بخوانیمTeacher narration / how to read this diagram
صفحه وضعیتت تازه کهربایی شده و مدیر محصول می‌پرسد «داریم می‌شکنیم؟» چپ‌به‌راست بخوان: دو کسر، یک بودجه، دو سرعت هشدار.
Your status page just turned amber and the product manager asks 'are we breaching?' Read left to right: two fractions, one budget, two alert speeds.
  1. پنل چپ: دو کسر SLI — تأخیر (سریع ÷ موفق) و دسترس‌پذیری (بدون‌خطا ÷ کل).
  2. میله وسط: بودجه ۳۰ روزه — سبز امن، کهربایی هشدار، قرمز اتمام — با سیاست فریز/rollback زیرش.
  3. نوار راست: دو سرعت مصرف — fast-burn در همان ساعت پیج، slow-burn در همان شیفت تیکت.
  4. خط پایین: جدایی SLO/SLA — بالای آن هدف داخلی، پایینش قرارداد مشتری با جبران.
  1. Left panel: the two SLI fractions — latency (fast ÷ successful) and availability (non-error ÷ total).
  2. Middle bar: the 30-day error budget as a strip — green safe, amber warning, red exhausted — with the freeze/rollback policy under it.
  3. Right strip: the two burn speeds — fast-burn pages within the hour, slow-burn tickets within the shift.
  4. Bottom line: the SLO/SLA split — internal objective above, customer contract with remedies below.

نقطهٔ تصمیم: نقطه تصمیم سطر سیاست بودجه است: روزی که بودجه صفر شد چه می‌شود. عدد بدون پیامد SLO نیست.

رد خرابی: قطعی وابستگی را دنبال کن: بخش قرمز میله دسترس‌پذیری رشد می‌کند ولی تأخیر سبز می‌ماند — یک SLI می‌شکند و دیگری سالم است. همین جدایی دلیل per-SLI بودن هشدارهای burn است.

تضمین نشان‌داده‌شده: شکل نشان می‌دهد کسرها چطور بودجه و بودجه‌ها چطور پیج می‌شوند. آستانه‌های تو را تعیین نمی‌کند — آن‌ها از تحمل کاربر و هزینه کسب‌وکار می‌آیند.

تضمین نشان‌نداده‌شده: جبران‌های SLA، حساب اعتبار یا نسبت‌دادن خطا بین چند سرویس را پوشش نمی‌دهد.

Decision point: The decision point is the budget policy row: what happens the day the budget hits zero. A number without a consequence is not an SLO.

Failure trace: Trace a dependency outage: the availability bar's red segment grows while latency stays green — one SLI breaches, the other holds. That split is why burn alerts are per-SLI.

Guarantee shown: The figure shows how fractions become budgets and budgets become pages. It does not set your thresholds — those come from user tolerance and business cost.

Not shown: It does not cover SLA remedies, credit math, or multi-service error attribution.

خودآزماییSelf-check

یک خواندن بعد از ۱ ثانیه تایم‌اوت می‌شود. SLI تأخیر را می‌شکند، دسترس‌پذیری را، یا هر دو؟

پاسخ: هر دو: تایم‌اوت در هر دو SLI خرابی حساب می‌شود. خطاهای وابستگی به دسترس‌پذیری می‌خورند؛ به تأخیر فقط اگر مسیر تایم‌اوت را بشماری — قاعده را قبل از حادثه بنویس.

A read times out after 1 s. Does it breach the latency SLI, the availability SLI, or both?

Answer: Both: timeouts count as failures in both SLIs. Dependency errors count against availability; against latency only if you count the timeout path — state the rule before the incident.

گزینه‌های جایگزینAlternatives

  • جایگزین دسترس‌پذیری‌اول: هنگام پارتیشن خواندن کمی stale را بپذیر وقتی محصول تحملش را دارد — مثلاً آخرین وضعیت معلوم با برچسب «به‌روز شده در X ثانیه پیش». هزینه: UX صریح و متریک تازگی.
  • Availability-first: accept slightly stale reads from any replica during a partition when the product tolerates it — e.g. last-known status with an 'updated Xs ago' label. Cost: explicit staleness UX and a freshness metric you must monitor.

سناریوهای خرابیFailure scenarios

  • قطعی وابستگی: خواندن وضعیت هم fail می‌شود مگر مسیر degraded با برچسب صریح staleness آخرین وضعیت معلوم را بدهد — از قبل تصمیم بگیر کدام خرابی‌ها اجازه انتشار دارند.
  • تمام شدن بودجه در روز ۱۰ از ۳۰: انتشارهای غیرحیاتی را فریز کن. بودجه بدون پیامد تزئین است.
  • Dependency outage: status reads fail too unless a degraded path serves last-known status with an explicit staleness label — decide in advance which failures may propagate.
  • Budget exhausted on day 10 of 30: freeze non-critical releases, spend headroom only on reliability work. A budget with no consequences is decoration.

کد قابل اجراRunnable code

اجراشده و تأییدشده در همین محیطExecuted and verified in this environment

این کد آموزشی و ساده‌سازی‌شده است؛ فقط برای آشنایی با ایده. کد واقعی پروداکشن ملاحظات بسیار بیشتری دارد و هیچ‌کس این‌شکلی کد پروداکشن نمی‌زند.Simplified teaching code for building intuition only — not production code; nobody ships production code like this.

// Teaching numbers: 50M status reads per 30-day window, 99.9% SLO.
// Budget, fast-burn page and slow-burn ticket thresholds.
public class SloBudget {
    public static void main(String[] a) {
        long requests = 50_000_000L; // teaching volume over the 30-day window
        double slo = 0.999;
        double budget = (1 - slo) * requests; // allowed failures
        System.out.printf("allowed failures: %,.0f%n", budget);
        // fast-burn: 2% of budget in 1 hour -> page; slow-burn: 10% in 6h -> ticket
        System.out.printf("page when failures/hour > %,.0f%n", 0.02 * budget);
        System.out.printf("ticket when failures/6h > %,.0f%n", 0.10 * budget);
        if (Math.abs(budget - 50_000) >= 1) throw new AssertionError(budget);
        System.out.println("OK");
    }
}

اجرا: javac SloBudget.java && java SloBudget — فقط استاندارد جاوا، فوری. requests و slo را با پنجره خودت تنظیم کن.

خروجی مورد انتظار: خرابی مجاز: ۵۰٬۰۰۰؛ آستانه پیج ۱٬۰۰۰ در ساعت؛ آستانه تیکت ۵٬۰۰۰ در ۶ ساعت؛ OK. (خروجی واقعی همین محیط)

Run: javac SloBudget.java && java SloBudget — stdlib Java only, instant. Tune requests and slo to your window.

Expected output: allowed failures: 50,000; page threshold 1,000/h; ticket threshold 5,000/6h; OK. (actual output of this environment)

آزمون صحت و معیار عملیاتیCorrectness checks and operational criteria

ماهانه راستی‌آزمایی کن: SLIها از لاگ خام؛ هشدارهای burn روی spike مصنوعی ظرف چند دقیقه؛ دسته‌بندی تایم‌اوت با قاعده ۱ ثانیه در traceها؛ game-day با وابستگی خوابیده و خواندن degraded برچسب‌دار.
Verify monthly: SLIs recomputed from raw logs; burn alerts fire on a synthetic spike within minutes; timeout classification matches the 1 s rule in traces; a game-day with the dependency down confirms labelled-stale degraded reads.

پاسخ سؤال‌های پیگیریFollow-up answers

پیگیری (سینیور): میانگین عالی است ولی p99 خراب؛ کدام گروه کاربران را بررسی می‌کنی؟Follow-up (Senior): The average latency is excellent but p99 is poor. Which user groups do you investigate?

قبل از نظریه، p99 را برش بزن: بر اساس ریجن/AZ، دستگاه یا نسخه اپ، hit/miss کش، شارد پایین‌دستی. مظنون‌های همیشگی: یک شارد کند وابستگی، گروه cache-miss، یا پاسخ‌های پرحجم که دم را تصاحب کرده‌اند. اول بزرگ‌ترین گروه را درست کن، بعد دوباره برش بزن.

Slice p99 before theorizing: by region/AZ, device or app version, cache-hit vs miss, downstream shard. Usual suspects: one slow dependency shard, a cache-miss cohort hitting the database, or large-payload responses dominating the tail. Fix the biggest cohort first, then re-slice. Averages improving while p99 stays bad means you optimized the cohort that was already fine.

اشتباهات رایجCommon mistakes

  • یک عدد برای همه‌چیز («۹۹٫۹٪ سریع و در دسترس»). تأخیر و دسترس‌پذیری دو SLI مستقل‌اند — خطای سریع هنوز خطاست و موفقیت کند هنوز SLO تأخیر را می‌شکند.
  • داشبوردی که کسی به آن عمل نمی‌کند. هر نمودار به آستانه هشدار و اقدام ران‌بوک نیاز دارد.
  • One number for everything ('99.9% fast and available'). Latency and availability are independent SLIs — a fast error is still an error, a slow success still breaches latency.
  • Dashboards nobody acts on. Every chart needs an alert threshold and a runbook action.

منابع:Sources: Google SRE · implementing SLOs · Atlassian · engineering interview handbook

33 تمرین ۳ — حذف SPOFExercise 3 — removing SPOFs

L2 · SeniorL2 · Seniorاولویت اصلیCore priority45 دقیقه بحث45 min discussion

بازگشت به صورت تمرینBack to the exercise · ارزیابی و سطحAssessment and level

سؤال مصاحبهInterview question
یک API ثبت سفارش با پایگاه داده و صف طراحی کن که خرابی یک ماشین یا AZ را تحمل کند. failure domain، quorum، fencing، session state، health check، ظرفیت AZ باقی‌مانده و RPO/RTO را مشخص کن. انتشار سازگار و rollback بدون توقف را توضیح بده.
Design an order API with database and queue that survives one host or one AZ failure. Name failure domains, quorum, fencing, session state, health checks, surviving-AZ capacity and RPO/RTO; explain zero-downtime release and rollback.

پاسخ ۶۰ثانیه‌ایThe 60-second answer

تحمل یک ماشین یا یک AZ: API سفارش stateless پشت load balancer روی ۳ AZ، پایگاه با standby هم‌گام به‌علاوه رپلیکای ناهمگام، صف بادوام تکثیرشده برای سه مصرف‌کننده، state نشست بیرونی. همه failure domainها را نام ببر (ماشین، AZ، control plane، state، شبکه)، در AZهای باقی‌مانده ظرفیت N+1 نگه دار و HA (زنده ماندن) را از backup (بازیابی، RPO/RTO) و DR (ریجن) جدا کن. ارتقای غلتان با expand–migrate–contract تا باینری قدیم و جدید هم‌زیستی کنند.
Survive one host or one AZ: stateless order API behind a load balancer across 3 AZs, database with synchronous standby plus asynchronous replica, durable replicated queue for the three consumers, session state externalized. Name every failure domain (host, AZ, control plane, state, network), keep N+1 capacity in the surviving AZs, and separate HA (survive) from backup (restore, RPO/RTO) and DR (region). Rolling upgrades use expand–migrate–contract so old and new binaries coexist.

صورت مسئله و فرض‌هاProblem and assumptions

  • سه AZ در یک region؛ هر AZ در این طرح محافظه‌کارانه کل peak را تحمل می‌کند. این بیش از حداقل N+1 است و هزینهٔ اضافی دارد.
  • نوشتن سفارش ≈ ۲۳۱ در ثانیه در اوج (از تمرین ۱)؛ RPO ≤ ۱ دقیقه و RTO ≤ ۵ دقیقه اهداف آموزشی‌اند.
  • کلاینت‌ها با backoff و کلید idempotency تلاش مجدد می‌کنند.
  • Three AZs in one region; each handles full peak in this conservative design. This exceeds minimum N+1 capacity and carries extra cost.
  • Order write ≈ 231/s peak (from exercise 1); RPO ≤ 1 min and RTO ≤ 5 min are teaching targets.
  • Clients retry with backoff and idempotency keys.

چرا این روش و این عددها؟Why this method and these numbers?

سه AZ برای تحمل خرابی یک AZ و حفظ quorum اکثریتِ دو از سه انتخاب شده‌اند؛ این انتخاب خرابی region را پوشش نمی‌دهد. اگر هر AZ کل peak را تحمل کند، پس از حذف یکی هنوز ظرفیت کافی داریم ولی هزینه بیش از حداقل N+1 می‌شود. ظرفیت حداقلی برای سه AZ و تحمل فقدان یکی حدود peak/2 در هر AZ، با حاشیهٔ عملیاتی است. RPO یک دقیقه یعنی حداکثر زیان پذیرفتنی داده و RTO پنج دقیقه یعنی سقف توقف؛ آزمایش failover و restore باید هر دو را جدا بسنجند.

Three AZs allow a two-of-three majority after losing one AZ, but do not cover regional failure. Giving each AZ full peak capacity is conservative and costs more than minimum N+1. Three-AZ capacity after one failure needs roughly peak/2 per AZ plus operational margin. One-minute RPO bounds accepted data loss; five-minute RTO bounds outage duration. Verify both through separate failover and restore exercises.

مدل ذهنیMental model

دو ماشین یعنی دو تا از همه‌چیز نیست. اول هر نقطه شکست واحد را بکش — data plane و control plane، stateدار و stateless — بعد هرکدام را روی کاغذ بکش. هرچه را روی کاغذ نکشی، در پروداکشن تو را می‌کشد.
Two machines are not two of everything. Draw every single point of failure first — data plane and control plane, state and stateless — then kill each one on paper. Anything you did not kill on paper will kill you in production.

حل گام‌به‌گامStep by step

  1. فهرست همه SPOFها همه SPOFها را فهرست کن: نمونه API، LB، رکورد DNS، primary پایگاه، صف تک‌نسخه، خط انتشار، انسان آنکال. data plane را از control plane و state را از stateless جدا کن.
  2. مقیاس لایه stateless stateless را افقی کن: API بدون state نشست پشت LB روی ۳ AZ با health check؛ ظرفیت هر AZ به‌تنهایی = اوج کامل (N+1).
  3. داوم state state را بادوام کن: primary + standby هم‌گام + رپلیکای ناهمگام؛ صف تکثیرشده با checkpoint؛ outbox در همان تراکنش سفارش. primary قدیم با epoch فنس می‌شود.
  4. جدایی HA و backup و DR HA را از backup و DR جدا کن: standby هم‌گام = HA (ثانیه)؛ اسنپ‌شات + WAL = backup با RPO/RTO که با drill اثبات می‌شود؛ ریجن دوم = DR.
  5. انتشار و rollback انتشار و rollback: expand–migrate–contract برای schema؛ باینری قدیم و جدید هم‌زیست؛ rollback یعنی برگرداندن مسیر تا وقتی ستون‌های قدیم هستند.
  1. List every SPOF List every SPOF: API instance, LB, DNS record, single DB primary, single-copy queue, deploy pipeline, on-call human. Separate data plane from control plane and state from stateless.
  2. Scale the stateless tier Scale the stateless tier: session-free API behind the LB over 3 AZs with health checks; each AZ alone carries the full peak (N+1).
  3. Durable state Make state durable: primary + sync standby + async replica; replicated queue with checkpoints; outbox in the same order transaction. The old primary is epoch-fenced.
  4. Separate HA, backup, DR Separate HA from backup and DR: sync standby = HA (seconds); snapshot + WAL = backup with drilled RPO/RTO; second region = DR.
  5. Release and roll back Release and roll back: expand–migrate–contract for schema; old and new binaries coexist; rollback means routing back while old columns still exist.

طراحی مشخصConcrete design

مسیر نوشتن: کلاینت → LB (با health check) → هر نمونه API → primary پایگاه (ACK از standby هم‌گام) → سطر outbox در همان تراکنش → صف → ۳ مصرف‌کننده. مسیر خواندن: خواندن وضعیت از کش API یا رپلیکای ناهمگام با lag اعلام‌شده؛ هرگز در مسیر داغ از primary. کلیدها: سفارش‌ها با کلید شناسه سفارش؛ صف با شناسه سفارش پارتیشن می‌شود. سلامت: LB نمونه‌های ناآماده را بیرون می‌کند؛ failover پایگاه standby هم‌گام را بالا می‌آورد؛ مصرف‌کننده‌های صف offset را checkpoint می‌کنند.

Write path: client → LB (health-checked) → any API instance → DB primary (sync standby ACK) → outbox row in the same transaction → queue → 3 consumers. Read path: status reads from API cache or async replica with a stated lag; never from the primary on the hot path. Keys: orders keyed by order id; queue partitioned by order id so one order's events stay ordered. Health: LB removes unready instances; DB failover promotes the sync standby (fenced old primary); queue consumers checkpoint offsets.

گام 1 از 3 · خط فکریStep 1 of 3 · reasoning thread · توپولوژی ۳ AZ: سطرهای stateless بالا، state فنس‌شده پایین3-AZ topology: stateless rows above, fenced state below · معماریarchitectureباز کردن در صفحه جدا ↗Open standalone ↗

دانلود نمودار مستقلDownload standalone diagram

روایت مدرس / چطور این نمودار را بخوانیمTeacher narration / how to read this diagram
ساعت ۳ صبح است و یک AZ تازه تاریک شده. از پیکان کلاینت بالا شروع کن و از هر جعبه‌ای که هنوز باید جواب بدهد پایین برو.
It is 3 a.m. and an AZ just went dark. Start at the client arrow at the top and follow it down through every box that must still answer.
  1. بالا: کلاینت به load balancer می‌رسد که هر نمونه را health-check می‌کند و مرده‌ها را بیرون می‌اندازد.
  2. ردیف وسط: سه سطر stateless روی سه AZ — هر سطر می‌تواند خاکستری شود و دو تای دیگر هنوز اوج ۲۳۱ را می‌گیرند، چون هر AZ به‌تنهایی کل اوج را می‌کشد.
  3. پایین: تاج پایگاه (primary + standby هم‌گام با فنس epoch) و صف تکثیرشده با checkpoint — state این‌جاست.
  4. خط‌های مرز: دامنه‌های خرابی — هرچه داخل یک مرز است با هم می‌میرد؛ و جعبه بکاپ با RPO/RTO که HA نیست.
  1. Top: the client reaches the load balancer, which health-checks every instance and drops the dead ones.
  2. Middle row: three stateless rows across three AZs — any row can go grey and the other two still take the 231 peak, because each AZ alone carries the full peak.
  3. Bottom: the database crown (primary plus sync standby with epoch fencing) and the checkpointed replicated queue — state lives here.
  4. Boundary lines: failure domains — everything inside one boundary dies together; plus the backup box with RPO/RTO, which is not HA.

نقطهٔ تصمیم: نقطه‌های تصمیم مرزها هستند: هرچه داخل یک مرز است می‌تواند با هم بمیرد — و تاج که فقط با fencing جابه‌جا می‌شود.

رد خرابی: اگر ظرفیت N+1 نخریده باشی، بازمانده‌ها زیر اوج خفه می‌شوند؛ اگر primary قدیم فنس نشود، دو نویسنده از هم واگرا می‌شوند.

تضمین نشان‌داده‌شده: شکل نشان می‌دهد توپولوژی کدام خرابی‌های واحد را تحمل می‌کند و state کجاست. اعداد RPO/RTO را اثبات نمی‌کند — آن‌ها drill بازیابی می‌خواهند.

تضمین نشان‌نداده‌شده: از دست رفتن ریجن، خطای اپراتور و تمام شدن کوتا بیرون این شکل‌اند — قلمرو DR و عملیات.

Decision point: The decision points are the boundaries: everything inside one line can die together — and the crown, which moves only with fencing.

Failure trace: Without bought N+1 capacity the survivors choke under the peak; without fencing the old primary, two writers diverge.

Guarantee shown: The figure shows which single failures the topology survives and where state lives. It does not prove RPO/RTO numbers — those need restore drills.

Not shown: Region loss, operator error and quota exhaustion are outside this figure — DR and operational territory.

خودآزماییSelf-check

چرا هر AZ باید به‌تنهایی کل اوج را بکشد و تاج پایگاه با چه سازوکاری جابه‌جا می‌شود؟

پاسخ: چون با مرگ یک AZ بازمانده‌ها باید اوج را جذب کنند (N+1)؛ تاج فقط با ارتقای standby و فنس epoch جابه‌جا می‌شود تا دو نویسنده نسازد.

Why must each AZ alone carry the full peak, and by what mechanism does the database crown move?

Answer: Because when one AZ dies the survivors must absorb the peak (N+1); the crown moves only by promoting the standby with epoch fencing, so no two writers exist.

گام 2 از 3 · خط فکریStep 2 of 3 · reasoning thread · از دست رفتن AZ تا فنس، به‌علاوه انتشار غلتان بدون توقفAZ loss to fencing, plus zero-downtime rolling deploy · توالیsequenceباز کردن در صفحه جدا ↗Open standalone ↗

دانلود نمودار مستقلDownload standalone diagram

روایت مدرس / چطور این نمودار را بخوانیمTeacher narration / how to read this diagram
AZ-b تاریک شده و هم‌زمان نسخه جدید آماده انتشار است. شکل را بالا‌به‌پایین بخوان: اول نجات، بعد انتشار.
AZ-b just went dark while a new release is ready to ship. Read top to bottom: rescue first, release second.
  1. دو فلش اول: balancer AZ مرده را drain می‌کند و بازمانده‌ها اوج را می‌گیرند — همان ظرفیتی که برایش پول دادی.
  2. سه فلش بعد: standby بالا می‌آید و epoch از ۷ به ۸ می‌رود؛ نوشتن کهنه با epoch ۷ رد می‌شود — split-brain ناممکن است.
  3. سه فلش آخر: انتشار غلتان با expand–migrate–contract — ستون nullable، هر دو باینری زنده، backfill و مقایسه، بعد حذف ستون قدیم.
  4. برچسب rollback: تا ستون‌های قدیم هستند برگرداندن مسیر کافی است؛ بعد از contract مهاجرت معکوس لازم است.
  1. First two arrows: the balancer drains the dead AZ and the survivors take the peak — the capacity you paid for.
  2. Next three arrows: the standby promotes and the epoch moves 7 to 8; the stale epoch-7 write is rejected — split brain is impossible.
  3. Last three arrows: the rolling deploy via expand–migrate–contract — nullable column, both binaries live, backfill and compare, then drop the old column.
  4. The rollback note: while old columns exist, routing back suffices; after contract it needs the reverse migration.

نقطهٔ تصمیم: نقطه تصمیم epoch است: فقط یک epoch می‌نویسد — و ترتیب انتشار: هرگز contract قبل از رفتن آخرین باینری قدیم.

رد خرابی: اگر fencing جا بیفتد، primary قدیم برمی‌گردد و می‌نویسد؛ اگر contract زود بیاید، باینری قدیم روی ستون ناموجود می‌شکند.

تضمین نشان‌داده‌شده: شکل ترتیب نجات و انتشار را نشان می‌دهد. زمان واقعی failover را تضمین نمی‌کند — آن را در staging بسنج.

تضمین نشان‌نداده‌شده: درباره پیام مسموم صف و dead-letter چیزی نمی‌گوید — آن‌ها در پنل اسمبلی‌اند.

Decision point: The decision point is the epoch: only one epoch writes — and the release order: never contract before the last old binary is gone.

Failure trace: Skipped fencing lets the old primary return and write; early contract breaks the old binary on a missing column.

Guarantee shown: The figure shows the rescue and release order. It does not guarantee real failover time — measure that in staging.

Not shown: It says nothing about poison queue messages and dead-lettering — those live in the assembly panel.

خودآزماییSelf-check

بعد از ارتقای standby، نوشتن قدیمی با epoch ۷ چه می‌شود و چرا؟ و contract چه زمانی مجاز است؟

پاسخ: رد می‌شود چون فنس شده و فقط epoch ۸ می‌نویسد؛ contract فقط وقتی هیچ باینری قدیمی نمانده.

After the standby promotes, what happens to an old epoch-7 write and why? And when is contract allowed?

Answer: Rejected — it is fenced and only epoch 8 writes; contract only when no old binary remains.

نمودار اسمبل نهاییFinal assembly diagram · یک ماشین یا یک AZ می‌تواند بمیرد؛ مسیر سفارش زنده می‌ماند (توپولوژی آموزشی)One host or one AZ can die; the order path survives (teaching topology) · معماری + دامنه‌های خرابیarchitecture + failure domainsباز کردن در صفحه جدا ↗Open standalone ↗

دانلود SVG مستقلDownload standalone SVG

روایت مدرس / چطور این نمودار را بخوانیمTeacher narration / how to read this diagram
ساعت ۳ صبح است و یک AZ تازه تاریک شده. از پیکان کلاینت بالا شروع کن و از هر جعبه‌ای که هنوز باید جواب بدهد پایین برو.
It is 3 a.m. and an AZ just went dark. Start at the client arrow at the top and follow it down through every box that must still answer.
  1. بالا: پیکان کلاینت به load balancer می‌رسد که هر نمونه API را health-check می‌کند و مرده‌ها را بیرون می‌اندازد.
  2. ردیف وسط: نمونه‌های stateless روی سه AZ — سطر هر AZ می‌تواند خاکستری شود و دو تای دیگر هنوز اوج را بگیرند.
  3. پایین: تاج پایگاه (primary + standby هم‌گام) و صف تکثیرشده — state این‌جاست، پس برچسب‌های fencing و RPO/RTO روی این جعبه‌هاست.
  4. خط‌های چین: مرزهای دامنه خرابی — ماشین، AZ، control plane، state — هرکدام با برچسب هزینه مردن داخلش.
  1. Top: the client arrow fans into the load balancer, which health-checks every API instance and drops the dead ones.
  2. Middle row: stateless API instances over three AZs — any one AZ's row can go grey and the other two still take the peak.
  3. Bottom: the database crown (primary + sync standby) and the replicated queue — state lives here, so fencing and RPO/RTO labels sit on these boxes.
  4. Dashed lines: failure-domain boundaries — host, AZ, control plane, state — each labelled with what dying inside it costs.

نقطهٔ تصمیم: نقطه‌های تصمیم خط‌های چین دامنه خرابی‌اند: هرچه داخل یک خط است می‌تواند با هم بمیرد — و تاج روی پایگاه که فقط با fencing جابه‌جا می‌شود.

رد خرابی: AZ-b را روی شکل بکش: جعبه‌های APIاش خاکستری می‌شوند، پیکان‌های LB به باقی‌مانده‌ها می‌چرخند و تاج پایگاه به standby می‌رود — و primary قدیم خط‌خورده token fencing را نشان می‌دهد.

تضمین نشان‌داده‌شده: شکل نشان می‌دهد توپولوژی کدام خرابی‌های واحد را تحمل می‌کند و هنگام failover state کجاست. اعداد RPO/RTO را اثبات نمی‌کند — آن‌ها drill بازیابی می‌خواهند.

تضمین نشان‌نداده‌شده: از دست رفتن ریجن، خطای اپراتور و تمام شدن کوتا بیرون این شکل‌اند — قلمرو DR و عملیات.

Decision point: The decision points are the dashed failure-domain lines: everything inside one line can die together — and the crown on the database, which moves only with fencing.

Failure trace: Kill AZ-b on the figure: its API boxes go grey, LB arrows swing to the survivors, the DB crown moves to the standby — while the crossed-out old primary shows the fencing token rejecting its writes.

Guarantee shown: The figure shows which single failures the topology survives and where state lives during failover. It does not prove RPO/RTO numbers — those need restore drills.

Not shown: Region loss, operator error and quota exhaustion are outside this figure — DR and operational territory.

خودآزماییSelf-check

standby تو جدول سفارش‌ها را دارد. برای بازیابی کامل کافی است؟ دیگر چه چیزی state است؟

پاسخ: نه: صف، جدول outbox و checkpointهای مصرف‌کننده هم stateاند. standbyای که فقط جدول سفارش‌ها را دارد جای صف و سطرهای outbox را گم می‌کند.

Your standby holds the orders table. Is that sufficient for full recovery? What else is state?

Answer: No: the queue, the outbox table and consumer checkpoints are state too. A standby with only the orders table loses queue position and outbox rows.

گزینه‌های جایگزینAlternatives

  • پذیرش queue-first: سفارش را در صف بادوام بپذیر و ناهمگام تأیید کن. وقتی اوج‌ها شدیدند و محصول حالت «دریافت شد، در حال پردازش» را تحمل می‌کند بهتر است. هزینه: حالت pending که باید توضیح، ردیابی و مغایرت‌گیری شود.
  • Queue-first intake: accept the order into a durable queue and confirm asynchronously. Better when peaks are extreme and the product tolerates 'received, processing' states. Cost: a pending state you must explain, track and reconcile — pending money must always be accounted for.

سناریوهای خرابیFailure scenarios

  • از دست رفتن AZ: آن را drain می‌کند، باقی‌مانده‌ها اوج را جذب می‌کنند؛ standby پایگاه در AZ سالم بالا می‌آید و primary قدیم با epoch token فنس می‌شود.
  • تداخل ارتقای غلتان: قدیم با schema نسخه ۱ می‌نویسد و جدید نسخه ۲ می‌خواهد. expand–migrate–contract ممنوعش می‌کند: v1+v2 هم‌زیست، هر دو باینری، backfill، بعد contract.
  • پیام مسموم صف: یک سفارش خراب مصرف‌کننده را در حلقه می‌کشد. صف dead-letter + سقف تلاش مجدد + هشدار؛ مسیر بالا می‌ماند.
  • AZ loss: LB drains it, survivors absorb peak (that is what N+1 was bought for); DB standby in a surviving AZ is promoted, old primary fenced with an epoch token so split-brain writes are rejected.
  • Rolling-upgrade clash: old binary writes schema v1 while new expects v2. Expand–migrate–contract forbids it: v1+v2 coexist, both binaries run, backfill, then contract. Rollback = route back while v1 columns still exist.
  • Poison queue message: one malformed order kills the consumer in a loop. Dead-letter queue + per-message retry cap + alert; the path stays up while the poison is inspected offline.

کد قابل اجراRunnable code

اجراشده و تأییدشده در همین محیطExecuted and verified in this environment

این کد آموزشی و ساده‌سازی‌شده است؛ فقط برای آشنایی با ایده. کد واقعی پروداکشن ملاحظات بسیار بیشتری دارد و هیچ‌کس این‌شکلی کد پروداکشن نمی‌زند.Simplified teaching code for building intuition only — not production code; nobody ships production code like this.

import java.util.*;
// Teaching sim: 3 AZs with N+1 capacity lose one AZ. Survivors absorb the
// peak, the sync standby promotes, the old primary is epoch-fenced, and a
// split-brain write with a stale epoch is rejected.
public class FailoverDemo {
    static class Db {
        String holder; long epoch; boolean fenced;
        Db(String holder, long epoch) { this.holder = holder; this.epoch = epoch; }
        boolean write(long epoch) { return !fenced && epoch == this.epoch; }
    }
    public static void main(String[] a) {
        double peak = 2_000_000 / 86400.0 * 10; // 231.5 orders/s teaching peak
        Map<String, Double> capacity = new LinkedHashMap<>(Map.of(
            "AZ-a", peak, "AZ-b", peak, "AZ-c", peak)); // each AZ alone carries peak
        capacity.remove("AZ-b"); // AZ-b goes dark
        double survivors = 0;
        for (double c : capacity.values()) survivors += c;
        boolean absorbs = survivors >= peak;
        Db db = new Db("AZ-a", 7);       // primary in AZ-a, epoch 7
        db.holder = "AZ-c"; db.epoch = 8; // standby in AZ-c promotes, epoch bumps
        Db oldPrimary = new Db("AZ-a", 7); oldPrimary.fenced = true; // fenced
        boolean staleRejected = !oldPrimary.write(7);  // split-brain write refused
        boolean freshAccepted = db.write(8);            // new primary serves
        System.out.println("dead AZ            = AZ-b");
        System.out.printf("survivor capacity  = %.1f/s vs peak %.1f/s -> %s%n",
            survivors, peak, absorbs ? "ABSORBED" : "OVERLOAD");
        System.out.println("db primary now     = " + db.holder + " at epoch " + db.epoch);
        System.out.println("old primary fenced = " + oldPrimary.fenced);
        System.out.println("stale write (ep 7) = " + (staleRejected ? "REJECTED" : "ACCEPTED"));
        System.out.println("fresh write (ep 8) = " + (freshAccepted ? "ACCEPTED" : "REJECTED"));
        if (!absorbs || !staleRejected || !freshAccepted) throw new AssertionError("failover broken");
        System.out.println("OK");
    }
}

اجرا: javac FailoverDemo.java && java FailoverDemo — فقط استاندارد جاوا، فوری.

خروجی مورد انتظار: بازمانده‌ها ۴۶۳ در برابر اوج ۲۳۱٫۵ یعنی ABSORBED؛ primary حالا AZ-c با epoch ۸؛ primary قدیم fenced؛ نوشتن کهنه REJECTED و نوشتن تازه ACCEPTED؛ OK. (خروجی واقعی همین محیط)

Run: javac FailoverDemo.java && java FailoverDemo — stdlib Java only, instant.

Expected output: survivors 463 vs peak 231.5 -> ABSORBED; primary now AZ-c at epoch 8; old primary fenced; stale write REJECTED, fresh write ACCEPTED; OK. (actual output of this environment)

آزمون صحت و معیار عملیاتیCorrectness checks and operational criteria

فصلی اثبات کن: در staging یک AZ را بکش و RTO را بسنج؛ بکاپ را روی کلاستر موقت برگردان و RPO/RTO واقعی را بگیر؛ ارتقای غلتان با تغییر expand–migrate–contract زیر بار؛ مسموم کردن یک پیام صف برای تأیید dead-letter.
Prove it quarterly: kill one AZ in staging and measure RTO; restore the backup to a scratch cluster and measure RPO/RTO; run a rolling upgrade with an expand–migrate–contract change under load; poison one queue message to confirm dead-lettering keeps the path up.

پاسخ سؤال‌های پیگیریFollow-up answers

پیگیری (سینیور): در rolling upgrade نسخه‌های قدیم و جدید هم‌زمان فعال‌اند؛ تغییر schema را چگونه منتشر می‌کنی؟Follow-up (Senior): Old and new binaries coexist during rolling deployment. How do you roll out a schema change?

به ترتیب expand–migrate–contract با هر دو باینری زنده. (۱) Expand: ستون جدید nullable (یا فیلد رویداد تازه که کد قدیم نادیده می‌گیرد)؛ باینری جدید هر دو را می‌نویسد و قدیمی را می‌خواند. (۲) Migrate: backfill و dual-read با مقایسه. (۳) Contract: خواندن‌ها به جدید، حذف نوشتن قدیم، بعد حذف ستون قدیم — فقط وقتی هیچ باینری قدیمی نمانده. rollback قبل از contract = برگرداندن ترافیک.

Expand–migrate–contract with both binaries live. (1) Expand: add the new column as nullable (or a new event field old code ignores); new binary writes both, reads old. (2) Migrate: backfill, dual-read and compare. (3) Contract: switch reads to new, drop old writes, then drop the old column — only after no old binary remains. Rollback before contract = route traffic back; after contract it needs the reverse migration. Never rename in place in one release.

پیگیری (استف): چرا failover جای backup restore را نمی‌گیرد؟Follow-up (Staff): Why does failover not replace backup restoration?

حذف اشتباه ممکن است به replica هم برسد، پس failover دادهٔ حذف‌شده را برنمی‌گرداند. RPO را از آخرین نقطهٔ بازیابی معتبر تا زمان رخداد حساب کن؛ RTO شامل تشخیص، آماده‌سازی، restore، replay و validation است. برای RTO پنج دقیقه باید جمع این مراحل در آزمون واقعی زیر پنج دقیقه باشد؛ صرف داشتن backup کافی نیست. restore را روی محیط جدا اجرا کن و checksum، invariant و registry حذف را پیش از سرو بررسی کن.

Replicas can copy accidental deletions, so failover cannot undo them. RPO measures the gap from the last usable recovery point to the incident. RTO includes detection, provisioning, restore, replay and validation. A five-minute RTO requires all stages to fit in an actual drill. Restore in isolation and verify checksums, invariants and deletion registries before serving.

اشتباهات رایجCommon mistakes

  • «دو ماشین، بدون SPOF.» balancer، DNS، primary واحد، خط انتشار و انسان آنکال همه هنوز نقطه واحدند.
  • قاطی کردن HA با backup. standby ثانیه‌ای HA است؛ اسنپ‌شات دیشب ساعتی backup است (RPO/RTO)؛ ریجن دوم DR است.
  • 'Two machines, no SPOF.' The load balancer, DNS, single DB primary, deploy pipeline and on-call human are all still single points until each is doubled or shown unnecessary.
  • Confusing HA with backup. Standby serving in seconds is HA; last night's snapshot restoring in an hour is backup (RPO/RTO); a second region is DR.

منابع:Sources: Microsoft · technical interviewing · Atlassian · engineering interview handbook