11 تمرین ۱ — محاسبهٔ بارExercise 1 — load math
بازگشت به صورت تمرینBack to the exercise · ارزیابی و سطحAssessment and level
پاسخ ۶۰ثانیهایThe 60-second answer
صورت مسئله و فرضها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
حل گامبهگامStep by step
- اول نرخها ۲٬۰۰۰٬۰۰۰ ÷ ۸۶۴۰۰ ≈ ۲۳٫۱ سفارش در ثانیه میانگین و با ضریب ۱۰ ≈ ۲۳۱٫۵ در اوج. این دو عدد را همیشه جفتی نگه دار.
- بعد fan-out گردشکار هر سفارش ۳ پیام داخلی میسازد: میانگین ≈ ۶۹٫۴ و اوج ≈ ۶۹۴ پیام در ثانیه. با payload آموزشی ۱ کیلوبایت، اوج ≈ ۰٫۷ مگابایت در ثانیه است — یک صف معمولی جواب میدهد.
- قبل از شمردن، view را تعریف کن حالا view را تعریف کن: سطر خلاصه سفارش با کلید شناسه سفارش در k مقصد (مثلاً ۲ ریجن × ۲ رپلیکا یعنی k=۴). هر سفارش دقیقاً k نوشتن اضافه میسازد — نه ۳.
- قاعده سربهسر را اعمال کن نامساوی تصمیم: اگر (خواندن در ثانیه × ارزش یک خواندن سریعتر) از (سفارش در ثانیه × k × هزینه یک نوشتن) بیشتر شد، متریالایز کن؛ وگرنه read-through با TTL کوتاه.
- Rates first 2,000,000 ÷ 86,400 ≈ 23.1 orders/s average, ≈ 231.5 at a 10x peak. Always keep the pair together.
- 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.
- 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.
- 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.
دانلود SVG مستقلDownload standalone SVG
- میلههای چپ: میانگین ۲۳٫۱ در برابر اوج ۲۳۱٫۵ سفارش در ثانیه — یک باند ۱۰ برابری که باید برایش ظرفیت بگذاری.
- میلههای وسط: همان دو نرخ ضربدر ۳ پیام (≈ ۶۹ و ≈ ۶۹۴ در ثانیه) — fan-out گردشکار که از صف میگذرد، نه به کش میرود.
- پنل راست: viewای که تعریف میکنی — یک سفارش داخل، k نوشتن بیرون — با اندازه payload و بودجه lag کنارش.
- نوار پایین: نامساوی سربهسر که حساب را به تصمیم متریالایزیشن تبدیل میکند.
- Left bars: average 23.1 vs peak 231.5 orders/s — a 10x band you must provision for, not the average alone.
- Middle bars: the same two rates times 3 notifications (≈ 69/s, ≈ 694/s) — workflow fan-out through a queue, not into a cache.
- Right panel: the view you define — one order in, k writes out — with payload size and lag budget written next to it.
- 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
پاسخ سؤالهای پیگیری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
بازگشت به صورت تمرینBack to the exercise · ارزیابی و سطحAssessment and level
پاسخ ۶۰ثانیهایThe 60-second answer
صورت مسئله و فرضها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
حل گامبهگامStep by step
- اول صورتها صورت و مخرج هر SLI را بنویس: تأخیر = خواندن موفق زیر ۳۰۰ms ÷ کل خواندن موفق؛ دسترسپذیری = پاسخ بدون خطا ÷ کل درخواست. پنجره: ۳۰ روزه غلتان.
- خطهای خرابی را بکش موفقیت معنایی را مستقل از سرعت تعریف کن: پاسخ 2xx با وضعیت درست و تازگی مجاز، موفق است؛ پاسخ موفق کند، SLI تأخیر را بدتر میکند. timeout یکثانیهای و خطای وابستگی شکست دسترسپذیریاند و در SLI تأخیر مشروط شمرده نمیشوند؛ به همین دلیل نمودار تأخیر بهتنهایی کافی نیست.
- بودجه و پیامدش بودجه: با SLO ۹۹٫۹٪ روی حجم آموزشی ۵۰ میلیون درخواست، ۵۰٬۰۰۰ خرابی مجاز است. سیاست اتمام: فریز انتشار غیرحیاتی + فقط کار قابلیت اطمینان.
- دو سرعت مصرف، دو اقدام هشدارها: fast-burn (۲٪ بودجه در ۱ ساعت → پیج و rollback) و slow-burn (۱۰٪ در ۶ ساعت → تیکت). هر هشدار به یک اقدام رانبوک وصل است.
- جدا کردن SLO از SLA جدایی SLO/SLA: مثلاً SLA قراردادی ۹۹٫۵٪ ماهانه با اعتبار ۱۰٪ — شلتر از SLO تا هفته بد معمولی جریمه مالی نسازد.
- 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.
- 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.
- 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.
- 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.
- 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.
دانلود نمودار مستقلDownload standalone diagram
- دو فلش اول: مانیتور burn پیج میزند (۲٪ بودجه در ۱ ساعت) و آنکال انتشارها را فریز و rollback میکند — هشدار به اقدام میرسد نه به نمودار.
- فلش برگشتی: قطار انتشار «burn تخت شد» را برمیگرداند — تا راستیآزمایی نشود قفل باز نمیشود.
- شاخه dashed: burn کند (۱۰٪ در ۶ ساعت) تیکت میشود نه پیج — در همان شیفت بررسی میشود.
- دو فلش آخر: p99 بر اساس گروه برش میخورد (ریجن، نسخه، hit/miss) و بزرگترین گروه اول درست میشود — بعد حلقه با سبز شدن burn بسته میشود.
- 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.
- Return arrow: the release train reports 'burn flat' — no unfreeze before verification.
- Dashed branch: the slow burn (10% in 6 hours) becomes a ticket, not a page — investigated inside the shift.
- 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.
دانلود SVG مستقلDownload standalone SVG
- پنل چپ: دو کسر SLI — تأخیر (سریع ÷ موفق) و دسترسپذیری (بدونخطا ÷ کل).
- میله وسط: بودجه ۳۰ روزه — سبز امن، کهربایی هشدار، قرمز اتمام — با سیاست فریز/rollback زیرش.
- نوار راست: دو سرعت مصرف — fast-burn در همان ساعت پیج، slow-burn در همان شیفت تیکت.
- خط پایین: جدایی SLO/SLA — بالای آن هدف داخلی، پایینش قرارداد مشتری با جبران.
- Left panel: the two SLI fractions — latency (fast ÷ successful) and availability (non-error ÷ total).
- Middle bar: the 30-day error budget as a strip — green safe, amber warning, red exhausted — with the freeze/rollback policy under it.
- Right strip: the two burn speeds — fast-burn pages within the hour, slow-burn tickets within the shift.
- 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
پاسخ سؤالهای پیگیری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
بازگشت به صورت تمرینBack to the exercise · ارزیابی و سطحAssessment and level
پاسخ ۶۰ثانیهایThe 60-second answer
صورت مسئله و فرضها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
حل گامبهگامStep by step
- فهرست همه SPOFها همه SPOFها را فهرست کن: نمونه API، LB، رکورد DNS، primary پایگاه، صف تکنسخه، خط انتشار، انسان آنکال. data plane را از control plane و state را از stateless جدا کن.
- مقیاس لایه stateless stateless را افقی کن: API بدون state نشست پشت LB روی ۳ AZ با health check؛ ظرفیت هر AZ بهتنهایی = اوج کامل (N+1).
- داوم state state را بادوام کن: primary + standby همگام + رپلیکای ناهمگام؛ صف تکثیرشده با checkpoint؛ outbox در همان تراکنش سفارش. primary قدیم با epoch فنس میشود.
- جدایی HA و backup و DR HA را از backup و DR جدا کن: standby همگام = HA (ثانیه)؛ اسنپشات + WAL = backup با RPO/RTO که با drill اثبات میشود؛ ریجن دوم = DR.
- انتشار و rollback انتشار و rollback: expand–migrate–contract برای schema؛ باینری قدیم و جدید همزیست؛ rollback یعنی برگرداندن مسیر تا وقتی ستونهای قدیم هستند.
- 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.
- 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).
- 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.
- Separate HA, backup, DR Separate HA from backup and DR: sync standby = HA (seconds); snapshot + WAL = backup with drilled RPO/RTO; second region = DR.
- 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.
دانلود نمودار مستقلDownload standalone diagram
- بالا: کلاینت به load balancer میرسد که هر نمونه را health-check میکند و مردهها را بیرون میاندازد.
- ردیف وسط: سه سطر stateless روی سه AZ — هر سطر میتواند خاکستری شود و دو تای دیگر هنوز اوج ۲۳۱ را میگیرند، چون هر AZ بهتنهایی کل اوج را میکشد.
- پایین: تاج پایگاه (primary + standby همگام با فنس epoch) و صف تکثیرشده با checkpoint — state اینجاست.
- خطهای مرز: دامنههای خرابی — هرچه داخل یک مرز است با هم میمیرد؛ و جعبه بکاپ با RPO/RTO که HA نیست.
- Top: the client reaches the load balancer, which health-checks every instance and drops the dead ones.
- 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.
- Bottom: the database crown (primary plus sync standby with epoch fencing) and the checkpointed replicated queue — state lives here.
- 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.
دانلود نمودار مستقلDownload standalone diagram
- دو فلش اول: balancer AZ مرده را drain میکند و بازماندهها اوج را میگیرند — همان ظرفیتی که برایش پول دادی.
- سه فلش بعد: standby بالا میآید و epoch از ۷ به ۸ میرود؛ نوشتن کهنه با epoch ۷ رد میشود — split-brain ناممکن است.
- سه فلش آخر: انتشار غلتان با expand–migrate–contract — ستون nullable، هر دو باینری زنده، backfill و مقایسه، بعد حذف ستون قدیم.
- برچسب rollback: تا ستونهای قدیم هستند برگرداندن مسیر کافی است؛ بعد از contract مهاجرت معکوس لازم است.
- First two arrows: the balancer drains the dead AZ and the survivors take the peak — the capacity you paid for.
- Next three arrows: the standby promotes and the epoch moves 7 to 8; the stale epoch-7 write is rejected — split brain is impossible.
- Last three arrows: the rolling deploy via expand–migrate–contract — nullable column, both binaries live, backfill and compare, then drop the old column.
- 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.
دانلود SVG مستقلDownload standalone SVG
- بالا: پیکان کلاینت به load balancer میرسد که هر نمونه API را health-check میکند و مردهها را بیرون میاندازد.
- ردیف وسط: نمونههای stateless روی سه AZ — سطر هر AZ میتواند خاکستری شود و دو تای دیگر هنوز اوج را بگیرند.
- پایین: تاج پایگاه (primary + standby همگام) و صف تکثیرشده — state اینجاست، پس برچسبهای fencing و RPO/RTO روی این جعبههاست.
- خطهای چین: مرزهای دامنه خرابی — ماشین، AZ، control plane، state — هرکدام با برچسب هزینه مردن داخلش.
- Top: the client arrow fans into the load balancer, which health-checks every API instance and drops the dead ones.
- Middle row: stateless API instances over three AZs — any one AZ's row can go grey and the other two still take the peak.
- Bottom: the database crown (primary + sync standby) and the replicated queue — state lives here, so fencing and RPO/RTO labels sit on these boxes.
- 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
پاسخ سؤالهای پیگیری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