DDIA·مطالعهSTUDY

نقشه راهRoadmap/ فصل ۹Chapter 9

دردسرهای سیستم‌های توزیع‌شدهThe Trouble with Distributed Systems

در چند ماشین، سکوتِ یک گره معنیِ قطعی ندارد: شاید خراب شده، شاید کند است و شاید فقط پیامش نرسیده. این فصل نشان می‌دهد با نتیجهٔ نامشخص، ساعتِ نادقیق و مجوزِ قدیمی چگونه روبه‌رو شویم. On one computer a program works or it doesn't; in a distributed system part breaks while the rest carries on — and you can't even tell which part! This chapter opens three sources of confusion: the unreliable network (no reply — but the request may have arrived), unreliable clocks (timestamps can lie), and sudden process pauses (GC pauses, VM migration) that breed zombies. Then it shows how to reason about this hazy world with quorum, fencing tokens, system models and safety/liveness. Twelve diagrams: timelines, state machines, a UML class diagram, a decision tree, sequences and a dataflow.

۱۲12 نمودار با روایت گام‌به‌گامdiagrams with step-by-step narration· محور: partial failure · timeouts · clock skew · leases · fencing · system modelsTheme: partial failure · timeouts · clock skew · leases · fencing · system models· زمان مطالعه: حدود ۶۰ دقیقهReading time: ~60 min

۰0 نقشهٔ راه فصلChapter roadmap

ایدهٔ کلیدی فصلChapter thesis

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

مثال فنی: timeout فقط یک مشاهده است

وضعیت اولیه: درخواست r7 به سرور می‌رود و کلاینت پس از 2 s پاسخی ندارد.

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

نتیجه و دلیل: از سکوت، شکست قطعیِ اثر را نتیجه نمی‌گیریم؛ شناسهٔ عملیات و مسیر استعلام برای حل ابهام لازم‌اند.

مثال‌های این فصل سناریوهای آموزشیِ برگرفته از کارهای روزمره‌اند. عددهای انتخاب‌شده برای توضیح‌اند؛ معماریِ داخلیِ یک شرکت مشخص را توصیف نمی‌کنند.

On one machine, failure means “everything stops”; in a distributed system, failure means “maybe”: maybe the request arrived, maybe not; maybe the node is dead, maybe just slow; maybe the other side's clock runs ahead. The art of distributed-systems engineering is giving certain guarantees (safety) inside this world of “maybe”, and framing the rest as “eventually happens” (liveness).

منبعِ سردرگمیچه چیزی خراب می‌شودراه‌حلِ اصلی در این فصل
شبکه ناهمگام تأخیر نامحدود، گم‌شدن بسته، queue؛ «پاسخ نیامد» یعنی هیچ‌چیز timeout + idempotency + retry محدود
ساعت‌های ناهمگام timestamp دروغ می‌گوید؛ ترتیبِ علّی به‌هم می‌خورد؛ LWW داده را می‌بلعد ساعت monotonic، logical clock، بازهٔ عدم قطعیت (TrueTime)
توقفِ فرآیند (GC / VM) نودِ زنده «مرده» اعلام می‌شود؛ دارندهٔ lease قدیمی برمی‌گردد و می‌نویسد fencing token و نوشتنِ شرطی
عدم قطعیتِ دانش هیچ نودی تنها نمی‌تواند تصمیم بگیرد؛ «حقیقت» را اکثریت تعیین می‌کند quorum + مدلِ سیستم + اثباتِ safety
Source of confusionWhat breaksMain fix in this chapter
Async networkUnbounded delay, lost packets, queues; “no reply” means nothingtimeouts + idempotency + bounded retry
Async clocksTimestamps lie; causal order breaks; LWW swallows datamonotonic clocks, logical clocks, uncertainty intervals (TrueTime)
Process pauses (GC / VM)A live node is declared “dead”; an old lease holder returns and writesfencing tokens and conditional writes
Knowledge uncertaintyNo node can decide alone; “truth” is set by majorityquorum + system model + safety proofs
چیدمان فصلChapter layout

بخش ۱–۲ مربوط به شبکه و مفهومِ نتیجهٔ نامشخص است؛ بخش ۳ به ساعت‌ها می‌پردازد؛ بخش ۴ ترکیبِ این دو یعنی lease، زامبی و fencing را باز می‌کند؛ بخش ۵ به فلسفه‌ی «چه چیزی را می‌توانیم بدانیم» و مدلِ سیستم می‌رسد و بخش ۶ ابزارهای اطمینان از صحت را معرفی می‌کند.

Sections 1–2 cover the network and unknown outcomes; Section 3 covers clocks; Section 4 combines the two — leases, zombies and fencing; Section 5 reaches the philosophy of “what can we know” and the system model, and Section 6 introduces validation tools.

۱1 خرابی جزئی و شبکه‌ی غیرقابل‌اعتمادPartial failure and the unreliable network

۱.۱. چرا توزیع‌شده ذاتاً سخت است1.1 Why distributed is inherently hard

در سیستم توزیع‌شده ممکن است بعضی اجزا سالم باشند و بعضی نباشند. گرهی که پاسخ نمی‌دهد الزاماً خاموش نیست؛ شاید شبکه یا زمان‌بندیِ اجرای آن مشکل دارد.

مثال فنی: نیمی از سیستم هنوز کار می‌کند

وضعیت اولیه: A نمی‌تواند با B ارتباط بگیرد، اما هر دو از کلاینت‌های محلی درخواست می‌گیرند.

اجرای مرحله‌به‌مرحله: restart کردنِ A یا B الزاماً قطع ارتباط بین آن‌ها را حل نمی‌کند؛ ممکن است دو طرف هنوز نوشتن محلی انجام دهند.

نتیجه و دلیل: partial failure با خاموشی یک فرایند فرق دارد؛ پروتکل باید رفتار هر طرف در زمان جدایی را مشخص کند.

A program on a healthy single computer is deterministic: every run of an operation gives the same result. Broken hardware usually kills everything at once (kernel panic) — no ambiguity. But with several machines joined by a network, part of the system may be broken while the rest is fine; that's partial failure, and worse, it's non-deterministic: the same operation sometimes succeeds, sometimes not.

خبر خوب اینکه همین تحملِ خرابی باعث می‌شود بتوانیم rolling upgrade کنیم، یک‌به‌یک نودها را ری‌استارت کنیم و سرویس قطع نشود. یعنی می‌توان از اجزای غیرقابل‌اعتماد، سیستمِ قابل‌اعتماد ساخت — به شرط آنکه اول بدانیم دقیقاً چه چیزهایی ممکن است خراب شوند. سیستم‌هایی که در این کتاب بررسی می‌کنیم عموماً shared-nothing هستند: چند ماشین با حافظه و دیسکِ مستقل که فقط از راهِ شبکه حرف می‌زنند.

The good news: this same fault tolerance enables rolling upgrades — restarting nodes one by one without downtime. Unreliable parts can build a reliable system — provided you first know exactly what can break. The systems in this book are mostly shared-nothing: machines with independent memory and disk that talk only over the network.

۱.۲. وقتی پاسخی نمی‌آید، هیچ‌چیز نمی‌دانی1.2 When no reply comes, you know nothing

وقتی پاسخ نمی‌رسد، چند احتمال داریم: درخواست نرسیده، هنوز درحال اجراست یا اجرا شده و پاسخ گم شده است. سکوتِ شبکه این حالت‌ها را از هم جدا نمی‌کند.

مثال فنی: سه سرنوشت همان درخواست

وضعیت اولیه: CreateOrder(r7) ارسال شده و پاسخ timeout شده است.

اجرای مرحله‌به‌مرحله: حالت اول: نرسیده و هیچ سفارش نیست؛ دوم: در حال اجراست؛ سوم: سفارش ۴۲ ثبت شده اما پاسخ گم شده است.

نتیجه و دلیل: retry با همان هویت باید این حالت‌ها را امن پوشش دهد؛ ساخت هویت تازه احتمالِ دو اثر را بالا می‌برد.

Ethernet and internet networks are asynchronous packet networks: no guarantee about delivery time — or delivery at all. Send a request, get no reply, and six indistinguishable things may have happened:

  • درخواستِ تو در شبکه گم شده (مثلاً کسی کابل را کشیده).
  • درخواست در صف مانده و بعداً تحویل می‌شود (شبکه یا گیرنده شلوغ است).
  • نودِ مقصد کرش کرده یا خاموش شده.
  • نود موقتاً پاسخ نمی‌دهد (مثلاً یک GC pause طولانی) ولی بعداً برمی‌گردد.
  • نود درخواست را انجام داده ولی پاسخ در شبکه گم شده.
  • نود انجام داده و پاسخ هم در راه است، فقط دیر رسیده.
  • Your request was lost in the network (someone pulled the cable).
  • The request is queued and arrives later (network or receiver busy).
  • The destination node crashed or is powered off.
  • The node is temporarily unresponsive (e.g. a long GC pause) but comes back later.
  • The node applied the request but the reply was lost in the network.
  • The node applied it and the reply is on its way — just late.

تنها اطلاعاتِ تو این است: «هنوز پاسخی نیامده.» حتی خودِ فرستنده هم نمی‌داند بسته رسیده یا نه؛ فقط گیرنده می‌تواند خبر دهد و آن خبر هم ممکن است گم شود. پس timeout یک حدس است، نه اثباتِ خرابی — و درخواستِ «تسلیم‌شده» هنوز ممکن است بعداً به مقصد برسد!

Your only information: “no reply yet.” Even the sender can't know whether the packet arrived; only the receiver can report back, and that report may be lost too. So a timeout is a guess, not proof of failure — and the “abandoned” request may still arrive later!

نمودار ۱ — تایم‌لاینِ ابهامِ timeoutDiagram 1 — the timeout-ambiguity timeline باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس — نمودار ۱ را با صدای بلند بخوانیمTeacher narration — read Diagram 1 aloud

مثال فنی برای خواندنِ نمودار: سه سرنوشت همان درخواست

وضعیت اولیه: CreateOrder(r7) ارسال شده و پاسخ timeout شده است.

اجرای مرحله‌به‌مرحله: حالت اول: نرسیده و هیچ سفارش نیست؛ دوم: در حال اجراست؛ سوم: سفارش ۴۲ ثبت شده اما پاسخ گم شده است.

نتیجه و دلیل: retry با همان هویت باید این حالت‌ها را امن پوشش دهد؛ ساخت هویت تازه احتمالِ دو اثر را بالا می‌برد.

بالای نمودار سه جمله می‌بینی: «اگر درخواستی بفرستی و پاسخی نیاید، نمی‌توانی بفهمی چرا.» زیر آن سه ردیف مستقل است که هر کدام یک تایم‌لاین کوچک‌اند. ردیف (a): فلش request از Client به Remote Node وسط راه به ضربدر صورتی dropped می‌رسد — بسته هرگز نرسیده. ردیف (b): درخواست می‌رسد ولی کنار نود ضربدر crashed / powered off است — شاید رسیده، شاید نه. ردیف (c): خطرناک‌ترین حالت — request — applied successfully ولی فلش پاسخ وسط راه lost می‌شود: اثر اعمال شده ولی کلاینت بی‌خبر است.

در هر سه ردیف جعبهٔ خاکستری یک جمله دارد: no response yet — علت‌ها متفاوت، مشاهده یکسان. جعبهٔ زرد جمع‌بندی می‌کند: timeout فقط یک حدس است و در حالت (c) یک retry کورکورانه کار را دوبار انجام می‌دهد.

Above the diagram three lines read: “if you send a request and no reply comes, you cannot tell why.” Below sit three independent rows, each a small timeline. Row (a): the request arrow from Client to Remote Node hits a pink cross mid-way, dropped — the packet never arrived. Row (b): the request arrives, but the node carries a crashed / powered off cross — maybe delivered, maybe not. Row (c): the most dangerous case — request — applied successfully, yet the reply arrow is lost mid-way: the effect applied, the client uninformed.

In all three rows the grey box says one line: no response yet — different causes, identical observation. The yellow box concludes: a timeout is only a guess, and in case (c) a blind retry performs the work twice.

۱.۳. محدودیت‌های TCP1.3 The limits of TCP

TCP انتقالِ مرتبِ بایت‌ها را در اتصال مدیریت می‌کند، اما نمی‌گوید منطقِ برنامهٔ مقصد با موفقیت تمام شده است. تأییدِ شبکه با تأییدِ عملیاتِ کسب‌وکاری یکی نیست.

مثال فنی: TCP ack، commit نیست

وضعیت اولیه: بایت‌های درخواست به TCP stack سرور رسیده و دریافت شبکه تأیید شده است.

اجرای مرحله‌به‌مرحله: برنامه ممکن است پیش از خواندن بایت‌ها crash کند یا پس از commit پاسخ ندهد.

نتیجه و دلیل: تضمین انتقالِ بایت، اجرای دستور یا پایداری تغییرِ برنامه را تأیید نمی‌کند؛ تأیید دامنه باید در لایهٔ برنامه تعریف شود.

TCP is famous for “reliable delivery”: it retransmits lost packets, reorders shuffled ones, catches data corruption with checksums and throttles send rate with congestion control. But that “reliability” doesn't fix our problem:

  • TCP نمی‌داند بسته‌ی رفت گم شده یا acknowledgment برگشتی؛ فقط دوباره می‌فرستد و اگر کابل کشیده شده باشد، کاری از دستش برنمی‌آید.
  • تشخیص تکرار فقط درونِ یک اتصال است؛ اگر اتصال قطع شود و برنامه دوباره وصل و دوباره بفرستد، داده ممکن است تکرار شود.
  • اگر اتصال با خطا بسته شود، نمی‌دانی مقصد دقیقاً تا کجا پردازش کرده.
  • حتی ACK یعنی «هسته‌ی سیستم‌عاملِ مقصد گرفت»، نه «برنامه انجام داد»؛ برای اطمینان باید پاسخِ صریحِ خودِ برنامه را بگیری.
  • تأخیرِ retransmission در لایه‌ی کاربر به شکلِ latency دیده می‌شود، نه خطای صریح.
  • TCP can't tell a lost outbound packet from a lost returning acknowledgment; it just resends, and a pulled cable defeats it entirely.
  • Dedup works only inside one connection; if the connection drops and the app reconnects and resends, data may duplicate.
  • If a connection closes with an error, you can't know exactly how far the destination processed.
  • Even an ACK only means “the destination OS kernel got it”, not “the application did it”; for certainty you need the application's own explicit reply.
  • Retransmission delay shows up to the user as latency, not an explicit error.
TCP در برابر UDPTCP vs UDP

در برنامه‌های حساس به تأخیر (مثل VoIP) گاهی UDP انتخاب می‌شود، چون داده‌ی دیررسیده بی‌ارزش است و به‌جای retransmit، جای بسته را با سکوت پر می‌کنند و «retry» را به لایه‌ی انسان می‌سپارند («ببخشید صدا قطع شد، دوباره بگو»). این یک تریدآفِ صریحِ قابلیت‌اطمینان در برابر ثباتِ تأخیر است.

Latency-sensitive apps (like VoIP) sometimes choose UDP, because late data is worthless: dropped packets are filled with silence and “retry” is delegated to humans (“sorry, you cut out — say again”). An explicit reliability-vs-stable-latency trade-off.

۱.۴. خرابی شبکه در عمل و عاملِ اصلیِ تأخیر: صف1.4 Network failure in practice, and the main source of delay: queues

تأخیر شبکه فقط زمانِ حرکت سیگنال نیست؛ انتظار در صفِ شبکه، سیستم‌عامل و برنامه هم اثر دارد. افزایش بار ممکن است بدون خرابیِ کامل، پاسخ‌ها را بسیار دیر کند.

مثال فنی: تأخیر در صف شبکه

وضعیت اولیه: لینک 100 MB/s دارد و یک انتقال بزرگ صف خروجی را پر کرده است.

اجرای مرحله‌به‌مرحله: پیام heartbeat کوچک پشت داده‌های دیگر منتظر می‌ماند؛ بعد از 500 ms ممکن است failure detector آن را دیررس بداند.

نتیجه و دلیل: نود می‌تواند زنده باشد و پاسخ دیر برسد؛ timeout تنها به خرابی CPU یا فرایند اشاره نمی‌کند.

You'd think decades made networks reliable; studies say otherwise. One mid-size datacenter logged ~12 network failures a month (half killing one machine, half a whole rack). Adding redundant gear helps less than expected, because human error (misconfigured switches) is the main failure path. WAN fiber cuts were dealt by cows, beavers, even sharks (!), and cross-region cloud round-trips reached minutes at high percentiles.

مواردِ عجیب‌تر: ارتباطِ نامتقارن (A با B حرف می‌زند، B با C، ولی A با C نه)، کارتِ شبکه‌ای که فقط بسته‌های ورودی را بی‌صدا می‌ریزد، و اینکه اثرِ یک قطعیِ کوتاه معمولاً خیلی بیشتر از خودِ قطعی باقی می‌ماند. پس هر ارتباطِ شبکه‌ای ممکن است شکست بخورد؛ راهِ گریزی نیست. نکته‌ی مهم اینکه «تحمل» لزوماً یعنی «ادامه‌ی کار» نیست: نشان‌دادنِ یک پیامِ خطا به کاربر هم استراتژیِ معتبری است — به شرط آنکه بدانی نرم‌افزارت چه واکنشی نشان می‌دهد و بعد از رفعِ مشکل بازیابی می‌شود.

Stranger cases: asymmetric links (A talks to B, B to C, but A not to C), NICs silently dropping only inbound packets, and short outages whose effects linger far longer than the outage. So any network link can fail; no escape. And “tolerating” needn't mean “keeping going”: showing the user an error is a valid strategy — provided you know how your software reacts and that it recovers once fixed.

عاملِ اصلیِ تغییرپذیریِ تأخیر، صف‌کشیدن (queueing) است — دقیقاً مثل ترافیکِ خیابان:

The main source of delay variance is queueing — exactly like street traffic:

  • وقتی چند ماشین هم‌زمان به یک مقصد می‌فرستند، سوییچ بسته‌ها را صف می‌کند؛ اگر صف پر شود، بسته ریخته می‌شود و باید دوباره ارسال شود.
  • وقتی بسته به ماشینِ مقصد می‌رسد، اگر همه‌ی CPUها مشغول باشند، درخواست در صفِ هسته می‌ماند — تأخیرِ نامعلوم.
  • در محیطِ مجازی، ماشینِ مجازی ممکن است ده‌ها میلی‌ثانیه pause شود و بسته‌ها در بافرِ hypervisor جمع شوند.
  • خودِ TCP هم برای حفظِ شبکه، ارسال را محدود می‌کند (backpressure).
  • در ابرِ چندمستاجری، همسایه‌ی پرسروصدا (noisy neighbor) منابعِ مشترک را می‌گیرد و تأخیر را نوسانی می‌کند.
  • Several machines sending to one destination at once make the switch queue packets; a full queue drops packets, which must be resent.
  • Arriving at the destination with all CPUs busy, the request waits in the kernel queue — unknown delay.
  • In virtual environments a VM may pause tens of milliseconds while packets pile in the hypervisor buffer.
  • TCP itself throttles sending to protect the network (backpressure).
  • On multi-tenant clouds a noisy neighbor grabs shared resources and jitters latency.

۱.۵. تشخیص خرابی و انتخاب timeout1.5 Failure detection and choosing a timeout

Timeout یک مرزِ انتظار است، نه مدرکِ قطعی خرابی. مقدار خیلی کوتاه هشدار و بازتلاشِ بی‌مورد می‌سازد؛ مقدار خیلی بلند تشخیص و بازیابی را عقب می‌اندازد.

مثال فنی: timeout کوتاه، خطای تشخیص بیشتر

وضعیت اولیه: معمولاً پاسخ در 50 ms می‌رسد، ولی گاهی pause یا صف آن را به 800 ms می‌رساند.

اجرای مرحله‌به‌مرحله: timeout برابر 100 ms بازیابی را زودتر شروع می‌کند، اما همین پاسخ‌های دیررس را هم خرابی می‌شمارد.

نتیجه و دلیل: آستانه از توزیع تأخیر و هزینهٔ تشخیص غلط انتخاب می‌شود؛ timeout پایان عمر نود را اثبات نمی‌کند.

Many systems must detect dead nodes automatically (a load balancer cutting it out, a follower taking the leader's place). Sometimes you get explicit feedback: an RST/FIN reply when nothing listens, a crash-notification script (like HBase's), switch management, or an ICMP Destination Unreachable. But none is reliable; in practice assume no reply ever comes and declare the node “dead” after a timeout.

timeoutمزیتخطر
خیلی کوتاهتشخیصِ سریعِ خرابیمثبتِ کاذب: نودِ زنده «مرده» اعلام می‌شود و بار روی بقیه می‌افتد → cascading failure
خیلی بلندخطای کاذبِ کمترکاربر منتظر می‌ماند، failover کُند می‌شود
تطبیقی (مثل Phi Accrual)بر اساسِ توزیعِ واقعیِ RTT تنظیم می‌شودپیاده‌سازی سخت‌تر؛ هنوز «قطعیت» نمی‌دهد
timeoutBenefitRisk
Too shortFast failure detectionFalse positives: live nodes “die”, load shifts → cascading failure
Too longFewer false alarmsUsers wait, failover slows
Adaptive (e.g. Phi Accrual)Follows the real RTT distributionHarder to build; still no “certainty”

فرضِ خیالیِ کتاب: اگر شبکه حداکثرِ تأخیرِ d و پردازشِ حداکثرِ r را تضمین می‌کرد، timeoutِ معقول 2d + r بود. اما شبکه‌های واقعی تأخیرِ نامحدود دارند و سرورها هم سقفِ زمانی تضمین نمی‌کنند؛ پس timeout مقدارِ «درست» ندارد و باید تجربی اندازه‌گیری شود: توزیعِ RTT را در طولِ زمان بسنج، سپس بینِ سرعتِ تشخیص و خطرِ اعلامِ زودهنگام تعادل برقرار کن.

The book's fantasy assumption: if the network guaranteed max delay d and processing max r, a sane timeout would be 2d + r. But real networks have unbounded delay and servers guarantee no time ceiling; so timeouts have no “correct” value and must be measured empirically: observe the RTT distribution over time, then balance detection speed against premature-declaration risk.

نمودار مفهومی · انتخاب timeout — تشخیص سریع در برابر هشدار اشتباهConcept diagram · choosing a timeout — fast detection vs false alarms باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس از نمودارTeacher narration

مثال فنی برای خواندنِ نمودار: timeout کوتاه، خطای تشخیص بیشتر

وضعیت اولیه: معمولاً پاسخ در 50 ms می‌رسد، ولی گاهی pause یا صف آن را به 800 ms می‌رساند.

اجرای مرحله‌به‌مرحله: timeout برابر 100 ms بازیابی را زودتر شروع می‌کند، اما همین پاسخ‌های دیررس را هم خرابی می‌شمارد.

نتیجه و دلیل: آستانه از توزیع تأخیر و هزینهٔ تشخیص غلط انتخاب می‌شود؛ timeout پایان عمر نود را اثبات نمی‌کند.

سه کارت را بخوان: TOO SHORT (تشخیص سریع ولی مثبت کاذب که بار را جابه‌جا می‌کند و زنجیرهٔ cascading failure می‌سازد)، TOO LONG (کمتر گرگ‌نمایی ولی failover کند و سوختن بودجهٔ SLO)، و ADAPTIVE (دنبال کردن RTT واقعی مثل Phi Accrual — بهترین پیش‌فرض، ولی هنوز حدس است نه اثبات).

نوار پایین حکم نهایی است: مقدار «درست» وجود ندارد — توزیع RTT را اندازه بگیر و تریدآف را صریح انتخاب کن.

Read the three cards: TOO SHORT (fast detection, but false positives that shift load and chain into cascading failure), TOO LONG (rarely cries wolf, but slow failover and a burning SLO budget), and ADAPTIVE (tracking real RTT like Phi Accrual — the best default, yet still a guess, not a proof).

The bottom bar is the final verdict: no “correct” value exists — measure the RTT distribution and pick the tradeoff explicitly.

اعلامِ زودهنگامِ مرگ خطرناک استPremature death announcements are dangerous

اگر نودِ «مرده‌اعلام‌شده» واقعاً زنده باشد و وسطِ انجامِ کاری (مثلاً ارسال ایمیل) باشد، نودِ جایگزین همان کار را دوباره انجام می‌دهد → اثرِ دوباره. اگر سیستم از قبل شلوغ باشد، منتقل‌کردنِ بار می‌تواند زنجیره‌ای از اعلامِ مرگ بسازد تا جایی که همه، همه را مرده بدانند و کلِ سیستم بایستد.

If the “declared-dead” node is actually alive and mid-task (e.g. sending an email), the replacement performs the same task again → duplicate effect. And the shifted load can chain death declarations until everyone deems everyone dead and the whole system halts.

۲2 نتیجهٔ نامشخص و بازتلاشِ امنUnknown outcomes and safe retry

۲.۱. ماشینِ وضعیتِ یک درخواست2.1 A request’s state machine

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

مثال فنی: حالتِ unknownِ عملیات

وضعیت اولیه: کلاینت درخواست را فرستاده و تایمر منقضی شده است.

اجرای مرحله‌به‌مرحله: حالت رابط کاربر به «در حال بررسی» می‌رود؛ استعلام GET /requests/r7 نتیجهٔ committed را به سفارش ۴۲ وصل می‌کند.

نتیجه و دلیل: نمایش «قطعاً ناموفق» پیش از حل وضعیت می‌تواند کاربر را به ثبت دوباره وادار کند.

Combining “timeout means nothing” with “the service must return” gives every network request a dangerous state: outcome unknown. Three choices there: retry, give up, or learn the state another way. The diagram below is one request's full state machine.

نمودار ۲ — ماشینِ وضعیتِ نتیجهٔ درخواست (lifecycle)Diagram 2 — a request’s outcome state machine (lifecycle) باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس — نمودار ۲ را با صدای بلند بخوانیمTeacher narration — read Diagram 2 aloud

مثال فنی برای خواندنِ نمودار: حالتِ unknownِ عملیات

وضعیت اولیه: کلاینت درخواست را فرستاده و تایمر منقضی شده است.

اجرای مرحله‌به‌مرحله: حالت رابط کاربر به «در حال بررسی» می‌رود؛ استعلام GET /requests/r7 نتیجهٔ committed را به سفارش ۴۲ وصل می‌کند.

نتیجه و دلیل: نمایش «قطعاً ناموفق» پیش از حل وضعیت می‌تواند کاربر را به ثبت دوباره وادار کند.

سه لین افقی: Client request (بالا)، Retry path (وسط) و Outcomes (پایین)؛ زمان چپ‌به‌راست. مسیر اصلی: Sent با «in flight» به Awaiting Reply می‌رسد؛ از آنجا یا «reply» به Confirmed (یک اثر، پایان خوش) یا «timeout» قرمز به لوزی Timed Out با برچسب unknown outcome.

از Timed Out سه فلش همان سه انتخاب‌اند: «maybe applied» به Applied, Unconfirmed، «retry» به Retry (که با «try again» از کانال بالا به Awaiting برمی‌گردد — حلقهٔ retry)، و از آنجا «idempotent» به Deduplicated سبز یا «not idempotent» قرمز به Duplicate Effect با برچسب corruption. نکتهٔ طلایی: retry بدون idempotency از حالت وسطی به فاجعهٔ سومی می‌افتد.

Three horizontal lanes: Client request (top), Retry path (middle) and Outcomes (bottom); time flows left to right. The main path: Sent reaches Awaiting Reply via “in flight”; from there either “reply” to green Confirmed (one effect, happy end) or red “timeout” to the Timed Out diamond tagged unknown outcome.

Three arrows leave Timed Out — the three famous choices: dashed “maybe applied” down to yellow Applied, Unconfirmed, “retry” to Retry (looping back via “try again” through the top channel to Awaiting — the retry loop), and from there “idempotent” to green Deduplicated or red “not idempotent” to Duplicate Effect tagged corruption. The golden point: retry without idempotency falls from the middle state into the third disaster.

۲.۲. درخت تصمیم: حالا چه کنیم؟2.2 Decision tree: what now?

تصمیم برای بازتلاش به نوع عملیات و اطلاعاتِ موجود بستگی دارد. اگر اجرای دوباره اثر اضافه دارد، ابتدا باید نتیجه را استعلام کنیم یا از شناسهٔ ثابت برای جلوگیری از تکرار استفاده کنیم.

مثال فنی: عملیاتِ مناسبِ تکرار

وضعیت اولیه: خواندن قیمت timeout شده، ولی ثبت سفارش هم می‌تواند timeout شود.

اجرای مرحله‌به‌مرحله: خواندن را می‌توان طبق سیاست بار تکرار کرد؛ برای ثبت، هویت ثابت و استعلام نتیجه لازم است و backoff جلوی retry storm را می‌گیرد.

نتیجه و دلیل: یک سیاست retry برای همهٔ عملیات مناسب نیست؛ بی‌اثر بودن تکرار و محدودیت بار باید بررسی شوند.

After a timeout, decide structurally instead of guessing. The tree below shows the thinking order: first ask whether the operation is safely repeatable at all, then whether you can read its state, and if not, whether your write needs a guard.

نمودار ۳ — درخت تصمیمِ بازیابی پس از timeoutDiagram 3 — the post-timeout recovery decision tree باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس — نمودار ۳ را با صدای بلند بخوانیمTeacher narration — read Diagram 3 aloud

مثال فنی برای خواندنِ نمودار: عملیاتِ مناسبِ تکرار

وضعیت اولیه: خواندن قیمت timeout شده، ولی ثبت سفارش هم می‌تواند timeout شود.

اجرای مرحله‌به‌مرحله: خواندن را می‌توان طبق سیاست بار تکرار کرد؛ برای ثبت، هویت ثابت و استعلام نتیجه لازم است و backoff جلوی retry storm را می‌گیرد.

نتیجه و دلیل: یک سیاست retry برای همهٔ عملیات مناسب نیست؛ بی‌اثر بودن تکرار و محدودیت بار باید بررسی شوند.

یک ردیف تصمیم (بالا) و سه ردیف نتیجه (پایین). از Request Timed Out (outcome unknown) شروع کن. تصمیم ۱ — Operation Idempotent?: اگر بله، Retry with Idem Key؛ اگر نه، تصمیم ۲ — Outcome Queryable? (می‌توانی بپرسی «انجام دادی؟»): اگر بله، Read Back State؛ اگر نه، تصمیم ۳ — Write Needs Guard?: اگر بله، Fencing Token Write (storage rejects stale)؛ وگرنه Fail the Request — خطای صریح بهتر از حدس است.

الگوی بصری: فلش‌های افقی پررنگ مسیر اصلی‌اند و عمودی‌های کم‌رنگ شاخه‌های yes. در مصاحبه حفظ باش: «اول idempotency، بعد خواندن وضعیت، بعد محافظت نوشته، و آخر شکست صریح.»

One decision row (top) and three outcome rows (bottom). Start at Request Timed Out (outcome unknown). Decision 1 — Operation Idempotent?: yes means Retry with Idem Key; no leads to decision 2 — Outcome Queryable? (can you ask “did you do it?”): yes means Read Back State; no leads to decision 3 — Write Needs Guard?: yes means Fencing Token Write (storage rejects stale); otherwise Fail the Request — an explicit error beats a guess.

The visual pattern: bold horizontal arrows are the main path, faint vertical ones the “yes” branches. Memorize for interviews: “idempotency first, then read the state, then guard the write, and finally fail explicitly.”

جمع‌بندیِ بخشSection recap
  • idempotency key: هر درخواستِ تغییردهنده یک کلیدِ یکتا دارد و سرور تکرارِ آن کلید را به یک اثر فرو می‌کاهد.
  • بازتلاش باید محدود باشد و با backoff + jitter همراه شود تا مشتری‌ها هم‌زمان حمله نکنند (thundering herd).
  • خواندنِ وضعیت (read-back) گاهی ارزان‌تر از ایمن‌سازیِ کاملِ نوشتن است.
  • idempotency key: every mutating request carries a unique key, and the server collapses repeats of that key into one effect.
  • Retries must be bounded with backoff + jitter so clients don't attack in sync (thundering herd).
  • Reading the state back (read-back) is sometimes cheaper than fully bulletproofing the write.

۳3 ساعت‌های غیرقابل‌اعتمادUnreliable clocks

ساعتِ دستگاه‌ها همیشه دقیق و هم‌قدم نیست. بعضی ساعت‌ها برای گفتنِ زمانِ تقویمی‌اند و بعضی برای اندازه‌گیریِ مدت یا وابستگیِ رویدادها؛ جایگزین یکدیگر نیستند.

مثال فنی: ساعت عقب می‌رود

وضعیت اولیه: فرایند شروع را با ساعت دیواری 12:00:10 ثبت می‌کند؛ NTP زمان را به 12:00:05 برمی‌گرداند.

اجرای مرحله‌به‌مرحله: تفاضل دو ساعت دیواری ممکن است مدت منفی بدهد؛ سنجش elapsed time با monotonic clock مناسب‌تر است.

نتیجه و دلیل: ساعتِ مناسبِ نمایش تاریخ با ساعتِ مناسبِ سنجش مدت یکی نیست؛ monotonic محلی هم ترتیب جهانی نمی‌سازد.

Programs use clocks for two kinds of questions: durations (how many seconds did this request take?) and points in time (when was this article published?). In a distributed system each machine has its own quartz clock differing from the rest, and inter-machine links aren't instant; so “event order” scrambles easily.

۳.۱. چهار نوع ساعت3.1 Four kinds of clock

ساعت دیواری زمان تقویمی می‌دهد، ساعت یکنواخت برای فاصلهٔ زمانی مناسب است، ساعت منطقی وابستگی را دنبال می‌کند و ساعت با بازهٔ عدم‌قطعیت میزان ابهام را هم بیان می‌کند.

مثال فنی: چهار ساعت، چهار سؤال

وضعیت اولیه: برای درخواست هم تاریخ ثبت می‌خواهیم، هم مدت انتظار، هم ترتیب علّی و هم تشخیص هم‌زمانی.

اجرای مرحله‌به‌مرحله: wall clock تاریخ می‌دهد؛ monotonic مدت محلی؛ Lamport ترتیب سازگار با علت؛ vector clock می‌تواند concurrent بودن را آشکار کند.

نتیجه و دلیل: جایگزین کردن همهٔ این نیازها با timestampِ دیواری یک قرارداد درست برای توزیع نمی‌سازد.

Modern computers have at least two clocks: time-of-day (wall clock, e.g. System.currentTimeMillis) and monotonic (stopwatch, e.g. System.nanoTime). But ordering in distributed systems offers two more options: the logical clock and the uncertainty-interval clock. The diagram below lines all four up like a UML class diagram.

نمودار ۴ — نمودار کلاسِ انواع ساعتDiagram 4 — the clock-types class diagram باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس — نمودار ۴ را با صدای بلند بخوانیمTeacher narration — read Diagram 4 aloud

مثال فنی برای خواندنِ نمودار: چهار ساعت، چهار سؤال

وضعیت اولیه: برای درخواست هم تاریخ ثبت می‌خواهیم، هم مدت انتظار، هم ترتیب علّی و هم تشخیص هم‌زمانی.

اجرای مرحله‌به‌مرحله: wall clock تاریخ می‌دهد؛ monotonic مدت محلی؛ Lamport ترتیب سازگار با علت؛ vector clock می‌تواند concurrent بودن را آشکار کند.

نتیجه و دلیل: جایگزین کردن همهٔ این نیازها با timestampِ دیواری یک قرارداد درست برای توزیع نمی‌سازد.

یک نمودار کلاس UML است نه جریان. بالای وسط کلاس انتزاعی Clock با سه ویژگی (read()، نوع instant/duration، و قطعیت known/hidden) و زیرش bus آبی با فلش تعمیم: چهار زیرکلاس. از راست به چپ: UncertaintyClock (نارنجی، خروجی [earliest, latest])، LogicalClock (بنفش، فقط ترتیب، بدون ثانیه)، MonotonicClock (فیروزه‌ای، هرگز به عقب نمی‌پرد، بین نودها قابل مقایسه نیست) و TimeOfDayClock (آبی، با NTP همگام، می‌تواند بپرد).

دو جعبهٔ خط‌چین حکم عملی‌اند: «خواندن ساعت یک نقطه نیست، یک بازه است» و «کدام ساعت برای کدام کار: timeout با monotonic، deadline با time-of-day، ترتیب با logical، و snapshot سراسری با uncertainty». جملهٔ پایانی: ارث‌بری دربارهٔ تضمین‌هاست نه پیاده‌سازی.

This is a UML class diagram, not a flow. Top center sits the abstract Clock class with three attributes (read(), instant/duration kind, known/hidden certainty) and below it a blue bus with a generalization arrow: four subclasses. Right to left: UncertaintyClock (orange, [earliest, latest] output), LogicalClock (purple, order only, no seconds), MonotonicClock (teal, never jumps back, not comparable across nodes) and TimeOfDayClock (blue, NTP-synced, can jump).

The two dashed boxes give the practical verdicts: “a clock reading is not a point but an interval”, and “which clock for which job: timeouts with monotonic, deadlines with time-of-day, ordering with logical, global snapshots with uncertainty”. Closing line: the inheritance is about guarantees, not implementation.

۳.۲. همگام‌سازی، drift و جهش‌های ساعت3.2 Synchronization, drift and clock jumps

  • کوارتزِ معمولی تا حدود 200 ppm خطا دارد (≈ ۱۷ ثانیه در روز اگر روزی یک‌بار sync شود)؛ دما هم روی drift اثر می‌گذارد.
  • اگر ساعتِ ماشین زیاد با سرورِ NTP فاصله بگیرد، ممکن است اجباراً ریست شود و برنامه ببیند که زمان به عقب پرید!
  • یک فایروالِ اشتباه می‌تواند NTP را ببُرد و کسی مدت‌ها نفهمد؛ drift هم آرام آرام جمع می‌شود.
  • خطای NTP روی اینترنت عمومی حدود ده‌ها میلی‌ثانیه است و در شلوغی ممکن است به بالای 100ms برسد؛ بعضی سرورهای NTP هم اشتباه گزارش می‌دهند.
  • leap second دقیقه‌ای ۵۹ یا ۶۱ ثانیه‌ای می‌سازد و سیستم‌های آماده‌نشده را می‌شکند (راهِ عملی: «smearing» یا پخشِ تدریجیِ اصلاح). از ۲۰۳۵ دیگر leap second نخواهیم داشت.
  • در ماشینِ مجازی، pause شدنِ VM از دیدِ برنامه مثلِ جهشِ ناگهانیِ زمان است و کلاینتِ NTP داخلِ VM هم نمی‌فهمد.
  • روی دستگاه‌های کنترل‌نشده (موبایلِ کاربر) اصلاً به ساعتِ سخت‌افزار اعتماد نکن — بعضی کاربران عمداً ساعت را جابه‌جا می‌کنند!
  • Ordinary quartz drifts up to ~200 ppm (≈ 17 seconds a day at one sync per day); temperature affects drift too.
  • A machine clock far from the NTP server may be forcibly reset, and the app sees time jump back!
  • One wrong firewall rule can cut NTP and nobody notices for ages; drift quietly accumulates.
  • Public-internet NTP error is tens of milliseconds, over 100ms when busy; some NTP servers misreport too.
  • A leap second makes a 59- or 61-second minute and breaks unprepared systems (practical fix: “smearing” the correction gradually). No more leap seconds after 2035.
  • On a VM, a pause looks to the app like a sudden time jump, and the in-VM NTP client never learns.
  • On unmanaged devices (the user's phone) never trust the hardware clock — some users shift it on purpose!
خطای ساعت بی‌صدا استClock error is silent

ساعت سخت‌افزاری کم‌کم جلو یا عقب می‌افتد و همگام‌سازی ممکن است آن را اصلاح کند. بنابراین زمانِ دیواری می‌تواند جهش کند یا نسبت به گره دیگر متفاوت باشد.

مثال فنی: اختلاف ساعت دو ماشین

وضعیت اولیه: ساعت A سه ثانیه جلو و ساعت B دو ثانیه عقب است.

اجرای مرحله‌به‌مرحله: رخداد بعدی روی B می‌تواند timestamp کوچک‌تری از رخداد قبلی A داشته باشد؛ اصلاح ساعت هم ممکن است جهش یا تغییر سرعت ایجاد کند.

نتیجه و دلیل: timestamp خام دو میزبان را بدون مدل خطا به ترتیب واقعی تبدیل نمی‌کنیم؛ عدم قطعیت بخشی از داده است.

A broken CPU stops the system outright and you notice instantly. But a broken quartz clock or NTP leaves everything silently “healthy” while data corrupts quietly. If your software depends on synchronized clocks, monitor the offset between cluster clocks and cut out drifting nodes.

۳.۳. ترتیب‌دادن با timestamp و فاجعه‌ی LWW3.3 Ordering with timestamps and the LWW disaster

در Last Write Wins، نسخه‌ای با زمانِ بزرگ‌تر انتخاب می‌شود. اگر ساعت‌ها دقیق نباشند، زمانِ بزرگ‌تر الزاماً نشان‌دهندهٔ تغییرِ واقعاً جدیدتر نیست و داده ممکن است بی‌صدا حذف شود.

مثال فنی: LWW با ساعت جلو افتاده

وضعیت اولیه: A مقدار v1 را با timestamp=105 ثبت می‌کند؛ B در زمان واقعیِ بعدی v2 را با timestamp=102 ثبت می‌کند.

اجرای مرحله‌به‌مرحله: حل تعارضِ بزرگ‌ترین timestamp، v1 را نگه می‌دارد.

نتیجه و دلیل: رویداد جدیدتر می‌تواند حذف شود؛ LWW فقط ترتیب timestampها را اجرا می‌کند، نه معنای درستِ آخرین ویرایش را.

The most dangerous temptation: ordering writes by timestamp. In last-write-wins (LWW), the write with the greatest timestamp wins. If two nodes' clocks skew slightly, a “newer” write may carry a smaller timestamp and vanish forever.

نمودار ۵ — تایم‌لاینِ clock skew و گم‌شدنِ یک نوشتهDiagram 5 — the clock-skew timeline and a lost write باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس — نمودار ۵ را با صدای بلند بخوانیمTeacher narration — read Diagram 5 aloud

مثال فنی برای خواندنِ نمودار: LWW با ساعت جلو افتاده

وضعیت اولیه: A مقدار v1 را با timestamp=105 ثبت می‌کند؛ B در زمان واقعیِ بعدی v2 را با timestamp=102 ثبت می‌کند.

اجرای مرحله‌به‌مرحله: حل تعارضِ بزرگ‌ترین timestamp، v1 را نگه می‌دارد.

نتیجه و دلیل: رویداد جدیدتر می‌تواند حذف شود؛ LWW فقط ترتیب timestampها را اجرا می‌کند، نه معنای درستِ آخرین ویرایش را.

بالا سه جعبهٔ شیء: Node 1 · clock 42.004 («client A writes x = 1»)، Node 3 · clock 42.003 («client B increments x = 2» با یادداشت «skew under 3 ms — still wrong»)، و Node 2 · receives both که قاعدهٔ LWW را اجرا می‌کند: «x = 1 (42.004) wins» و «x = 2 (42.003) is discarded». فلش‌های سبز تکثیر را نشان می‌دهند.

قلب شکل دو بخش پایین است. باند ① «Real time»: دایرهٔ آبی «A writes x=1» چپ و سبز «B increments to x=2» راست (B مقدار A را خوانده، پس علّی دیرتر است). باند ② «Timestamp order»: همان دو رویداد جابه‌جا — x=2 با مهر کوچک‌تر چپ («LWW drops this one») و x=1 راست («LWW keeps this one»). دو ترتیب ناسازگارند و جعبهٔ صورتی می‌گوید «increment lost» بدون هیچ خطایی. جعبهٔ زرد راه‌حل‌ها: مهر بالاتر از timestamp فعلی، logical clock، یا صبر Spanner تا سپری شدن بازهٔ عدم قطعیت.

Three object boxes on top: Node 1 · clock 42.004 (“client A writes x = 1”), Node 3 · clock 42.003 (“client B increments x = 2”, noted “skew under 3 ms — still wrong”), and Node 2 · receives both, which executes LWW: “x = 1 (42.004) wins”, “x = 2 (42.003) is discarded”. Green arrows show replication.

The heart is the two bands below. Band ① “Real time”: blue circle “A writes x=1” left, green “B increments to x=2” right (B read A's value first, so B is causally later). Band ② “Timestamp order”: the same two events swapped — x=2 with the smaller stamp left (“LWW drops this one”), x=1 right (“LWW keeps this one”). The two orders disagree, and the pink box says “increment lost” with no error raised. The yellow box lists fixes: stamping above the current timestamp, logical clocks, or Spanner's wait past the uncertainty interval.

۳.۴. بازه‌ی عدم قطعیت و راه‌حلِ Spanner3.4 Uncertainty intervals and the Spanner fix

اگر زمان همراه با بازهٔ عدم‌قطعیت بیان شود، می‌توان دربارهٔ تقدم بعضی رویدادها دقیق‌تر استدلال کرد. در طراحی‌هایی مثل Spanner، انتظارِ کنترل‌شده بخشی از راهِ تضمین ترتیب واقعی است.

مثال فنی: بازهٔ زمان به‌جای نقطهٔ قطعی

وضعیت اولیه: API زمان را به‌شکل بازهٔ [100,106] برمی‌گرداند و timestamp کامیت 106 انتخاب شده است.

اجرای مرحله‌به‌مرحله: در الگوی commit-wait، تأیید تا وقتی عقب می‌افتد که کران پایین زمان از ۱۰۶ عبور کند.

نتیجه و دلیل: این ایدهٔ Spanner عدم قطعیتِ محدود را در قرارداد وارد می‌کند؛ ساعت دقیق بدون انتظار و replication به‌تنهایی کافی نیست.

If a clock returns an interval instead of a number ([earliest, latest]), we can reason: non-overlapping intervals of two events mean their order is certain; only overlapping intervals leave us unsure which came first. Google Spanner's TrueTime and Amazon's ClockBound give exactly this. So a transaction's timestamp can't violate causality, Spanner waits out the interval's width (commit wait) — with GPS and atomic clocks in every datacenter shrinking the width to ~7ms.

نمودار ۶ — مسیرِ تولیدِ یک timestamp (dataflow)Diagram 6 — the timestamp-production path (dataflow) باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس — نمودار ۶ را با صدای بلند بخوانیمTeacher narration — read Diagram 6 aloud

مثال فنی برای خواندنِ نمودار: بازهٔ زمان به‌جای نقطهٔ قطعی

وضعیت اولیه: API زمان را به‌شکل بازهٔ [100,106] برمی‌گرداند و timestamp کامیت 106 انتخاب شده است.

اجرای مرحله‌به‌مرحله: در الگوی commit-wait، تأیید تا وقتی عقب می‌افتد که کران پایین زمان از ۱۰۶ عبور کند.

نتیجه و دلیل: این ایدهٔ Spanner عدم قطعیتِ محدود را در قرارداد وارد می‌کند؛ ساعت دقیق بدون انتظار و replication به‌تنهایی کافی نیست.

چهار ستون (Time Source، Time Service، Local Clock، Using Time) و دو مسیر موازی. مسیر بالا («best-effort»): GPS Receiver ← satellite time ← NTP Servers ← NTP corrections ← Wall Clock ← wall-clock stamps ← LWW Timestamps قرمز (unsafe if skewed) — همان مسیر خطرناک نمودار ۵.

مسیر پایین («bounded»): Atomic Clock ← disciplined signal ← PTP + ClockBound ← reported error bound ← Uncertainty (earliest .. latest) ← wait out the width ← Commit Wait (Spanner snapshots). درس: مسیر بالا خطا را پنهان می‌کند، مسیر پایین خطا را اندازه می‌گیرد و گزارش می‌دهد — به بهای سخت‌افزار و صبر.

Four stages (Time Source, Time Service, Local Clock, Using Time) and two parallel paths. The upper (“best-effort”) path: GPS Receiver → satellite time → NTP Servers → NTP corrections → Wall Clock → wall-clock stamps → red LWW Timestamps (unsafe if skewed) — the same dangerous path as Diagram 5.

The lower (“bounded”) path: Atomic Clock → disciplined signal → PTP + ClockBound → reported error bound → Uncertainty (earliest .. latest) → wait out the width → Commit Wait (Spanner snapshots). Lesson: the upper path hides error, the lower measures and reports it — at the price of hardware and patience.

۴4 توقفِ فرآیند، lease و fencingProcess pauses, leases and fencing

۴.۱. هر نخی ممکن است در هر لحظه pause شود4.1 Any thread may pause at any point

یک فرایند می‌تواند بی‌خبر برای مدتی متوقف شود و بعد ادامه دهد. اجاره یا lease مجوزی زمان‌دار است؛ ادامهٔ اجرای کد بعد از مکث ثابت نمی‌کند مجوز هنوز معتبر است.

مثال فنی: Pause در جای حساس

وضعیت اولیه: worker مجوز ۵ثانیه‌ای گرفته و پیش از نوشتن ۱۰ ثانیه متوقف می‌شود.

اجرای مرحله‌به‌مرحله: worker دیگری پس از انقضای مجوز کار را می‌گیرد؛ worker اول از pause برمی‌گردد و هنوز خط بعدی کد را اجرا می‌کند.

نتیجه و دلیل: چک کردن مجوز پیش از pause تضمین اعتبار هنگام اثر نیست؛ GC، تعلیق VM یا زمان‌بندی می‌تواند بین این دو رخ دهد.

Suppose each shard has one leader and only the leader may write. How does it stay sure it's still leader? The usual answer is a lease: a lock with a timeout that must be renewed before expiry. Now look at this code:

while (true) {
    request = getIncomingRequest();
    // Ensure that the lease always has at least 10 seconds remaining
    if (lease.expiryTimeMillis - System.currentTimeMillis() < 10000) {
        lease = lease.renew();
    }
    if (lease.isValid()) {
        process(request);
    }
}

دو اشکال دارد: اول اینکه به ساعتِ همگام تکیه کرده (انقضا را ماشینِ دیگری تنظیم کرده و با ساعتِ محلی مقایسه می‌شود)؛ دوم و مهم‌تر اینکه فرض کرده بینِ بررسیِ ساعت و پردازشِ درخواست، زمانِ کمی می‌گذرد. حالا تصور کن همان نخ ۱۵ ثانیه pause شود: lease منقضی شده، نودِ دیگری leader شده، و وقتی نخ بیدار می‌شود، اصلاً نمی‌داند خواب بوده — و همان درخواستِ ناامن را اجرا می‌کند.

It has two flaws: first it leans on a synchronized clock (expiry set by another machine, compared against the local clock); second and more importantly, it assumes little time passes between checking the clock and processing the request. Now imagine that thread pauses for 15 seconds: the lease expires, another node becomes leader, and when the thread wakes it never knew it slept — executing the same unsafe request.

دلایلِ واقعیِ چنین pauseهایی کم نیستند:

Real causes of such pauses are far from rare:

  • GC: توقفِ «stop-the-world» در JVMهای قدیمی تا چند دقیقه هم گزارش شده؛ GCهای مدرن بهترند اما هنوز محسوس‌اند.
  • مهاجرتِ زنده‌ی VM (live migration): توقفِ دلخواه با مدتِ نامعلوم.
  • context switch و steal time در hypervisor؛ در بارِ سنگین، صفِ نخ‌ها طولانی است.
  • I/O سنکرون، page fault و swap؛ در حالتِ فشارِ حافظه، سیستم وارد thrashing می‌شود.
  • سیگنالِ SIGSTOP (مثلاً Ctrl-Z اشتباهِ یک مهندسِ عملیات).
  • GC: “stop-the-world” pauses of old JVMs reached minutes; modern GCs are better but still noticeable.
  • Live VM migration: arbitrary stalls of unknown length.
  • context switch and hypervisor steal time; under heavy load thread queues run long.
  • Synchronous I/O, page faults and swap; under memory pressure the system thrashes.
  • A SIGSTOP signal (e.g. an ops engineer's mistaken Ctrl-Z).
ابزارهای thread-safe به دادِ توزیع‌شده نمی‌رسندThread-safe tools won’t save the distributed

mutex، شمارنده‌ی اتمیک و صفِ بلاک‌شونده روی یک ماشین عالی‌اند، چون حافظه‌ی مشترک داریم. در سیستم توزیع‌شده فقط پیام داریم؛ پس باید فرض کنی اجرای هر نخ می‌تواند در هر نقطه‌ای متوقف شود، در حالی که بقیه‌ی دنیا جلو می‌رود و ممکن است تو را «مرده» اعلام کنند. (سیستم‌های hard real-time مثل کیسه‌ی هوا این توقف‌ها را با هزینه‌ی بالا حذف می‌کنند؛ برای سرویس‌های معمولی به‌صرفه نیست.)

Mutexes, atomic counters and blocking queues are great on one machine, where memory is shared. Distributed systems have only messages; so assume any thread's execution can stall at any point while the rest of the world moves on and may declare you “dead”. (Hard real-time systems like airbags remove these stalls at great cost; not economical for ordinary services.)

۴.۲. زامبی، split brain و راه‌حلِ fencing4.2 Zombies, split brain and the fencing fix

گرهِ قدیمی ممکن است پس از پایانِ اجاره هنوز خودش را صاحب کار بداند. Fencing با بررسیِ مجوز در مقصدِ نوشتن، مانعِ اثرگذاریِ چنین نویسنده‌ای می‌شود.

مثال فنی: زامبی با مجوز قدیمی

وضعیت اولیه: A توکن ۴۱ داشته و متوقف شده؛ B با توکن ۴۲ فایل را به‌روز کرده است.

اجرای مرحله‌به‌مرحله: A برمی‌گردد و با ۴۱ می‌نویسد؛ مقصدی که آخرین توکن ۴۲ را نگه می‌دارد آن را رد می‌کند.

نتیجه و دلیل: fencing باید در همان منبعِ اثر اعمال شود؛ صرفِ انتخاب رهبر تازه، درخواستِ درراهِ رهبر قدیمی را خنثی نمی‌کند.

An old lease holder that hasn't realized its lease ended and keeps writing is called a zombie. Two famous book scenarios (both really happened in HBase): (1) client 1 pauses, the lease expires, client 2 writes, then client 1 returns and writes the same file; (2) client 1 dies but its write request sticks in the network and arrives only after the lease moved on. Both leave two writers interleaving and corrupt data.

راه‌حلِ ساده‌اندیشانه (خاموش‌کردنِ زامبی از راهِ شبکه یا hypervisor) کافی نیست: در برابرِ تأخیرِ بلندِ حالتِ (۲) محافظت نمی‌کند، ممکن است همه، هم‌زمان همدیگر را بکُشند، و تا زامبی کشف شود داده خراب شده. راه‌حلِ درست fencing token است: هر بار که lock service قفل را می‌دهد، یک عددِ افزایشی هم می‌دهد و سرویسِ ذخیره‌سازی فقط نوشته‌هایی را می‌پذیرد که tokenشان از آخرین tokenِ پذیرفته‌شده بزرگ‌تر باشد.

The naive fix (killing the zombie over the network or via the hypervisor) isn't enough: it doesn't guard against scenario (2)'s long delay, everyone may kill everyone at once, and by the time the zombie is found the data is corrupt. The right fix is a fencing token: every lock grant carries an increasing number, and the storage service accepts only writes whose token exceeds the last accepted one.

نمودار ۷ — ماشینِ وضعیتِ lease، زامبی و fencingDiagram 7 — the lease, zombie and fencing state machine باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس — نمودار ۷ را با صدای بلند بخوانیمTeacher narration — read Diagram 7 aloud

مثال فنی برای خواندنِ نمودار: زامبی با مجوز قدیمی

وضعیت اولیه: A توکن ۴۱ داشته و متوقف شده؛ B با توکن ۴۲ فایل را به‌روز کرده است.

اجرای مرحله‌به‌مرحله: A برمی‌گردد و با ۴۱ می‌نویسد؛ مقصدی که آخرین توکن ۴۲ را نگه می‌دارد آن را رد می‌کند.

نتیجه و دلیل: fencing باید در همان منبعِ اثر اعمال شود؛ صرفِ انتخاب رهبر تازه، درخواستِ درراهِ رهبر قدیمی را خنثی نمی‌کند.

چهار ردیف: Lease holder، Lock service، Storage guard و Outcome. از Lease Held (token 33) شروع کن: «renew» به Renewals (شمارش معکوس TTL) و «TTL ends» به Lease Expires؛ هم‌زمان دارنده با pause به Process Paused (GC / VM suspend) می‌رود. از آنجا سه راه است: «resumes» سبز به Renewals، «pause beats TTL» قرمز به Lease Expires، و «wakes up» به Zombie Writer (stale token 33).

از Lease Expires «new owner» به New Holder (token 34) و «writes 34» به Write Token 34 می‌رسد؛ ولی نوشتهٔ زامبی («delayed write 33») از کانال بالای نمودار — استعارهٔ درخواست دیررسیده — وارد Write Token 33 می‌شود. دو پایان: «rejected 33» به Zombie Fenced سبز (33 < 34 rejected) یا «no token check» به Split Brain (interleaved writes) — همان فاجعهٔ HBase.

Four rows: Lease holder, Lock service, Storage guard and Outcome. Start at green Lease Held (token 33): “renew” down to Renewals (TTL countdown), “TTL ends” to Lease Expires; meanwhile the holder pauses into Process Paused (GC / VM suspend). Three exits leave it: green “resumes” back to Renewals (the normal return), red “pause beats TTL” to Lease Expires (the stall outlived the lease), and red “wakes up” to Zombie Writer (tagged “stale token 33”).

From Lease Expires, “new owner” reaches New Holder (client 2, token 34) and “writes 34” lands in Write Token 34; but the zombie's write (“delayed write 33”) travels the diagram's top channel — the metaphor of a late network request — into Write Token 33. Two endings: red “rejected 33” to green Zombie Fenced (“33 < 34 rejected”, the token mechanism worked), or red “no token check” to Split Brain (“interleaved writes”) — exactly the HBase disaster without token checks.

۴.۳. fencing token در عمل4.3 fencing tokens in practice

توکنِ fencing عددی افزایشی برای دوره‌های مالکیت است. مقصد، بزرگ‌ترین توکنِ پذیرفته‌شده را نگه می‌دارد و درخواستِ قدیمی‌تر را نمی‌پذیرد؛ صرف تولید توکن کافی نیست.

مثال فنی: بررسی توکن اتمیک است

وضعیت اولیه: storage بیشترین توکن پذیرفته‌شده را 42 نگه می‌دارد.

اجرای مرحله‌به‌مرحله: نوشتن با ۴۱ رد می‌شود؛ پذیرفتن ۴۳ باید همراه به‌روزرسانیِ بیشترین توکن و اعمال اثر، اتمیک باشد.

نتیجه و دلیل: اگر بررسی و نوشتن جدا شوند، رقابت دوباره ایجاد می‌شود؛ توکن افزایشی بدون اجرای صحیح در مقصد کافی نیست.

The rule is simple: every storage write must carry the writer's current token, and storage remembers the greatest token seen. The diagram below retells Figure 9-6 of the book.

نمودار ۸ — ردکردنِ نوشته‌ی زامبی با token (sequence)Diagram 8 — rejecting the zombie write with a token (sequence) باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس — نمودار ۸ را با صدای بلند بخوانیمTeacher narration — read Diagram 8 aloud

مثال فنی برای خواندنِ نمودار: بررسی توکن اتمیک است

وضعیت اولیه: storage بیشترین توکن پذیرفته‌شده را 42 نگه می‌دارد.

اجرای مرحله‌به‌مرحله: نوشتن با ۴۱ رد می‌شود؛ پذیرفتن ۴۳ باید همراه به‌روزرسانیِ بیشترین توکن و اعمال اثر، اتمیک باشد.

نتیجه و دلیل: اگر بررسی و نوشتن جدا شوند، رقابت دوباره ایجاد می‌شود؛ توکن افزایشی بدون اجرای صحیح در مقصد کافی نیست.

یک sequence با چهار شرکت‌کننده (Client 1، Lock Service، Client 2، Storage) و سه پرده. پردهٔ اول: «acquire lease» و پاسخ «lease + token 33»، بعد «write x=1, token 33» با «200 OK, stored 33» (اولین نوشته دارندگان کهنه را fence می‌کند). پردهٔ دوم: نوار Client 1 قطع است («client 1 here paused — a 15 s GC pause»)؛ Client 2 «lease + token 34» می‌گیرد و «write x=2, token 34» را موفق می‌نویسد.

پردهٔ سوم: نوار Client 1 برمی‌گردد و «write x=3, token 33» — نوشتهٔ زامبی — با «409 rejected: 33 < 34» قرمز رد می‌شود. نکته: امنیت را ترتیب tokenها می‌سازد نه ساعت — به همین دلیل همین مفهوم همه‌جا نام‌های مختلف دارد (sequencer در Chubby، epoch در Kafka، ballot/term در Paxos/Raft).

A sequence diagram with four participants (Client 1, Lock Service, Client 2, Storage) in three acts. Act one: “acquire lease” answered with “lease + token 33”, then “write x=1, token 33” answered “200 OK, stored 33” (the first write fences stale holders). Act two: Client 1's bar is cut (“client 1 here paused — a 15 s GC pause”); Client 2 takes “lease + token 34” (a bigger number!) and successfully writes “write x=2, token 34”.

Act three: Client 1's bar resumes with “write x=3, token 33” — the zombie write — rejected in red: “409 rejected: 33 < 34”. The reading point: token order creates the safety, not clock time — which is why the same idea wears many names (sequencer in Chubby, epoch in Kafka, ballot/term in Paxos/Raft).

جمع‌بندیِ بخشSection recap

امنیتِ یک lease فقط به «یکی بودنِ دارنده» نیست — چنین چیزی را نمی‌توان تضمین کرد. آنچه می‌توانی تضمین کنی این است که نوشته‌های کهنه بی‌اثر شوند. fencing دقیقاً همین است: یک ترتیبِ قطعیِ افزایشی که storage آن را اجرا می‌کند، حتی اگر زامبی‌ها هنوز زنده باشند.

A lease's safety isn't about “having exactly one holder” — that can't be guaranteed. What you can guarantee is that stale writes take no effect. That's exactly fencing: a strictly increasing order enforced by storage, even while zombies still walk.

۵5 دانش، حقیقت و دروغKnowledge, truth and lies

۵.۱. quorum و حکومتِ اکثریت5.1 quorum and majority rule

برای بعضی تصمیم‌ها، نظرِ یک گره کافی نیست و تأییدِ اکثریت لازم می‌شود. در پروتکلِ درست، مجموعه‌های رأی هم‌پوشان‌اند تا دو گروهِ جدا نتوانند تصمیم‌های متعارضِ معتبر بسازند.

مثال فنی: اکثریت هنگام جدایی شبکه

وضعیت اولیه: پنج رأی‌دهنده به گروه‌های {A,B,C} و {D,E} تقسیم شده‌اند.

اجرای مرحله‌به‌مرحله: فقط گروه سه‌عضوی می‌تواند اکثریت بگیرد؛ گروه دو‌عضوی برای عملیاتِ نیازمند quorum منتظر می‌ماند.

نتیجه و دلیل: هر دو گروه اکثریت جدا نمی‌سازند؛ پروتکل epoch و رأی‌دهی هنوز باید مانع پذیرش هم‌زمانِ رهبران نامعتبر شود.

The book's nightmare scenario: a node can receive every message while its outbound messages get lost. Everyone declares it dead after timeouts while it screams “I'm not dead!” — and nobody hears. Or: a node pauses a minute, everyone buries it, then it returns healthy and surprised!

درس این است که هیچ نودی نمی‌تواند به قضاوتِ خودش تکیه کند. به همین دلیل الگوریتم‌های توزیع‌شده به quorum (رأی‌گیری بینِ نودها) تکیه می‌کنند: تصمیم‌ها به حداقلِ آرا نیاز دارند. معمولاً اکثریتِ مطلق (بیش از نصف) — با ۳ نود، ۱ خرابی قابل‌تحمل است و با ۵ نود، ۲ خرابی. و مهم‌تر: نمی‌تواند دو اکثریتِ متناقض هم‌زمان وجود داشته باشد. اگر quorum تو را مرده اعلام کند، حتی اگر زنده باشی باید کنار بروی.

The lesson: no node may trust its own judgment. So distributed algorithms lean on quorum (voting among nodes): decisions need a minimum of votes. Usually an absolute majority (over half) — 3 nodes tolerate 1 failure, 5 nodes tolerate 2. And crucially: two contradictory majorities can't exist at once. If a quorum declares you dead, step aside even if you're alive.

۵.۲. خرابی‌های بیزانسی5.2 Byzantine faults

خرابی بیزانسی یعنی گره ممکن است اطلاعات غلط یا متناقض بفرستد. این با گرهِ خاموش یا کند فرق دارد و تحمل آن معمولاً پروتکل و فرض‌های متفاوتی می‌خواهد.

مثال فنی: نودِ دروغ‌گو در برابر نودِ خاموش

وضعیت اولیه: نود A به B می‌گوید نتیجه x و به C می‌گوید نتیجه y.

اجرای مرحله‌به‌مرحله: این رفتار با crash یا تأخیر متفاوت است؛ الگوریتمِ صرفاً crash-tolerant لزوماً چنین تناقضی را تحمل نمی‌کند.

نتیجه و دلیل: مدل اعتماد تعیین‌کننده است؛ checksum خطای تصادفی و امضای پیام، به‌تنهایی همهٔ رفتار بیزانسی را حل نمی‌کنند.

So far we assumed nodes are unreliable but honest: they may be slow or silent, but their answers never lie. If nodes can lie (forged messages, conflicting votes), the problem becomes the “Byzantine generals” — far harder.

  • جایی که واقعاً لازم است: کنترلِ پرواز (تخریبِ حافظه با پرتو)، سیستم‌های چندحزبی با طرف‌های متخاصم (بلاک‌چین).
  • جایی که لازم نیست: دیتاسنترِ خودت با نودهای تحتِ کنترلت — پروتکل‌های بیزانسی گران‌اند و معمولاً عملی نیستند.
  • اگر یک باگ در نرم‌افزار باشد و همه‌ی نودها نسخه‌ی یکسان داشته باشند، تحملِ بیزانسی هم نجاتت نمی‌دهد؛ برای آن به چند پیاده‌سازیِ مستقل نیاز است.
  • در برابرِ مهاجم، همچنان authentication، رمزنگاری و firewall اصلی‌ترین سپرند.
  • شکل‌های ضعیفِ دروغ‌گویی را جدی بگیر: checksum در لایه‌ی برنامه، اعتبارسنجیِ ورودی، و کلاینتِ NTP چندسروره که outlier را حذف می‌کند.
  • Where truly needed: flight control (memory damage by radiation), multi-party systems with hostile sides (blockchain).
  • Where not: your own datacenter with controlled nodes — Byzantine protocols are expensive and usually impractical.
  • If the bug is in the software and all nodes run the same version, Byzantine tolerance won't save you; that needs several independent implementations.
  • Against attackers, authentication, encryption and firewalls remain the main shield.
  • Take weak forms of lying seriously: app-layer checksums, input validation, and multi-server NTP clients dropping outliers.

۵.۳. مدلِ سیستم، safety و liveness5.3 System models, safety and liveness

مدل سیستم فرض‌ها را روشن می‌کند. Safety یعنی اتفاقِ ممنوع رخ ندهد؛ liveness یعنی تحت شرایط مشخص، سیستم سرانجام پیشرفت کند. برای ارزیابیِ درستی، هر دو لازم‌اند.

مثال فنی: درستی حتی هنگام توقف پیشرفت

وضعیت اولیه: قانون سیستم «یک صندلی به دو نفر فروخته نشود» است و شبکه دوپاره شده است.

اجرای مرحله‌به‌مرحله: توقف فروش در اقلیت می‌تواند safety را حفظ کند، ولی تکمیل درخواست‌های آن بخش را موقتاً عقب بیندازد.

نتیجه و دلیل: safety می‌گوید چه نتیجهٔ بدی هرگز رخ ندهد؛ liveness می‌گوید تحت فرض‌های پیشرفت، کار بالاخره انجام شود.

To talk about an algorithm's “correctness” we must first write down its assumed world: the system model. The model has two dimensions: timing assumptions (synchronous, partially synchronous, asynchronous) and failure assumptions (crash-stop, crash-recovery, partial slowness, Byzantine). The diagram below maps this space like a class diagram.

نمودار ۹ — فضای مدلِ سیستم و خواصِ safety/livenessDiagram 9 — the system-model space and safety/liveness باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس — نمودار ۹ را با صدای بلند بخوانیمTeacher narration — read Diagram 9 aloud

مثال فنی برای خواندنِ نمودار: درستی حتی هنگام توقف پیشرفت

وضعیت اولیه: قانون سیستم «یک صندلی به دو نفر فروخته نشود» است و شبکه دوپاره شده است.

اجرای مرحله‌به‌مرحله: توقف فروش در اقلیت می‌تواند safety را حفظ کند، ولی تکمیل درخواست‌های آن بخش را موقتاً عقب بیندازد.

نتیجه و دلیل: safety می‌گوید چه نتیجهٔ بدی هرگز رخ ندهد؛ liveness می‌گوید تحت فرض‌های پیشرفت، کار بالاخره انجام شود.

دو درخت تعمیم UML. چپ کلاس انتزاعی TimingModel با سه زیرکلاس: Synchronous (سقف مشخص برای همه‌چیز)، PartiallySynchronous (قاب برجسته: بیشتر اوقات سقف دارد، گاهی نه) و Asynchronous (هیچ فرض زمانی). راست FaultModel با چهار زیرکلاس: CrashStop، CrashRecovery (قاب برجسته)، Limping (fail-slow) (از خرابی تمیز سخت‌تر) و Byzantine (می‌تواند دروغ بگوید). جعبهٔ زرد وسط انتخاب عملی است: partially synchronous + crash-recovery.

ردیف پایین دو خاصیت درستی: Safety («هیچ اتفاق بدی نیفتد» — نقضش یک لحظهٔ مشخص و غیرقابل جبران است) و Liveness («بالاخره اتفاق خوبی بیفتد» — نشانه‌اش کلمهٔ eventually؛ eventual consistency هم liveness است). safety باید در همهٔ شرایط بماند ولی liveness می‌تواند فرض بازیابی داشته باشد. و جملهٔ آخر: مدل‌ها ساده‌سازی‌اند — یک دیسک پاک‌شده می‌تواند فرض stable storage را بشکند.

Two UML generalization trees. Left, the abstract TimingModel with three subclasses: blue Synchronous (a stated ceiling for everything), highlighted orange PartiallySynchronous (usually bounded, sometimes not) and teal Asynchronous (no timing assumption, not even a clock). Right, the abstract FaultModel with four subclasses: CrashStop, highlighted orange CrashRecovery (returns but lost memory), Limping (fail-slow) (answers health checks yet does no work — harder than a clean crash) and Byzantine (may lie).

The dashed yellow box in the middle is the practical pick: partially synchronous + crash-recovery. The bottom row defines the two correctness properties. Blue Safety: “nothing bad ever happens” — its violation has a specific moment and is irreparable (a replayed token). Teal Liveness: “something good eventually happens” — possibly false now, true later; “eventually” in a definition marks it (eventual consistency is liveness too). Safety must hold in all conditions, but liveness may assume recovery (“if the network returns and a majority survives, answers come”). And never forget the line under the diagram: models simplify — a wiped disk can break the model itself.

۶6 چطور مطمئن شویم؟ (روش‌های اعتبارسنجی)How can we be sure? (validation methods)

اعتماد به سیستم از ترکیبِ استدلال دربارهٔ پروتکل و آزمایشِ خرابی‌ها به دست می‌آید. تست معمولیِ مسیر موفق، همهٔ حالت‌های قطع ارتباط و توقف را پوشش نمی‌دهد.

مثال فنی: آزمودن ترتیب نادر خرابی

وضعیت اولیه: سناریو شامل pause رهبر، انتخاب رهبر تازه، commit و برگشت رهبر قدیمی است.

اجرای مرحله‌به‌مرحله: model checker حالت‌های محدود مدل را می‌کاود؛ شبیه‌سازی قطعی همین زمان‌بندی را با seed مشخص بازتولید می‌کند؛ fault injection پیاده‌سازی واقعی را تحت اختلال می‌برد.

نتیجه و دلیل: هر روش بخشی از شواهد است؛ مدلِ ناقص یا حالت آزموده‌نشده همچنان می‌تواند خطا را پنهان کند.

With this many possible states, how do we know our algorithm is right? The book's answer: combine theoretical analysis with empirical testing. Neither alone suffices.

روشچه می‌کندنمونه‌ها و محدودیت
اثباتِ صوری الگوریتم را ریاضی می‌نویسی و خواص را اثبات می‌کنی پیاده‌سازیِ واقعی را پوشش نمی‌دهد؛ ولی اشکالاتِ پنهانِ فرض‌های زمانی را بیرون می‌کشد
Model checking (TLA+، FizzBee) همه‌ی حالت‌های ممکنِ مدل را سیستماتیک امتحان می‌کند فضای حالت نامتناهی است، پس تقریب یا سقفِ تعداد پیام لازم است؛ CockroachDB و Kafka استفاده می‌کنند
Fault injection / Chaos خرابیِ واقعی تزریق می‌کند: kill، umount، قطعِ فایروالی Chaos Monkey و Jepsen باگ‌های بحرانیِ زیادی پیدا کرده‌اند؛ نوشتنشان زحمت دارد
Deterministic simulation testing شبکه، I/O و ساعت را mock می‌کند و اجرای قطعی و تکرارپذیر می‌سازد FoundationDB، TigerBeetle، Antithesis؛ نیاز به کنترلِ همه‌ی منابعِ non-determinism دارد
Property-based / fuzz testing ورودی‌های تصادفی برای یافتنِ گوشه‌های فراموش‌شده مکملِ روش‌های بالا؛ AWS و TigerBeetle ترکیبشان کرده‌اند
MethodWhat it doesExamples and limits
Formal proofWrites the algorithm as math and proves propertiesMisses the real implementation; but exposes hidden timing assumptions
Model checking (TLA+, FizzBee)Systematically tries every possible model stateState space is infinite, so approximate or cap message counts; used by CockroachDB and Kafka
Fault injection / ChaosInjects real failures: kill, umount, firewall cutsChaos Monkey and Jepsen found many critical bugs; writing them is work
Deterministic simulation testingMocks network, I/O and clocks for deterministic, repeatable runsFoundationDB, TigerBeetle, Antithesis; needs control of every nondeterminism source
Property-based / fuzz testingRandom inputs to find forgotten cornersComplements the above; AWS and TigerBeetle combine them
نمودار مفهومی · اثبات درستی — تئوری به‌علاوهٔ تجربهConcept diagram · proving correctness — theory plus experiment باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس از نمودارTeacher narration

مثال فنی برای خواندنِ نمودار: آزمودن ترتیب نادر خرابی

وضعیت اولیه: سناریو شامل pause رهبر، انتخاب رهبر تازه، commit و برگشت رهبر قدیمی است.

اجرای مرحله‌به‌مرحله: model checker حالت‌های محدود مدل را می‌کاود؛ شبیه‌سازی قطعی همین زمان‌بندی را با seed مشخص بازتولید می‌کند؛ fault injection پیاده‌سازی واقعی را تحت اختلال می‌برد.

نتیجه و دلیل: هر روش بخشی از شواهد است؛ مدلِ ناقص یا حالت آزموده‌نشده همچنان می‌تواند خطا را پنهان کند.

پنج کارت را از چپ بخوان: اثبات صوری (ریاضی روی کاغذ، ولی کد واقعی را نمی‌بیند)، model checking (گشتن همهٔ حالت‌های مدل با TLA+، ولی فضای حالت نامتناهی است)، fault injection (کشتن نود واقعی با Chaos/Jepsen، ولی کنترل ریزبینانه ندارد)، شبیه‌سازی قطعی DST (اجرای کد واقعی با شبکه و ساعت mock و تکرار بیت‌به‌بیت، ولی باید همهٔ non-determinism را کنترل کنی)، و fuzz/property (یافتن گوشه‌های فراموش‌شده، ولی بدون تضمین).

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

Read the five cards left to right: formal proof (math on paper, blind to real code), model checking (walking every model state with TLA+, but the state space is infinite), fault injection (killing real nodes with Chaos/Jepsen, but no fine control), deterministic DST simulation (real code on mocked network and clocks, bit-for-bit repeatable — but every nondeterminism source must be controlled), and fuzz/property testing (finding forgotten corners, but no guarantees).

The bottom bar is the common thread: determinism — replayable logs, deterministic workflows, simulated time.

قدرتِ قطعی‌بودن (The Power of Determinism)The power of determinism

تمامِ دردسرهای این فصل از non-determinism می‌آید: تأخیر، pause، جهشِ ساعت، crash. اگر بتوانی سیستم را قطعی کنی، همه‌چیز ساده می‌شود. همین ایده قبلاً هم آمده: بازپخشِ قطعیِ event log در event sourcing، تعریفِ قطعیِ workflow در durable execution، و اجرای قطعیِ تراکنش‌ها در state machine replication. ولی مراقب باش: حتی ترتیبِ iterateشدنِ یک hash table یا محدودیتِ حافظه هم می‌تواند non-determinism را برگرداند.

All this chapter's pain flows from non-determinism: delay, pauses, clock jumps, crashes. Make the system deterministic and everything simplifies. The idea already appeared: deterministic event-log replay in event sourcing, deterministic workflow definitions in durable execution, and deterministic transaction execution in state machine replication. But beware: even a hash table's iteration order or memory limits can reintroduce non-determinism.

نقشهٔ فصل ۹ — دردسر توزیع‌شده در یک نگاهChapter 9 map — distributed trouble at a glance

نقشهٔ فصل ۹ · گرهٔ وسط دردسر است، یال‌ها شک و سپرهاChapter 9 map · trouble at the center, doubt and shields on the edges باز کردن در تب جدید ↗Open standalone ↗
روایت مدرس از نقشهٔ فصلTeacher narration of the chapter map

مثال فنی برای خواندنِ نمودار: timeout فقط یک مشاهده است

وضعیت اولیه: درخواست r7 به سرور می‌رود و کلاینت پس از 2 s پاسخی ندارد.

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

نتیجه و دلیل: از سکوت، شکست قطعیِ اثر را نتیجه نمی‌گیریم؛ شناسهٔ عملیات و مسیر استعلام برای حل ابهام لازم‌اند.

گرهٔ وسط (Distributed Trouble) را مرکز بگیر: بالا منابع سردرگمی‌اند (partial failure، timeout، ساعت‌ها)، دو طرف سپرها (fencing در برابر زامبی، quorum در برابر قضاوت فردی)، و پایین خطرها و ابزارها (LWW، دو خاصیت safety/liveness، و اعتبارسنجی).

یک جملهٔ کل فصل: شبکه و ساعت و pauseی متوقف‌شده «شک» می‌سازند؛ quorum و fencing و اثبات «اطمینان» را برمی‌گردانند.

Take the center node (Distributed Trouble): confusion sources on top (partial failure, timeouts, clocks), shields on the sides (fencing against zombies, quorum against self-judgment), and dangers plus tools below (LWW, the two safety/liveness properties, and validation).

The whole chapter in one sentence: network, clocks and stalled pauses manufacture “doubt”; quorum, fencing and proofs restore “certainty”.

۷7 بستهٔ مصاحبهInterview pack

۷.۱. همه‌چیز در یک نگاه7.1 Everything at a glance

مفهومتعریفِ یک‌خطینکته‌ی مصاحبه
partial failureبخشی از سیستم خراب، بقیه سالم — به‌صورتِ غیرقطعیویژگیِ تعریف‌کننده‌ی سیستم توزیع‌شده
asynchronous networkتأخیر و تحویلِ بسته هیچ سقفی نداردtimeout یعنی «چیزی نشنیدم»، نه «خراب است»
timeoutحدسِ آگاهانه برای پایانِ انتظارکوتاه = مثبتِ کاذب و cascading failure؛ بلند = failover کُند
idempotencyتکرارِ عملیات، اثرِ یکسانکلیدِ idempotency، ابزارِ اصلیِ retry امن
monotonic clockفقط جلو می‌رود؛ برای سنجشِ مدتبینِ ماشین‌ها قابلِ مقایسه نیست
time-of-day clockساعتِ دیواری؛ ممکن است به عقب بپردبرای ترتیب‌دهیِ بینِ نودها خطرناک است
logical clockشمارنده‌ای که فقط ترتیب را ثبت می‌کندLamport و version vector؛ بدونِ مفهومِ ثانیه
clock skewاختلافِ ساعتِ دو ماشینبا LWW می‌تواند نوشته‌ی جدیدتر را بی‌صدا حذف کند
uncertainty intervalساعت به‌جای عدد، بازه می‌دهدTrueTime/ClockBound؛ Spanner با commit wait
process pauseتوقفِ دلخواه و نامعلومِ یک نخGC، مهاجرتِ VM، swap، SIGSTOP
leaseقفلِ دارایِ انقضا که باید تمدید شوددارنده‌اش ممکن است نداند که از دستش داده
zombieدارنده‌ی قدیمی که هنوز می‌نویسد«خاموش‌کردنش» کافی نیست؛ باید بی‌اثرش کنی
fencing tokenعددِ افزایشیِ هر واگذاریِ قفلstorage نوشته‌های کهنه را رد می‌کند (33 < 34)
quorumتصمیم با رأیِ حداقلیِ چند نوددو اکثریتِ متناقض هم‌زمان ممکن نیست
Byzantine faultنود می‌تواند دروغ بگویددر دیتاسنترِ معمولی لازم نیست؛ گران است
system modelمجموعه‌ی فرض‌های زمانی و خرابیواقع‌بینانه: partially synchronous + crash-recovery
safetyهیچ اتفاقِ بدی نیفتد (نقض = یک لحظه‌ی مشخص)باید در همه‌ی شرایط برقرار بماند
livenessبالاخره اتفاقِ خوبی بیفتد«eventually» نشانه‌اش است؛ eventual consistency هم liveness است
fail-slow / limpingنود جواب می‌دهد ولی کار نمی‌کنداز خرابیِ تمیز سخت‌تر است
DSTآزمونِ قطعی و تکرارپذیرِ کدِ واقعیFoundationDB و TigerBeetle؛ کنترلِ کاملِ non-determinism
ConceptOne-line definitionInterview note
partial failurePart of the system broken, the rest fine — non-deterministicallyThe defining trait of distributed systems
asynchronous networkPacket delay and delivery have no ceilingTimeout means “heard nothing”, not “broken”
timeoutAn informed guess at ending the waitShort = false positives and cascading failure; long = slow failover
idempotencyRepeating an operation, same effectThe idempotency key is the safe-retry tool
monotonic clockOnly moves forward; for measuring durationsNot comparable across machines
time-of-day clockWall clock; may jump backDangerous for cross-node ordering
logical clockA counter recording only orderLamport and version vectors; no seconds
clock skewTwo machines' clock differenceWith LWW can silently drop the newer write
uncertainty intervalThe clock returns an interval, not a numberTrueTime/ClockBound; Spanner with commit wait
process pauseArbitrary, unknown thread stallGC, VM migration, swap, SIGSTOP
leaseA lock with an expiry that must be renewedIts holder may not know it lost it
zombieAn old holder that still writes“Shooting it” isn't enough; neutralize it
fencing tokenAn increasing number per lock grantStorage rejects stale writes (33 < 34)
quorumDecisions by a minimum vote of nodesTwo contradictory majorities can't coexist
Byzantine faultA node may lieUnneeded in ordinary datacenters; expensive
system modelThe set of timing and failure assumptionsRealistic: partially synchronous + crash-recovery
safetyNothing bad ever happens (violation = one moment)Must hold in all conditions
livenessSomething good eventually happens“eventually” marks it; eventual consistency is liveness
fail-slow / limpingA node answers but does no workHarder than a clean crash
DSTDeterministic, repeatable testing of real codeFoundationDB and TigerBeetle; full nondeterminism control

۷.۲. سوالات پرتکرار مصاحبه7.2 Frequent interview questions

۱. اگر بعد از ارسالِ یک درخواست timeout بگیریم، دقیقاً چه چیزهایی ممکن است رخ داده باشد؟1. After sending a request and getting a timeout, what exactly may have happened?

ممکن است درخواست نرسیده باشد، هنوز اجرا شود، شکست خورده باشد یا موفق شده و پاسخ گم شده باشد. Timeout فقط پایانِ انتظارِ کلاینت را نشان می‌دهد.

مثال فنی: سه سرنوشت همان درخواست

وضعیت اولیه: CreateOrder(r7) ارسال شده و پاسخ timeout شده است.

اجرای مرحله‌به‌مرحله: حالت اول: نرسیده و هیچ سفارش نیست؛ دوم: در حال اجراست؛ سوم: سفارش ۴۲ ثبت شده اما پاسخ گم شده است.

نتیجه و دلیل: retry با همان هویت باید این حالت‌ها را امن پوشش دهد؛ ساخت هویت تازه احتمالِ دو اثر را بالا می‌برد.

Six states: request lost; queued; node crashed; node paused; node applied it but the reply was lost; reply on its way. Key point: all look identical to the client, so a timeout proves no failure.

۲. چرا «TCP که قابل‌اعتماد است» مشکل را حل نمی‌کند؟2. Why doesn’t “TCP is reliable” solve the problem?

TCP رسیدن و ترتیبِ بایت‌ها را در اتصال مدیریت می‌کند؛ تأییدِ دریافتِ شبکه با تمام شدنِ عملیاتِ برنامه و پایدار شدنِ داده فرق دارد.

مثال فنی: TCP ack، commit نیست

وضعیت اولیه: بایت‌های درخواست به TCP stack سرور رسیده و دریافت شبکه تأیید شده است.

اجرای مرحله‌به‌مرحله: برنامه ممکن است پیش از خواندن بایت‌ها crash کند یا پس از commit پاسخ ندهد.

نتیجه و دلیل: تضمین انتقالِ بایت، اجرای دستور یا پایداری تغییرِ برنامه را تأیید نمی‌کند؛ تأیید دامنه باید در لایهٔ برنامه تعریف شود.

Because dedup and retransmit live inside one connection only; after a reconnect you can't know how far the destination processed. And an ACK only means the destination OS kernel got the packet — not that the application did it. For certainty you need the application's explicit reply.

۳. timeout مناسب چقدر باید باشد؟3. How long should a good timeout be?

زمانِ مناسب به توزیعِ تأخیر، هزینهٔ انتظار و هزینهٔ تشخیصِ اشتباه بستگی دارد. timeout کوتاه بازتلاشِ بی‌مورد و timeout بلند بازیابیِ دیرتر می‌سازد؛ یک عددِ عمومی نداریم.

مثال فنی: timeout کوتاه، خطای تشخیص بیشتر

وضعیت اولیه: معمولاً پاسخ در 50 ms می‌رسد، ولی گاهی pause یا صف آن را به 800 ms می‌رساند.

اجرای مرحله‌به‌مرحله: timeout برابر 100 ms بازیابی را زودتر شروع می‌کند، اما همین پاسخ‌های دیررس را هم خرابی می‌شمارد.

نتیجه و دلیل: آستانه از توزیع تأخیر و هزینهٔ تشخیص غلط انتخاب می‌شود؛ timeout پایان عمر نود را اثبات نمی‌کند.

There is no “correct” value. In the fantasy model of delay cap d and processing cap r, 2d+r is sensible — but real networks have unbounded delay. So measure the RTT distribution and balance detection speed against premature-declaration risk (like Phi Accrual or TCP's RTO).

۴. چرا اعلامِ زودهنگامِ مرگِ یک نود خطرناک است؟4. Why is prematurely declaring a node dead dangerous?

گرهِ کند ممکن است هنوز فعالیت کند. اعلامِ مرگ و واگذاریِ کار، بدونِ جلوگیری از اثرِ صاحبِ قدیمی، می‌تواند دو نویسنده یا پردازشِ تکراری بسازد.

مثال فنی: زامبی با مجوز قدیمی

وضعیت اولیه: A توکن ۴۱ داشته و متوقف شده؛ B با توکن ۴۲ فایل را به‌روز کرده است.

اجرای مرحله‌به‌مرحله: A برمی‌گردد و با ۴۱ می‌نویسد؛ مقصدی که آخرین توکن ۴۲ را نگه می‌دارد آن را رد می‌کند.

نتیجه و دلیل: fencing باید در همان منبعِ اثر اعمال شود؛ صرفِ انتخاب رهبر تازه، درخواستِ درراهِ رهبر قدیمی را خنثی نمی‌کند.

Because the node may be alive and mid-task; the replacement repeats the work (duplicate effect). Plus load suddenly shifts to the rest, and on a busy system a chain of death declarations (cascading failure) forms.

۵. فرقِ monotonic clock و time-of-day clock چیست و هر کدام برای چه خوبند؟5. What’s the difference between a monotonic and a time-of-day clock, and what is each good for?

ساعتِ دیواری زمانِ تقویمی می‌دهد و ممکن است اصلاح شود؛ ساعتِ یکنواخت برای سنجشِ فاصلهٔ زمانیِ همان ماشین مناسب است و مقایسهٔ آن بینِ ماشین‌ها معنای عمومی ندارد.

مثال فنی: ساعت عقب می‌رود

وضعیت اولیه: فرایند شروع را با ساعت دیواری 12:00:10 ثبت می‌کند؛ NTP زمان را به 12:00:05 برمی‌گرداند.

اجرای مرحله‌به‌مرحله: تفاضل دو ساعت دیواری ممکن است مدت منفی بدهد؛ سنجش elapsed time با monotonic clock مناسب‌تر است.

نتیجه و دلیل: ساعتِ مناسبِ نمایش تاریخ با ساعتِ مناسبِ سنجش مدت یکی نیست؛ monotonic محلی هم ترتیب جهانی نمی‌سازد.

Monotonic never jumps back and suits measuring durations (timeout, latency) on one machine; its absolute value is meaningless. Time-of-day (wall clock) suits dates and deadlines but can jump via NTP or leap seconds — dangerous for cross-node ordering.

۶. چرا با timestamp نمی‌توان ترتیبِ نوشته‌ها را درست کرد؟6. Why can’t timestamps order writes correctly?

ساعتِ گره‌ها اختلاف و جهش دارد؛ زمانِ بزرگ‌تر الزاماً تغییرِ واقعاً بعدی نیست. برای وابستگی یا ترتیبِ قوی، قراردادِ منطقی یا پروتکلِ هماهنگی لازم است.

مثال فنی: LWW با ساعت جلو افتاده

وضعیت اولیه: A مقدار v1 را با timestamp=105 ثبت می‌کند؛ B در زمان واقعیِ بعدی v2 را با timestamp=102 ثبت می‌کند.

اجرای مرحله‌به‌مرحله: حل تعارضِ بزرگ‌ترین timestamp، v1 را نگه می‌دارد.

نتیجه و دلیل: رویداد جدیدتر می‌تواند حذف شود؛ LWW فقط ترتیب timestampها را اجرا می‌کند، نه معنای درستِ آخرین ویرایش را.

Because skew exists: a causally later write can carry a smaller timestamp and be dropped by LWW (Diagram 5). LWW also can't tell “sequential” from “truly concurrent” writes, and equal timestamps happen too. Fixes: logical clocks / version vectors, increasing monotonic stamps, or uncertainty intervals.

۷. منظور از «خواندنِ ساعت یک بازه است» چیست؟7. What does “reading the clock is an interval” mean?

به‌جای ادعای یک زمانِ دقیق، زمان با حدِ زودترین و دیرترین مقدارِ محتمل بیان می‌شود. هم‌پوشانیِ دو بازه یعنی ترتیب را از همین اطلاعات قطعی نمی‌دانیم.

مثال فنی: بازهٔ زمان به‌جای نقطهٔ قطعی

وضعیت اولیه: API زمان را به‌شکل بازهٔ [100,106] برمی‌گرداند و timestamp کامیت 106 انتخاب شده است.

اجرای مرحله‌به‌مرحله: در الگوی commit-wait، تأیید تا وقتی عقب می‌افتد که کران پایین زمان از ۱۰۶ عبور کند.

نتیجه و دلیل: این ایدهٔ Spanner عدم قطعیتِ محدود را در قرارداد وارد می‌کند؛ ساعت دقیق بدون انتظار و replication به‌تنهایی کافی نیست.

Quartz and NTP clocks err; so a reading means “95% sure now is between t1 and t2”. Most APIs hide this interval; TrueTime and ClockBound report it explicitly — which is what lets you argue about certain ordering.

۸. Spanner چطور از ساعت برای اسنپ‌شاتِ سراسری استفاده می‌کند؟8. How does Spanner use clocks for global snapshots?

Spanner از اطلاعاتِ زمانِ دارای عدم‌قطعیت و پروتکلِ تراکنش استفاده می‌کند. انتظارِ commit کمک می‌کند زمانِ تصمیم از محدودهٔ عدم‌قطعیت عبور کند؛ ساعتِ عادی به‌تنهایی این تضمین را نمی‌دهد.

مثال فنی: بازهٔ زمان به‌جای نقطهٔ قطعی

وضعیت اولیه: API زمان را به‌شکل بازهٔ [100,106] برمی‌گرداند و timestamp کامیت 106 انتخاب شده است.

اجرای مرحله‌به‌مرحله: در الگوی commit-wait، تأیید تا وقتی عقب می‌افتد که کران پایین زمان از ۱۰۶ عبور کند.

نتیجه و دلیل: این ایدهٔ Spanner عدم قطعیتِ محدود را در قرارداد وارد می‌کند؛ ساعت دقیق بدون انتظار و replication به‌تنهایی کافی نیست.

منبعِ توضیحِ فنی: TrueTime و سازگاریِ بیرونیِ Spanner

Each clock read gives an interval. Non-overlapping intervals of two transactions mean certain order. Spanner waits out the interval width before commit (commit wait) so intervals stop overlapping; GPS and atomic clocks only shrink the width.

۹. چه اتفاقی می‌افتد که یک نودِ زنده «مرده» اعلام شود و بعد زنده شود؟9. How does a live node get declared “dead” and come back?

فرایند می‌تواند به‌خاطر زمان‌بندی، توقفِ ماشین، جمع‌آوریِ حافظه یا انتظارِ منابع مکث کند. دیگران او را مشکوک می‌دانند، ولی پس از ادامه، کدِ قدیمی هنوز ممکن است اجرا شود.

مثال فنی: Pause در جای حساس

وضعیت اولیه: worker مجوز ۵ثانیه‌ای گرفته و پیش از نوشتن ۱۰ ثانیه متوقف می‌شود.

اجرای مرحله‌به‌مرحله: worker دیگری پس از انقضای مجوز کار را می‌گیرد؛ worker اول از pause برمی‌گردد و هنوز خط بعدی کد را اجرا می‌کند.

نتیجه و دلیل: چک کردن مجوز پیش از pause تضمین اعتبار هنگام اثر نیست؛ GC، تعلیق VM یا زمان‌بندی می‌تواند بین این دو رخ دهد.

A long pause (GC, VM migration, swap). During the pause everyone times out and appoints a replacement; when the thread wakes it never knew it slept, and without an explicit check it writes as a zombie.

۱۰. lease چیست و چرا کدِ نمونه‌ی فصل باگ دارد؟10. What is a lease, and why is the chapter’s sample code buggy?

Lease مجوزِ زمان‌دار است. بررسیِ اعتبار پیش از کار کافی نیست، چون فرایند می‌تواند میانِ بررسی و نوشتن مکث کند؛ مقصد هم باید مجوزِ منقضی را بی‌اثر کند.

مثال فنی: Pause در جای حساس

وضعیت اولیه: worker مجوز ۵ثانیه‌ای گرفته و پیش از نوشتن ۱۰ ثانیه متوقف می‌شود.

اجرای مرحله‌به‌مرحله: worker دیگری پس از انقضای مجوز کار را می‌گیرد؛ worker اول از pause برمی‌گردد و هنوز خط بعدی کد را اجرا می‌کند.

نتیجه و دلیل: چک کردن مجوز پیش از pause تضمین اعتبار هنگام اثر نیست؛ GC، تعلیق VM یا زمان‌بندی می‌تواند بین این دو رخ دهد.

A lease is a lock with an expiry that must be renewed. The sample code has two flaws: it leans on a synchronized clock, and assumes no pause happens between checking the clock and processing. A 15-second pause invalidates the whole logic.

۱۱. دقیقاً چه چیزی یک زامبی را بی‌خطر می‌کند؟11. What exactly makes a zombie harmless?

مقصدِ تغییر باید توکنِ قدیمی‌تر از دورهٔ پذیرفته‌شده را رد کند. این حفاظت، اثرِ فرایندِ قدیمی را محدود می‌کند، حتی اگر خودش هنوز نفهمیده باشد مالکیتش تمام شده است.

مثال فنی: بررسی توکن اتمیک است

وضعیت اولیه: storage بیشترین توکن پذیرفته‌شده را 42 نگه می‌دارد.

اجرای مرحله‌به‌مرحله: نوشتن با ۴۱ رد می‌شود؛ پذیرفتن ۴۳ باید همراه به‌روزرسانیِ بیشترین توکن و اعمال اثر، اتمیک باشد.

نتیجه و دلیل: اگر بررسی و نوشتن جدا شوند، رقابت دوباره ایجاد می‌شود؛ توکن افزایشی بدون اجرای صحیح در مقصد کافی نیست.

fencing tokens: each lock grant carries an increasing number, and storage accepts only writes with a greater token. So the zombie's write with the smaller token is rejected. The newcomer must also send its first write with the new token immediately, fencing zombies at once.

۱۲. آیا «خاموش‌کردنِ زامبی» (STONITH) کافی است؟12. Is “shooting the zombie” (STONITH) enough?

خاموش کردنِ مالکِ قدیمی می‌تواند بخشی از حفاظت باشد، اما باید اجرای واقعیِ آن و ترتیبِ اقدامات تضمین شود. پیامِ درراه یا مقصدهای متعدد هم باید در مدل دیده شوند.

مثال فنی: خاموش کردن، پیامِ درراه را حذف نمی‌کند

وضعیت اولیه: درخواستِ نوشتن A پیش از خاموش شدنش وارد شبکه شده است.

اجرای مرحله‌به‌مرحله: B رهبر می‌شود، ولی درخواست قدیمی A بعداً به storage می‌رسد؛ مقصد باید epoch یا fencing token آن را رد کند.

نتیجه و دلیل: STONITH می‌تواند بخشی از مهار باشد؛ برای پیام‌های درراه هم باید شرط اعتبارِ اثر وجود داشته باشد.

No. It doesn't guard against late-arriving network requests, everyone may kill everyone at once, and by discovery time the data is corrupt. fencing is enforced at the storage layer and is permanent (unlike OCC, which can retry).

۱۳. quorum چه تضمینی می‌دهد؟13. What guarantee does quorum give?

اکثریتِ دو مجموعهٔ رأیِ ثابت هم‌پوشانی دارد. پروتکل با قواعدِ دوره و رأی از تصمیمِ متعارض جلوگیری می‌کند؛ اکثریت به‌تنهایی هر نوعِ خواندنِ تازه را تضمین نمی‌کند.

مثال فنی: اکثریت هنگام جدایی شبکه

وضعیت اولیه: پنج رأی‌دهنده به گروه‌های {A,B,C} و {D,E} تقسیم شده‌اند.

اجرای مرحله‌به‌مرحله: فقط گروه سه‌عضوی می‌تواند اکثریت بگیرد؛ گروه دو‌عضوی برای عملیاتِ نیازمند quorum منتظر می‌ماند.

نتیجه و دلیل: هر دو گروه اکثریت جدا نمی‌سازند؛ پروتکل epoch و رأی‌دهی هنوز باید مانع پذیرش هم‌زمانِ رهبران نامعتبر شود.

Decisions need a minimum vote so no single node is load-bearing. With an absolute majority, two contradictory decisions can't coexist, and 2f+1 nodes tolerate f failures. A quorum's verdict rules even for “who died”: the node must step aside.

۱۴. خرابیِ بیزانسی چیست و کی باید نگرانش باشیم؟14. What is a Byzantine fault, and when should we worry?

گرهِ بیزانسی می‌تواند پاسخِ غلط یا متناقض بدهد. وقتی شرکت‌کننده‌ها یا محیط ممکن است بدخواه یا فاسد باشند، فرضِ «گره‌ها فقط خاموش یا کند می‌شوند» کافی نیست.

مثال فنی: نودِ دروغ‌گو در برابر نودِ خاموش

وضعیت اولیه: نود A به B می‌گوید نتیجه x و به C می‌گوید نتیجه y.

اجرای مرحله‌به‌مرحله: این رفتار با crash یا تأخیر متفاوت است؛ الگوریتمِ صرفاً crash-tolerant لزوماً چنین تناقضی را تحمل نمی‌کند.

نتیجه و دلیل: مدل اعتماد تعیین‌کننده است؛ checksum خطای تصادفی و امضای پیام، به‌تنهایی همهٔ رفتار بیزانسی را حل نمی‌کنند.

When a node may lie, forge messages or cast conflicting votes. In your own datacenter with trusted nodes it's usually unnecessary (expensive, needs a two-thirds majority). It matters in multi-party/blockchain systems and flight control. And if the bug is in the software, you need multiple independent implementations — not just a Byzantine algorithm.

۱۵. تفاوتِ safety و liveness چیست و چرا مهم است؟15. What’s the difference between safety and liveness, and why does it matter?

ایمنی می‌گوید اتفاقِ ممنوع رخ ندهد؛ پیشرفت می‌گوید تحت شرایطِ لازم، کار سرانجام نتیجه بگیرد. توقفِ همهٔ درخواست‌ها شاید ایمن باشد، اما خدمتِ قابل‌استفاده نمی‌سازد.

مثال فنی: درستی حتی هنگام توقف پیشرفت

وضعیت اولیه: قانون سیستم «یک صندلی به دو نفر فروخته نشود» است و شبکه دوپاره شده است.

اجرای مرحله‌به‌مرحله: توقف فروش در اقلیت می‌تواند safety را حفظ کند، ولی تکمیل درخواست‌های آن بخش را موقتاً عقب بیندازد.

نتیجه و دلیل: safety می‌گوید چه نتیجهٔ بدی هرگز رخ ندهد؛ liveness می‌گوید تحت فرض‌های پیشرفت، کار بالاخره انجام شود.

Safety means nothing bad ever happens; its violation has a specific moment and is irreparable (token replay, lost writes). Liveness means something good eventually happens (request answered, eventual consistency). Safety must hold in all conditions; liveness may assume recovery. The split makes reasoning in hard models possible.

۱۶. مدلِ سیستم چیست و کدام برای دنیای واقعی مناسب است؟16. What is a system model, and which fits the real world?

مدلِ سیستم فرض‌های زمان، شبکه و نوعِ خرابی را مشخص می‌کند. در عمل معمولاً باید مکث و تأخیرِ متغیر را بپذیریم و شرایطِ لازمِ پیشرفت را صریح بیان کنیم.

مثال فنی: مدل زمانیِ شبکه

وضعیت اولیه: پیام heartbeat از A هنوز نرسیده و B نمی‌داند A crash کرده یا فقط کند است.

اجرای مرحله‌به‌مرحله: مدل synchronous کران‌های زمانی را فرض می‌کند؛ asynchronous چنین کرانِ شناخته‌شده‌ای ندارد؛ partial synchrony اجازه می‌دهد پس از دورهٔ نامعلوم، شرایطِ کافیِ پیشرفت برقرار شود.

نتیجه و دلیل: ادعای safety و liveness به همین فرض‌ها وابسته است؛ timeout در مدلِ مناسب ابزار پیشرفت است، نه گواه قطعی مرگ نود.

The formal set of assumptions an algorithm is proven in: the time dimension (synchronous/partially synchronous/asynchronous) and the failure dimension (crash-stop/crash-recovery/limping/Byzantine). The most realistic mix: partially synchronous + crash-recovery. But remember models simplify: a wiped disk can break the “stable storage” assumption itself.

۱۷. fail-slow یا limping node چرا سخت‌تر از crash است؟17. Why is a fail-slow (limping) node harder than a crash?

گرهِ کند ممکن است اتصال داشته باشد و بخشی از کار را انجام دهد؛ بنابراین تشخیص و انتخابِ جایگزین مبهم‌تر از توقفِ کامل است و می‌تواند صف و timeout را زیاد کند.

مثال فنی: نودِ نیمه‌جان

وضعیت اولیه: A هنوز heartbeat می‌فرستد، اما دیسکش هر write را ۲۰ ثانیه معطل می‌کند.

اجرای مرحله‌به‌مرحله: مسیریابِ فقط مبتنی بر زنده بودن، درخواست‌ها را به A می‌فرستد و صف بالا می‌رود؛ سنجهٔ latency و backlog خرابی رفتاری را آشکار می‌کند.

نتیجه و دلیل: fail-slow با crash روشن فرق دارد؛ نود می‌تواند از نظر فرایند زنده و از نظر هدف سرویس نامناسب باشد.

Because the node answers health checks (so it looks “alive”) while doing no real work: a NIC degraded to 1Kb/s, a process mostly GC-ing, a worn SSD. Detecting and removing it isn't as simple as a crash.

۱۸. model checking چه کمکی می‌کند و محدودیتش چیست؟18. What does model checking help with, and what are its limits?

بررسیِ مدل، ترتیب‌های ممکنِ یک مدلِ محدود را جست‌وجو می‌کند تا نقضِ قواعد پیدا شود. نتیجه به صحت و حدودِ مدل وابسته است و همهٔ کد و محیطِ واقعی را خودکار پوشش نمی‌دهد.

مثال فنی: آزمودن ترتیب نادر خرابی

وضعیت اولیه: سناریو شامل pause رهبر، انتخاب رهبر تازه، commit و برگشت رهبر قدیمی است.

اجرای مرحله‌به‌مرحله: model checker حالت‌های محدود مدل را می‌کاود؛ شبیه‌سازی قطعی همین زمان‌بندی را با seed مشخص بازتولید می‌کند؛ fault injection پیاده‌سازی واقعی را تحت اختلال می‌برد.

نتیجه و دلیل: هر روش بخشی از شواهد است؛ مدلِ ناقص یا حالت آزموده‌نشده همچنان می‌تواند خطا را پنهان کند.

You write the algorithm's spec in a language like TLA+ and the tool systematically walks the state space (e.g. the viewstamped replication bug). Limit: the state space is infinite, so approximate or cap it; and the model may drift from the real implementation.

۱۹. deterministic simulation testing چه فرقی با fault injection دارد؟19. How does deterministic simulation testing differ from fault injection?

شبیه‌سازیِ قطعی زمان‌بندی و خرابی را کنترل می‌کند تا اجرای مشکل‌دار قابل‌تکرار باشد. تزریقِ خرابی در محیطِ واقعی یا نزدیک به آن، رفتارِ اجزای واقعی را می‌سنجد؛ این دو مکمل‌اند.

مثال فنی: آزمودن ترتیب نادر خرابی

وضعیت اولیه: سناریو شامل pause رهبر، انتخاب رهبر تازه، commit و برگشت رهبر قدیمی است.

اجرای مرحله‌به‌مرحله: model checker حالت‌های محدود مدل را می‌کاود؛ شبیه‌سازی قطعی همین زمان‌بندی را با seed مشخص بازتولید می‌کند؛ fault injection پیاده‌سازی واقعی را تحت اختلال می‌برد.

نتیجه و دلیل: هر روش بخشی از شواهد است؛ مدلِ ناقص یا حالت آزموده‌نشده همچنان می‌تواند خطا را پنهان کند.

DST runs the real code with mocked network, I/O and clocks, controlling the exact event order; so every failure repeats precisely. Fault injection is more real but lacks fine control. DST demands controlling every nondeterminism source (even hash order).

۲۰. اگر مجبور باشی یک جمله از این فصل را در مصاحبه بگویی، کدام است؟20. If you had to say one sentence from this chapter in an interview, which?

وقتی خبری نمی‌رسد، نتیجه را نمی‌دانیم؛ طراحی باید این ابهام را تحمل کند. شناسهٔ درخواست، پیگیریِ نتیجه و حفاظتِ مقصد از مالکِ قدیمی ابزارهای مهم‌اند.

مثال فنی: timeout فقط یک مشاهده است

وضعیت اولیه: درخواست r7 به سرور می‌رود و کلاینت پس از 2 s پاسخی ندارد.

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

نتیجه و دلیل: از سکوت، شکست قطعیِ اثر را نتیجه نمی‌گیریم؛ شناسهٔ عملیات و مسیر استعلام برای حل ابهام لازم‌اند.

“In a distributed system, no reply doesn't mean failure; so I either make operations idempotent, or read the state back, or neutralize stale writes with fencing tokens — and I write my guarantees as safety and liveness.”

۷.۳. اشتباهات رایج در جواب‌های مصاحبه7.3 Common interview mistakes

  • ❌ «timeout یعنی سرور خراب است.» → ✅ timeout فقط یعنی «پاسخی در این بازه نیامد»؛ ممکن است نوشته انجام شده باشد.
  • ❌ «TCP که تحویلِ قابل‌اعتماد دارد، پس duplicate نداریم.» → ✅ بعد از reconnect و retransmitِ سطحِ برنامه، تکرار کاملاً ممکن است.
  • ❌ «ساعت‌ها را با NTP همگام می‌کنیم، پس timestamp معتبر است.» → ✅ skew همیشه هست و NTP هم خطا دارد؛ LWW می‌تواند داده را بی‌صدا حذف کند.
  • ❌ «از یک قفلِ توزیع‌شده استفاده می‌کنیم، پس فقط یک نویسنده داریم.» → ✅ بدونِ fencing، زامبی می‌تواند هم‌زمان بنویسد.
  • ❌ «nondeterminism فقط مربوط به race condition است.» → ✅ تأخیرِ شبکه، pause و جهشِ ساعت هم non-determinism‌اند.
  • ❌ «برای اطمینان، retry را نامحدود می‌کنیم.» → ✅ retry باید budget و backoff داشته باشد وگرنه خودش حمله می‌شود.
  • ❌ «سیستم تحملِ خرابی دارد، پس خرابیِ شبکه را هم تحمل می‌کند.» → ✅ باید صریح بگویی چه خرابی‌هایی و با چه سنجه‌ای (f گره؟ چند ناحیه؟).
  • ❌ «از ساعتِ monotonic برای ترتیب‌دادنِ رویدادها استفاده می‌کنیم.» → ✅ monotonic فقط روی یک ماشین معنی دارد.
  • ❌ “timeout means the server is broken.” → ✅ timeout only means “no reply arrived in this window”; the write may have applied.
  • ❌ “TCP delivers reliably, so no duplicates.” → ✅ after reconnects and app-level retransmits, repeats are fully possible.
  • ❌ “We sync clocks with NTP, so timestamps are valid.” → ✅ skew always exists and NTP errs too; LWW can silently drop data.
  • ❌ “We use a distributed lock, so there's exactly one writer.” → ✅ without fencing, a zombie can write concurrently.
  • ❌ “nondeterminism is only about race conditions.” → ✅ network delay, pauses and clock jumps are nondeterminism too.
  • ❌ “For safety we retry without limit.” → ✅ retries need a budget and backoff, or they become the attack.
  • ❌ “The system tolerates faults, so it tolerates network faults.” → ✅ say explicitly which faults and by what measure (f nodes? how many zones?).
  • ❌ “We use the monotonic clock to order events.” → ✅ monotonic is meaningful on one machine only.

۷.۴. چک‌لیستِ ۵ دقیقه‌ای قبل از مصاحبه7.4 Five-minute pre-interview checklist

  1. سه منبعِ سردرگمی را نام ببر: شبکه، ساعت، توقفِ فرآیند.
  2. شش حالتِ «پاسخ نیامد» را مرور کن و بگو چرا غیرقابل‌تفکیک‌اند.
  3. نمودارِ ۲ را در ذهنت بکش: Sent → Awaiting → (Confirmed | Timed Out) → Retry → (Deduplicated | Duplicate).
  4. قاعده‌ی fencing را بگو: 33 < 34 و storage تصمیم می‌گیرد.
  5. تفاوتِ دو ساعت و کاربردشان + اینکه چرا timestamp برای ترتیب خطرناک است.
  6. تعریفِ safety و liveness و اینکه eventual consistency کدام است (liveness).
  7. مدلِ سیستمِ واقع‌بینانه: partially synchronous + crash-recovery.
  8. یک جمله درباره‌ی idempotency و یک جمله درباره‌ی quorum.
  1. Name the three confusion sources: network, clocks, process pauses.
  2. Review the six “no reply” states and say why they're indistinguishable.
  3. Draw Diagram 2 from memory: Sent → Awaiting → (Confirmed | Timed Out) → Retry → (Deduplicated | Duplicate).
  4. State the fencing rule: 33 < 34, and storage decides.
  5. The two clocks' difference and uses + why timestamps are dangerous for ordering.
  6. Define safety and liveness, and which one eventual consistency is (liveness).
  7. The realistic system model: partially synchronous + crash-recovery.
  8. One sentence on idempotency and one on quorum.

۸8 تمرین‌های مصاحبه و آزمایشگاهInterview exercises and hands-on labs

این تمرین‌ها برای مسیر Backend / Distributed Systems در ۱۴ اکتبر ۲۰۲۶ بازبینی شدند. سطح مربوط به نسخهٔ مصاحبه‌ای است؛ زمان آزمایشگاه جداست.Reviewed on 14 October 2026 for Backend / Distributed Systems. Level refers to the interview brief; full-lab time is separate. روش ارزیابی و مسیر مطالعهMethod and study path

تمرین ۱ — شبیه‌سازِ ابهامِ timeoutExercise 1 — the timeout-ambiguity simulator

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

timeout ambiguity کاربردی است؛ نسخهٔ مصاحبه باید retry storm، deadline و overload را هم بپرسد.Add overload-aware retry policies to the timeout ambiguity example.

نسخهٔ بازبینی‌شده برای مصاحبهReviewed interview brief

شمارنده یا پرداختی داریم که درخواست، commit یا پاسخ آن ممکن است گم شود. history هر حالت را نشان بده. پروتکل با request ID، result lookup، payload binding و retention طراحی کن؛ deadline، backoff+jitter و retry budget را اضافه کن.Trace lost request, server failure and lost response. Design request ID, result lookup, payload binding and retention, then deadlines, backoff with jitter and a retry budget.

معیار پاسخ خوبWhat a good answer demonstrates

  • timeout را با عدم اجرا یکی نمی‌گیردTimeout remains indeterminate
  • ID درخواست قصد مشتری را حفظ می‌کندCaller intent in request ID
  • retry در همهٔ لایه‌ها بار را چندبرابر نمی‌کندBounded layered retries

پیگیری مصاحبه:Interview follow-up: سه لایه هر کدام سه بار retry کنند، حداکثر چند تلاش downstream داریم؟Three layers each make up to three attempts. What is the maximum number of downstream attempts?

آزمایشگاه تکمیلی · زمان تقریبی ۱–۲ ساعتExtended hands-on laboratory · estimated 1–2 hours

یک سرویسِ ساده بنویس که یک شمارنده را افزایش می‌دهد و یک کلاینت که درخواست می‌فرستد. به‌صورتِ تصادفی یکی از سه حالتِ نمودارِ ۱ را شبیه‌سازی کن (درخواست گم، سرور کرش، پاسخ گم). حالا retry را اضافه کن و نشان بده بدونِ idempotency، شمارنده بیش از یک‌بار زیاد می‌شود. سوال: چه تغییری در پروتکل، تکرار را بی‌اثر می‌کند؟

Write a simple service bumping a counter plus a client sending requests. Randomly simulate one of Diagram 1's three states (lost request, crashed server, lost reply). Now add retry and show the counter over-increments without idempotency. Question: which protocol change neutralizes repeats?

برای یادگیریِ عمیق است؛ انجام کاملش در زمان بحثِ مصاحبه انتظار نمی‌رود.The full laboratory is optional depth work and is not expected within the discussion time.

ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · AWS · safe retries with idempotent APIs · AWS · timeouts, backoff and jitter

تمرین ۲ — clock skew و عدم‌قطعیت اندازه‌گیریExercise 2 — clock skew and measurement uncertainty

L2 · SeniorL2 · Seniorوابسته به نقشRole-specific25 دقیقه بحث25 min discussion

اختلاف CLOCK_REALTIME روی دو host/VM مستقل با بازهٔ عدم‌قطعیت سنجیده می‌شود؛ CLOCK_MONOTONIC فقط مدت روی همان host را می‌سنجد.Measure CLOCK_REALTIME offset across independent hosts/VMs with uncertainty; CLOCK_MONOTONIC measures durations on one host only.

نسخهٔ بازبینی‌شده برای مصاحبهReviewed interview brief

wall clock و monotonic clock را در deadline، اندازه‌گیری duration و LWW مقایسه کن. skew را با دو host/VM مستقل یا ساعت mock تزریق کن. uncertainty ناشی از زمان ارسال نمونه را گزارش کن و نشان بده timestamp آینده چگونه write واقعی بعدی را می‌پوشاند.Compare wall and monotonic clocks for durations, deadlines and LWW. Use independent hosts/VMs or explicitly mocked skew. Report sampling uncertainty and show how a future timestamp hides a later real write.

معیار پاسخ خوبWhat a good answer demonstrates

  • monotonic clock بین ماشین‌ها مقایسه نمی‌شودNo cross-host monotonic comparison
  • mock skew با اندازه‌گیری skew واقعی برچسب متفاوت داردReal versus mock skew
  • rollback ساعت و uncertainty دیده شده‌اندClock steps and sampling uncertainty

پیگیری مصاحبه:Interview follow-up: NTP ساعت را step کند؛ timeout و TTL برنامه کدام clock را باید استفاده کنند؟NTP steps the wall clock. Which clock should application timeouts and TTL durations use?

آزمایشگاه تکمیلی · زمان تقریبی ۱–۲ ساعتExtended hands-on laboratory · estimated 1–2 hours

روی دو host/VM با ساعت مستقل، نمونه‌های CLOCK_REALTIME و مدت‌های CLOCK_MONOTONIC را ثبت کن. offset ساعت دوم نسبت به اول را با timestamp exchange و RTT اندازه بگیر؛ عدم‌قطعیت مسیر نامتقارن و زمان پردازش را ثبت کن و فرض‌های استفاده از RTT/2 را توضیح بده. مقدارهای monotonic دو host را از هم کم نکن چون مبدأ مشترک ندارند. دو کانتینر روی یک Linux host معمولاً CLOCK_REALTIME مشترک دارند. بدون زیرساخت، +۲۵۰ میلی‌ثانیه را در clock mock تزریق و همهٔ خروجی‌ها را شبیه‌سازی بنام. نشان بده LWW مبتنی بر ساعت دیواری چگونه نوشتن واقعاً دیرتر را کنار می‌گذارد و چرا ساعت واقعی سیستم کاربر لازم نیست تغییر کند.

On two independent-clock hosts/VMs, record CLOCK_REALTIME samples and local CLOCK_MONOTONIC durations. Estimate the second wall clock's offset using timestamp exchange and RTT; document processing and asymmetric-delay uncertainty and the assumptions behind RTT/2. Never subtract monotonic timestamps from different hosts: their origins differ. Two containers on one Linux host normally share CLOCK_REALTIME. Without infrastructure inject +250ms into a mock clock and label every output simulated. Show wall-clock LWW discarding a genuinely later write; no change to the user's real system clock is needed.

برای یادگیریِ عمیق است؛ انجام کاملش در زمان بحثِ مصاحبه انتظار نمی‌رود.The full laboratory is optional depth work and is not expected within the discussion time.

ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · Linux · time namespaces and realtime · Atlassian · engineering interview handbook

تمرین ۳ — fencing token عملیExercise 3 — fencing tokens in practice

L3 · Staff / PrincipalL3 · Staff / Principalاولویت اصلیCore priority40 دقیقه بحث40 min discussion

epoch هر lease افزایش می‌یابد؛ storage مقادیر کمتر از بیشینهٔ پذیرفته‌شده را اتمیک رد می‌کند. op_id مستقل، retry را تشخیص می‌دهد.Epoch increases per lease; storage atomically rejects values below its highest accepted epoch. An independent op_id identifies retries.

نسخهٔ بازبینی‌شده برای مصاحبهReviewed interview brief

کارگر A lease با token=7 دارد، pause می‌شود و B با token=8 می‌نویسد؛ A سپس بیدار می‌شود. storage باید stale epoch را اتمیک رد کند. چند write معتبر در یک epoch را از duplicate operation جدا کن و برای token allocation و دوام highest-seen شرط تعریف کن.Pause owner A at token 7, let B obtain token 8 and write, then resume A. Enforce atomic stale-epoch rejection in storage. Distinguish multiple legitimate same-epoch writes from duplicate operations and persist the highest token.

معیار پاسخ خوبWhat a good answer demonstrates

  • lease expiry به‌تنهایی جلوی stale write را نمی‌گیردLease expiry is insufficient
  • token کمتر رد می‌شود؛ token برابر نیازمند semantics جداستEpoch versus operation identity
  • بررسی token در خود storage انجام می‌شودStorage-enforced durable fencing

پیگیری مصاحبه:Interview follow-up: storage restart شود و highest token را فراموش کند، چه خطایی ممکن است؟Storage restarts and forgets the highest fencing token. What failure becomes possible?

آزمایشگاه تکمیلی · زمان تقریبی ۱–۳ ساعتExtended hands-on laboratory · estimated 1–3 hours

lock service با lease و epoch افزایشی و storage بادوام بساز. در یک تراکنش، epoch کمتر از highest_epoch را رد کن و برای epoch تازه high-water mark و اثر را با هم پایدار کن. در یک epoch، op_id تازه باید مجاز و retry همان op_id باید بی‌اثر باشد. A با epoch=7 متوقف می‌شود؛ B با epoch=8 در storage fence را ثبت می‌کند و دو write متفاوت انجام می‌دهد؛ سپس A برمی‌گردد و رد می‌شود. تأکید کن صرف صدور lease جدید تا قبل از رؤیت epoch در storage، fence را فعال نمی‌کند. restart storage، تکرار عملیات و جابه‌جایی پیام‌ها را با assertion مستقل بسنج.

Build a lease/epoch service and durable storage. In one transaction reject epoch below highest_epoch and persist a newer epoch with the effect. A new op_id in the same epoch is valid; retrying an old op_id is a no-op. Pause A at epoch 7; B installs epoch 8 at storage and performs two distinct writes; resume A and reject it. Issuing the lease alone does not activate the storage fence until storage observes it. Assert behavior across storage restart, operation retries and reordered messages.

برای یادگیریِ عمیق است؛ انجام کاملش در زمان بحثِ مصاحبه انتظار نمی‌رود.The full laboratory is optional depth work and is not expected within the discussion time.

ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · Kleppmann · distributed locking and fencing · Atlassian · engineering interview handbook

تمرین ۴ — safety/livenessExercise 4 — safety/liveness

L2 · SeniorL2 · Seniorوابسته به نقشRole-specific25 دقیقه بحث25 min discussion

safety/liveness برای استدلال خوب است؛ مثال at-most-once delivery می‌تواند به از دست رفتن پیام منجر شود و فرض progress باید صریح باشد.Progress requires explicit recovery, network and capacity assumptions.

نسخهٔ بازبینی‌شده برای مصاحبهReviewed interview brief

برای صف، safety «اثر پیام بیش از یک بار اعمال نشود» و liveness «پیام معتبر تحت بازیابی شبکه بالاخره اثر کند» تعریف کن. partition دائمی، redelivery و از دست رفتن dedup را تحلیل کن. برای هر ادعا یک counterexample و فرض زمانی روشن بده.Define no duplicate application effects as safety and eventual valid-message effects under recovery as liveness. Give counterexamples for permanent partitions, redelivery and missing dedup state.

معیار پاسخ خوبWhat a good answer demonstrates

  • delivery را از application effect جدا می‌کندDelivery versus effects
  • liveness بدون فرض شبکه/ظرفیت قول داده نمی‌شودExplicit progress assumptions
  • مثال safety violation می‌تواند همراه progress باشدIndependent safety/liveness counterexamples

پیگیری مصاحبه:Interview follow-up: سیستم همهٔ پیام‌ها را رد کند؛ کدام خاصیت ظاهراً سالم می‌ماند؟The service rejects every message. Which property can appear healthy?

آزمایشگاه تکمیلی · زمان تقریبی ۱–۲ ساعتExtended hands-on laboratory · estimated 1–2 hours

برای یک سرویسِ صف‌محورِ ساده، دو خاصیتِ safety و liveness بنویس (مثلاً «هر پیام حداکثر یک‌بار تحویل می‌شود» و «هر پیامِ ارسال‌شده بالاخره تحویل می‌شود»). بعد یک سناریوی خرابیِ شبکه طراحی کن که liveness را نقض کند ولی safety نه. آیا برعکسش ممکن است؟

For a simple queue-based service, write two properties — one safety, one liveness (e.g. “each message is delivered at most once” and “each sent message is eventually delivered”). Then design a network failure breaking liveness but not safety. Is the reverse possible?

برای یادگیریِ عمیق است؛ انجام کاملش در زمان بحثِ مصاحبه انتظار نمی‌رود.The full laboratory is optional depth work and is not expected within the discussion time.

ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · Jepsen · consistency models · Kafka 4.1 · design and delivery semantics

نکته‌ی پایانیClosing note

این فصل عمداً فقط «دردسر» را نشان می‌دهد. فصل ۱۰ همان مسایل را با الگوریتم‌های consensus، linearizability و replicationِ لاگ‌محور حل می‌کند. پس اگر الان احساس می‌کنی «همه‌چیز شکننده است»، دقیقاً حسِ درستی داری — فصل بعد نسخه‌ی درمان است.

This chapter deliberately shows only the “trouble”. Chapter 10 solves the same problems with consensus, linearizability and log-based replication. So if everything feels fragile right now, that's exactly the right feeling — the next chapter is the cure.